Skip to content

Commit e60d46a

Browse files
committed
fix sakura api base handling
1 parent f91e867 commit e60d46a

4 files changed

Lines changed: 92 additions & 12 deletions

File tree

doc/CHANGELOG_v3.0.0.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# v3.0.0 更新日志
2+
3+
发布日期:2026-06-28
4+
5+
## 🐛 修复与改进
6+
7+
### AI 渲染器文本替换修复
8+
- 修复了在使用 AI 渲染器(OpenAI / Gemini)时,`text_replacements.yaml` 中配置的文本替换规则(如通用/横排/竖排符号或文本正则替换)不生效的问题。现在 AI 渲染模块在组装翻译 Prompt 前会正确加载并应用该规则。
9+
10+
### AI 渲染器前置画框与坐标去除(极简参考原文渲染 + 彻底擦除原文防重叠)
11+
- 彻底去除了 AI 渲染器在发送的前置图像上绘制红色编号框与数字的逻辑,直接发送干净的去字/原图背景。
12+
- 移除了冗长且大模型不易精确理解的绝对像素坐标,改为极简提示词,让模型在渲染时根据“原文(original text)”参考定位气泡。
13+
- **强制擦除原文防重叠**:优化了默认提示词,明确要求大模型在渲染翻译前,必须**完全擦除原图上对应的日语文本**,防止翻译后的文字与原日语文字在气泡中重叠或并存。同时,也会自动识别并干净擦除所有残留的日语注音假名(Furigana/Rubi 字符)以及漏检文本,并将其转换为翻译渲染,保证整页漫画画面百分之百干净。
14+
15+
### 修复异常文本误判导致 API 密钥被误拉黑的问题
16+
- 修复了当 AI 渲染失败抛出通用 `RuntimeError` 异常时,其文本描述中包含 "HTTP 404" 的排查细节信息会被轮询系统(`api_key_rotation.py`)的正则表达式抓取,误判定为发生了真实的 HTTP 404 永久网络错误,从而导致该 API Key 被错误的拉黑屏蔽。现在系统在提取状态码时会自动过滤并跳过 `RuntimeError``ValueError` 等内置异常类型的正则匹配,避免发生误拉黑。
17+
18+
### 修复渲染报错被错误归类为“模型不支持多模态”的问题
19+
- 修复了当 AI 渲染发生其他错误(例如 API 404 响应细节描述)而抛出异常时,由于异常消息中带有 `"vision"``"image_url"` 关键字,导致客户端 UI 界面(`app_logic.py`)将其误判并显示为“模型不支持多模态输入”引导信息的 Bug。现已增加豁免判定,排除由渲染器(Renderer)抛出的此类异常误匹配。
20+
21+
### 修复 Sakura API 地址更新与失败处理
22+
- 修复 Sakura 翻译器只读取启动时 `SAKURA_API_BASE` 的问题。现在 UI、Web 用户配置或预设中修改 Sakura 地址后,会在翻译任务解析配置时重新应用,避免地址改为 8081 后实际仍连接 8080。
23+
- Sakura 翻译开始时会在日志中输出当前连接地址,连接重试与最终失败日志也会带上地址,便于定位实际请求的端口。
24+
- 修复 Sakura 连接失败达到重试上限后返回原文、导致流程继续渲染的问题。现在会抛出异常并终止任务,避免把原文误当翻译结果。

manga_translator/server/routes/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@
8484
"GEMINI_API_KEY",
8585
"GEMINI_API_BASE",
8686
"GEMINI_MODEL",
87+
"SAKURA_API_BASE",
88+
"SAKURA_DICT_PATH",
8789
"OCR_OPENAI_API_KEY",
8890
"OCR_OPENAI_API_BASE",
8991
"OCR_OPENAI_MODEL",

manga_translator/server/runtime_api.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
from typing import Optional
22

33
RUNTIME_API_ENV_PRIORITY = {
4+
"translator": {
5+
"sakura": {
6+
"api_base": ["SAKURA_API_BASE"],
7+
},
8+
},
49
"ocr": {
510
"openai": {
611
"api_key": ["OCR_OPENAI_API_KEY"],

manga_translator/translators/sakura.py

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@
99
import logging
1010
from typing import Callable, List, Tuple
1111

12+
from ..runtime_api_resolver import get_runtime_api_override
1213
from .common import CommonTranslator, validate_openai_response
1314
from .keys import SAKURA_API_BASE, SAKURA_DICT_PATH
1415

16+
DEFAULT_SAKURA_API_BASE = 'http://127.0.0.1:8080/v1'
17+
DEFAULT_SAKURA_DICT_PATH = './dict/sakura_dict.txt'
18+
1519

1620
class SakuraDict():
1721
def __init__(self, path: str, logger: logging.Logger) -> None:
@@ -209,12 +213,10 @@ class SakuraTranslator(CommonTranslator):
209213

210214
def __init__(self):
211215
super().__init__()
212-
self.client = openai.AsyncOpenAI(api_key = openai.api_key or 'empty')
213-
if "/v1" not in SAKURA_API_BASE:
214-
self.client.base_url = SAKURA_API_BASE + "/v1"
215-
else:
216-
self.client.base_url = SAKURA_API_BASE
217-
self.client.api_key = "sk-114514"
216+
self.api_base = self._normalize_api_base(os.getenv('SAKURA_API_BASE') or SAKURA_API_BASE)
217+
self.dict_path = os.getenv('SAKURA_DICT_PATH') or SAKURA_DICT_PATH or DEFAULT_SAKURA_DICT_PATH
218+
self.client = None
219+
self._setup_client()
218220
self.temperature = 0.3
219221
self.top_p = 0.3
220222
self.frequency_penalty = 0.1
@@ -223,8 +225,56 @@ def __init__(self):
223225
self._heart_pattern = re.compile(r'❤')
224226
self.sakura_dict = SakuraDict(self.get_dict_path(), self.logger)
225227

228+
@staticmethod
229+
def _normalize_api_base(api_base: str) -> str:
230+
normalized = (
231+
str(api_base or SAKURA_API_BASE or DEFAULT_SAKURA_API_BASE).strip()
232+
or DEFAULT_SAKURA_API_BASE
233+
)
234+
normalized = normalized.rstrip("/")
235+
if "/v1" not in normalized:
236+
normalized = f"{normalized}/v1"
237+
return normalized
238+
239+
def _setup_client(self):
240+
self.client = openai.AsyncOpenAI(
241+
api_key="sk-114514",
242+
base_url=self.api_base,
243+
)
244+
245+
def parse_args(self, config):
246+
super().parse_args(config)
247+
translator_args = self._resolve_translator_config(config)
248+
user_env_vars = getattr(config, "_user_env_vars", None) or {}
249+
runtime_override = get_runtime_api_override(config, "translator", "sakura")
250+
251+
api_base = (
252+
self._get_config_value(translator_args, "user_api_base", None)
253+
or runtime_override.get("api_base")
254+
or user_env_vars.get("SAKURA_API_BASE")
255+
or os.getenv("SAKURA_API_BASE")
256+
or SAKURA_API_BASE
257+
or DEFAULT_SAKURA_API_BASE
258+
)
259+
api_base = self._normalize_api_base(api_base)
260+
if api_base != self.api_base:
261+
self.api_base = api_base
262+
self._setup_client()
263+
self.logger.info(f"Sakura API base updated: {self.api_base}")
264+
265+
dict_path = (
266+
user_env_vars.get("SAKURA_DICT_PATH")
267+
or os.getenv("SAKURA_DICT_PATH")
268+
or SAKURA_DICT_PATH
269+
or DEFAULT_SAKURA_DICT_PATH
270+
)
271+
dict_path = str(dict_path or DEFAULT_SAKURA_DICT_PATH).strip() or DEFAULT_SAKURA_DICT_PATH
272+
if dict_path != self.dict_path:
273+
self.dict_path = dict_path
274+
self.sakura_dict = SakuraDict(self.get_dict_path(), self.logger)
275+
226276
def get_dict_path(self):
227-
return SAKURA_DICT_PATH
277+
return getattr(self, 'dict_path', None) or SAKURA_DICT_PATH or DEFAULT_SAKURA_DICT_PATH
228278

229279
def detect_and_caculate_repeats(self, s: str, threshold: int = _REPEAT_DETECT_THRESHOLD, remove_all=True) -> Tuple[bool, str, int, str]:
230280
"""
@@ -457,6 +507,7 @@ def _delete_quotation_mark(self, texts: List[str]) -> List[str]:
457507

458508
async def _translate(self, from_lang: str, to_lang: str, queries: List[str], ctx=None) -> List[str]:
459509
self.logger.debug(f'Temperature: {self.temperature}, TopP: {self.top_p}')
510+
self.logger.info(f'Sakura当前连接地址: {self.api_base}')
460511
self.logger.debug(f'原文: {queries}')
461512
text_prompt = '\n'.join(queries)
462513
self.logger.debug('-- Sakura Prompt --\n' + self._format_prompt_log(text_prompt) + '\n\n')
@@ -500,11 +551,9 @@ async def _handle_translation_request(self, prompt) -> str:
500551
except (openai.APIError, openai.APIConnectionError) as e:
501552
server_error_attempt += 1
502553
if server_error_attempt >= self._RETRY_ATTEMPTS:
503-
self.logger.error(f'Sakura API请求失败。错误信息: {e}')
504-
if isinstance(prompt, list):
505-
return "\n".join(prompt)
506-
return str(prompt)
507-
self.logger.warning(f'Sakura因服务器错误而进行重试。尝试次数: {server_error_attempt},错误信息: {e}')
554+
self.logger.error(f'Sakura API请求失败。地址:{self.api_base},错误信息: {e}')
555+
raise Exception(f'Sakura API请求失败(地址:{self.api_base}):{e}') from e
556+
self.logger.warning(f'Sakura因服务器错误而进行重试。地址:{self.api_base},尝试次数: {server_error_attempt},错误信息: {e}')
508557

509558
return response
510559

0 commit comments

Comments
 (0)