feat:自动根据环境变量填写前端输入项,支持术语表与领域知识
This commit is contained in:
29
.env.example
Normal file
29
.env.example
Normal file
@@ -0,0 +1,29 @@
|
||||
# DocuTranslate 环境变量配置示例
|
||||
# 复制此文件为 .env 并按需修改(需配合 python-dotenv 或容器环境变量使用)
|
||||
|
||||
# --- 代理配置 ---
|
||||
# 是否启用系统代理,设置为 true 开启
|
||||
# DOCUTRANSLATE_PROXY_ENABLED=true
|
||||
|
||||
# --- 缓存配置 ---
|
||||
# 任务缓存数量(默认 10)
|
||||
# DOCUTRANSLATE_CACHE_NUM=10
|
||||
|
||||
# --- 翻译 API 默认配置 ---
|
||||
# 前端"自定义接口-default"平台的默认值,留空则不预填
|
||||
|
||||
# API 地址(Base URL),例如 https://api.openai.com/v1
|
||||
DOCUTRANSLATE_BASE_URL=
|
||||
|
||||
# API 密钥
|
||||
DOCUTRANSLATE_API_KEY=
|
||||
|
||||
# 模型 ID,例如 qwen-mt-turbo
|
||||
DOCUTRANSLATE_MODEL_ID=
|
||||
|
||||
# --- 限流配置 ---
|
||||
# RPM 限制(每分钟请求数),留空则不限制
|
||||
DOCUTRANSLATE_RPM=
|
||||
|
||||
# TPM 限制(每分钟 Token 数),留空则不限制
|
||||
DOCUTRANSLATE_TPM=
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -13,4 +13,7 @@ docutranslate/output/
|
||||
#idea
|
||||
.idea/
|
||||
#claude
|
||||
.claude/
|
||||
.claude/
|
||||
/.omc/
|
||||
# Environment variables
|
||||
.env
|
||||
|
||||
@@ -59,6 +59,7 @@ class AgentConfig:
|
||||
rpm: int | None = None # 每分钟请求数限制
|
||||
tpm: int | None = None # 每分钟Token数限制
|
||||
provider: ProviderType | None = None
|
||||
source_lang: str | None = None # qwen-mt: 源语言
|
||||
|
||||
|
||||
class TotalErrorCounter:
|
||||
@@ -534,9 +535,10 @@ class Agent:
|
||||
|
||||
self.provider = config.provider if config.provider is not None else get_provider_by_domain(self.domain)
|
||||
self.is_mt_mode = "mt" in self.model_id.lower()
|
||||
self.mt_source_lang = getattr(config, "source_lang", "auto")
|
||||
self.mt_source_lang = config.source_lang if config.source_lang else "auto"
|
||||
self.mt_target_lang = getattr(config, "to_lang", None)
|
||||
self.mt_domains = getattr(config, "custom_prompt", None)
|
||||
self.mt_glossary_dict = getattr(config, "glossary_dict", None)
|
||||
|
||||
def _estimate_tokens(self, text: str) -> int:
|
||||
"""
|
||||
@@ -588,7 +590,7 @@ class Agent:
|
||||
return _MT_LANG_ALIASES[key]
|
||||
return lang_text
|
||||
|
||||
def _build_mt_translation_options(self) -> dict:
|
||||
def _build_mt_translation_options(self, prompt: str = "") -> dict:
|
||||
translation_options = {}
|
||||
|
||||
source_lang = self._normalize_mt_lang(self.mt_source_lang)
|
||||
@@ -603,6 +605,15 @@ class Agent:
|
||||
if domains:
|
||||
translation_options["domains"] = domains
|
||||
|
||||
if self.mt_glossary_dict:
|
||||
terminology_list = [
|
||||
{"source": src, "target": tgt}
|
||||
for src, tgt in self.mt_glossary_dict.items()
|
||||
if src and tgt and src.lower() in prompt.lower()
|
||||
]
|
||||
if terminology_list:
|
||||
translation_options["terms"] = terminology_list
|
||||
|
||||
return translation_options
|
||||
|
||||
def _build_mt_user_prompt(self, prompt: str, system_prompt: str) -> str:
|
||||
@@ -627,7 +638,7 @@ class Agent:
|
||||
{"role": "user", "content": self._build_mt_user_prompt(prompt, system_prompt)},
|
||||
],
|
||||
}
|
||||
translation_options = self._build_mt_translation_options()
|
||||
translation_options = self._build_mt_translation_options(prompt=prompt)
|
||||
if translation_options:
|
||||
data["translation_options"] = translation_options
|
||||
return headers, data
|
||||
|
||||
@@ -2472,6 +2472,25 @@ async def service_flat_translate(
|
||||
})
|
||||
|
||||
|
||||
@app.get("/api/config", tags=["Config"], summary="获取服务端环境变量默认配置")
|
||||
async def get_config():
|
||||
"""返回由服务端环境变量预设的前端默认配置,不含敏感信息(API key 仅返回是否存在)。"""
|
||||
from docutranslate.environment import (
|
||||
DOCUTRANSLATE_BASE_URL,
|
||||
DOCUTRANSLATE_API_KEY,
|
||||
DOCUTRANSLATE_MODEL_ID,
|
||||
DOCUTRANSLATE_RPM,
|
||||
DOCUTRANSLATE_TPM,
|
||||
)
|
||||
return JSONResponse({
|
||||
"base_url": DOCUTRANSLATE_BASE_URL,
|
||||
"api_key": DOCUTRANSLATE_API_KEY,
|
||||
"model_id": DOCUTRANSLATE_MODEL_ID,
|
||||
"rpm": DOCUTRANSLATE_RPM,
|
||||
"tpm": DOCUTRANSLATE_TPM,
|
||||
})
|
||||
|
||||
|
||||
@app.get("/", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def main_page():
|
||||
index_path = Path(STATIC_DIR) / "index.html"
|
||||
|
||||
@@ -60,7 +60,7 @@ def create_workflow_from_payload(payload: TranslatePayload, logger: logging.Logg
|
||||
# 1. Markdown Based Workflow
|
||||
if isinstance(payload, MarkdownWorkflowParams):
|
||||
translator_args = payload.model_dump(
|
||||
include={"skip_translate", "base_url", "api_key", "model_id", "to_lang", "custom_prompt",
|
||||
include={"skip_translate", "base_url", "api_key", "model_id", "to_lang", "source_lang", "custom_prompt",
|
||||
"temperature", "thinking", "chunk_size", "concurrent", "glossary_dict", "timeout",
|
||||
"retry", "system_proxy_enable", "force_json", "rpm", "tpm", "provider"},
|
||||
exclude_none=True,
|
||||
|
||||
@@ -157,6 +157,9 @@ class BaseWorkflowParams(BaseModel):
|
||||
custom_prompt: Optional[str] = Field(
|
||||
default="", description="用户自定义的翻译Prompt。", alias="custom_prompt"
|
||||
)
|
||||
source_lang: Optional[str] = Field(
|
||||
default=None, description="源语言(qwen-mt系列模型专用,如 'Chinese'、'English')。", examples=[None]
|
||||
)
|
||||
glossary_dict: Optional[Dict[str, str]] = Field(
|
||||
None, description="术语表字典,key为原文,value为译文。", examples=[None]
|
||||
)
|
||||
|
||||
41
docutranslate/environment.py
Normal file
41
docutranslate/environment.py
Normal file
@@ -0,0 +1,41 @@
|
||||
# SPDX-FileCopyrightText: 2025 QinHan
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
"""
|
||||
集中管理所有环境变量。
|
||||
所有 os.getenv() 调用应在此处统一声明,其他模块从这里导入。
|
||||
"""
|
||||
import os
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv() # 自动从项目根目录的 .env 文件加载环境变量(不覆盖已有的 shell 变量)
|
||||
|
||||
|
||||
# --- 代理配置 ---
|
||||
# 是否启用系统代理,设置为 "true" 开启
|
||||
DOCUTRANSLATE_PROXY_ENABLED: bool = (
|
||||
os.getenv("DOCUTRANSLATE_PROXY_ENABLED", "").lower() == "true"
|
||||
)
|
||||
|
||||
# --- 缓存配置 ---
|
||||
# 任务缓存数量
|
||||
DOCUTRANSLATE_CACHE_NUM: int = int(os.getenv("DOCUTRANSLATE_CACHE_NUM", "10"))
|
||||
|
||||
# --- 翻译 API 默认配置 ---
|
||||
# 默认 API 地址 (自定义接口的 Base URL)
|
||||
DOCUTRANSLATE_BASE_URL: str = os.getenv("DOCUTRANSLATE_BASE_URL", "")
|
||||
|
||||
# 默认 API 密钥
|
||||
DOCUTRANSLATE_API_KEY: str = os.getenv("DOCUTRANSLATE_API_KEY", "")
|
||||
|
||||
# 默认模型 ID
|
||||
DOCUTRANSLATE_MODEL_ID: str = os.getenv("DOCUTRANSLATE_MODEL_ID", "")
|
||||
|
||||
# --- 限流默认配置 ---
|
||||
# 默认 RPM 限制 (Requests Per Minute),不设置则不限制
|
||||
_rpm_str = os.getenv("DOCUTRANSLATE_RPM", "")
|
||||
DOCUTRANSLATE_RPM: int | None = int(_rpm_str) if _rpm_str.strip() else None
|
||||
|
||||
# 默认 TPM 限制 (Tokens Per Minute),不设置则不限制
|
||||
_tpm_str = os.getenv("DOCUTRANSLATE_TPM", "")
|
||||
DOCUTRANSLATE_TPM: int | None = int(_tpm_str) if _tpm_str.strip() else None
|
||||
@@ -1,16 +1,9 @@
|
||||
# SPDX-FileCopyrightText: 2025 QinHan
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
import os
|
||||
from docutranslate.environment import DOCUTRANSLATE_PROXY_ENABLED
|
||||
|
||||
from .conditional_import import available_packages, conditional_import
|
||||
|
||||
USE_PROXY = (
|
||||
True
|
||||
if (
|
||||
os.getenv("DOCUTRANSLATE_PROXY_ENABLED")
|
||||
and os.getenv("DOCUTRANSLATE_PROXY_ENABLED").lower() == "true"
|
||||
)
|
||||
else False
|
||||
)
|
||||
USE_PROXY = DOCUTRANSLATE_PROXY_ENABLED
|
||||
if USE_PROXY:
|
||||
print(f"USE_PROXY:{USE_PROXY}")
|
||||
|
||||
@@ -198,6 +198,7 @@ class Client:
|
||||
retry: Optional[int] = None,
|
||||
thinking: Optional[ThinkingMode] = None,
|
||||
custom_prompt: Optional[str] = None,
|
||||
source_lang: Optional[str] = None,
|
||||
system_proxy_enable: Optional[bool] = None,
|
||||
force_json: Optional[bool] = None,
|
||||
rpm: Optional[int] = None,
|
||||
@@ -264,6 +265,7 @@ class Client:
|
||||
retry: Optional[int] = None,
|
||||
thinking: Optional[ThinkingMode] = None,
|
||||
custom_prompt: Optional[str] = None,
|
||||
source_lang: Optional[str] = None,
|
||||
system_proxy_enable: Optional[bool] = None,
|
||||
force_json: Optional[bool] = None,
|
||||
rpm: Optional[int] = None,
|
||||
|
||||
@@ -596,6 +596,7 @@
|
||||
<option value="Portuguese">葡萄牙文(Português)</option>
|
||||
<option value="Arabic">阿拉伯文(العَرَبِيَّة)</option>
|
||||
<option value="Vietnamese">越南文(tiếng Việt)</option>
|
||||
<option value="Indonesian">印尼文(Bahasa Indonesia)</option>
|
||||
<option value="custom">{{ t('targetLanguageCustom') }}</option>
|
||||
</select>
|
||||
<div class="mt-2" v-if="form.to_lang === 'custom'">
|
||||
@@ -673,7 +674,7 @@
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
||||
data-bs-target="#collapseGlossary">
|
||||
<strong><span class="step-number">{{ stepMap.glossary }} </span><i
|
||||
<strong><i
|
||||
class="bi bi-journal-bookmark me-2"></i><span>{{ t('glossaryGenTitle')
|
||||
}}</span></strong>
|
||||
</button>
|
||||
@@ -698,148 +699,6 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch mb-3 border-top pt-3">
|
||||
<input class="form-check-input" type="checkbox" role="switch"
|
||||
v-model="form.glossary_generate_enable"
|
||||
@change="saveSetting('glossary_generate_enable', form.glossary_generate_enable)">
|
||||
<label class="form-check-label">{{ t('glossaryGenEnableLabel') }}</label>
|
||||
</div>
|
||||
|
||||
<div v-if="form.glossary_generate_enable">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{{ t('glossaryCustomPromptLabel') }}</label>
|
||||
<textarea class="form-control"
|
||||
v-model="form.glossary_agent_custom_prompt"
|
||||
@input="saveSetting('glossary_agent_custom_prompt', form.glossary_agent_custom_prompt)"
|
||||
rows="3"
|
||||
:placeholder="t('glossaryCustomPromptPlaceholder')"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{{ t('glossaryGenConfigLabel') }}</label>
|
||||
<div class="btn-group w-100">
|
||||
<input type="radio" class="btn-check" value="same" id="gSame"
|
||||
v-model="form.glossary_agent_config_choice"
|
||||
@change="saveSetting('glossary_agent_config_choice', 'same')">
|
||||
<label class="btn btn-outline-primary"
|
||||
for="gSame">{{ t('glossaryGenConfigSame') }}</label>
|
||||
<input type="radio" class="btn-check" value="custom" id="gCustom"
|
||||
v-model="form.glossary_agent_config_choice"
|
||||
@change="saveSetting('glossary_agent_config_choice', 'custom')">
|
||||
<label class="btn btn-outline-primary"
|
||||
for="gCustom">{{ t('glossaryGenConfigCustom') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="form.glossary_agent_config_choice === 'custom'"
|
||||
class="border p-3 rounded">
|
||||
<platform-selector
|
||||
v-model:platform="form.glossary_agent_platform"
|
||||
v-model:base-url="form.glossary_agent_baseurl"
|
||||
v-model:api-key="form.glossary_agent_key"
|
||||
v-model:model-id="form.glossary_agent_model_id"
|
||||
v-model:provider="form.glossary_agent_provider"
|
||||
:t="t" prefix="glossary_agent_platform"></platform-selector>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{{ t('targetLanguageLabel') }}</label>
|
||||
<select class="form-select" v-model="form.glossary_agent_to_lang"
|
||||
@change="saveSetting('glossary_agent_to_lang', form.glossary_agent_to_lang)">
|
||||
<option value="Simplified Chinese">中文(简体中文)</option>
|
||||
<option value="English">英文(English)</option>
|
||||
<option value="Spanish">西班牙文(Español)</option>
|
||||
<option value="French">法文(Français)</option>
|
||||
<option value="German">德文(Deutsch)</option>
|
||||
<option value="Japanese">日文(日本語)</option>
|
||||
<option value="Korean">韩文(한국어)</option>
|
||||
<option value="Russian">俄文(Русский)</option>
|
||||
<option value="Portuguese">葡萄牙文(Português)</option>
|
||||
<option value="Arabic">阿拉伯文(العَرَبِيَّة)</option>
|
||||
<option value="Vietnamese">越南文(tiếng Việt)</option>
|
||||
<option value="custom">{{ t('targetLanguageCustom') }}</option>
|
||||
</select>
|
||||
<div class="mt-2" v-if="form.glossary_agent_to_lang === 'custom'">
|
||||
<input type="text" class="form-control"
|
||||
v-model="form.glossary_agent_custom_to_lang"
|
||||
@input="saveSetting('glossary_agent_custom_to_lang', form.glossary_agent_custom_to_lang)"
|
||||
:placeholder="t('customLangPlaceholder')">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<slider-control :label="t('chunkSizeLabel')"
|
||||
v-model="form.glossary_agent_chunk_size"
|
||||
save-key="glossary_agent_chunk_size"
|
||||
:default-val="defaultParams.chunk_size" :min="1000"
|
||||
:max="8000" :step="100" :t="t"></slider-control>
|
||||
<slider-control :label="t('concurrentLabel')"
|
||||
v-model="form.glossary_agent_concurrent"
|
||||
save-key="glossary_agent_concurrent"
|
||||
:default-val="defaultParams.concurrent" :min="1"
|
||||
:max="120" :step="1" :t="t"></slider-control>
|
||||
<slider-control label="Temperature"
|
||||
v-model="form.glossary_agent_temperature"
|
||||
save-key="glossary_agent_temperature" :default-val="0.7"
|
||||
:min="0" :max="2" :step="0.1" :t="t"></slider-control>
|
||||
<slider-control :label="t('retryLabel')"
|
||||
v-model="form.glossary_agent_retry"
|
||||
save-key="glossary_agent_retry"
|
||||
:default-val="defaultParams.retry" :min="1" :max="6"
|
||||
:step="1" :t="t"></slider-control>
|
||||
|
||||
<!-- Glossary Agent RPM/TPM [Vertical Layout] -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">RPM <small
|
||||
class="text-muted">({{ t('rpmLabel')
|
||||
}})</small></label>
|
||||
<input type="number" class="form-control"
|
||||
v-model="form.glossary_agent_rpm"
|
||||
@input="saveSetting('glossary_agent_rpm', form.glossary_agent_rpm)"
|
||||
min="1" :placeholder="t('unlimitedPlaceholder')">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">TPM <small
|
||||
class="text-muted">({{ t('tpmLabel')
|
||||
}})</small></label>
|
||||
<input type="number" class="form-control"
|
||||
v-model="form.glossary_agent_tpm"
|
||||
@input="saveSetting('glossary_agent_tpm', form.glossary_agent_tpm)"
|
||||
min="1" :placeholder="t('unlimitedPlaceholder')">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{{ t('thinkingModeLabel') }}</label>
|
||||
<div class="btn-group w-100">
|
||||
<input type="radio" class="btn-check" value="enable"
|
||||
id="gtEnable" v-model="form.glossary_agent_thinking"
|
||||
@change="saveSetting('glossary_agent_thinking_mode', 'enable')">
|
||||
<label class="btn btn-outline-primary"
|
||||
for="gtEnable">{{ t('thinkingModeEnable') }}</label>
|
||||
<input type="radio" class="btn-check" value="disable"
|
||||
id="gtDisable" v-model="form.glossary_agent_thinking"
|
||||
@change="saveSetting('glossary_agent_thinking_mode', 'disable')">
|
||||
<label class="btn btn-outline-primary"
|
||||
for="gtDisable">{{ t('thinkingModeDisable') }}</label>
|
||||
<input type="radio" class="btn-check" value="default"
|
||||
id="gtDefault" v-model="form.glossary_agent_thinking"
|
||||
@change="saveSetting('glossary_agent_thinking_mode', 'default')">
|
||||
<label class="btn btn-outline-primary"
|
||||
for="gtDefault">{{ t('thinkingModeDefault') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-check form-switch mb-3">
|
||||
<input class="form-check-input" type="checkbox" role="switch"
|
||||
v-model="form.glossary_agent_system_proxy_enable"
|
||||
@change="saveSetting('glossary_agent_system_proxy_enable', form.glossary_agent_system_proxy_enable)">
|
||||
<label class="form-check-label">{{ t('systemProxyLabel') }}</label>
|
||||
</div>
|
||||
<div class="form-check form-switch mb-3">
|
||||
<input class="form-check-input" type="checkbox" role="switch"
|
||||
v-model="form.glossary_agent_force_json"
|
||||
@change="saveSetting('glossary_agent_force_json', form.glossary_agent_force_json)">
|
||||
<label class="form-check-label">{{ t('forceJson') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1447,25 +1306,6 @@
|
||||
retry: 3,
|
||||
rpm: null, // New RPM
|
||||
tpm: null, // New TPM
|
||||
glossary_generate_enable: false,
|
||||
glossary_agent_custom_prompt: '',
|
||||
glossary_agent_config_choice: 'same',
|
||||
glossary_agent_platform: 'https://api.302.ai/v1',
|
||||
glossary_agent_baseurl: '',
|
||||
glossary_agent_key: '',
|
||||
glossary_agent_model_id: '',
|
||||
glossary_agent_provider: 'api.openai.com', // Default glossary provider
|
||||
glossary_agent_to_lang: 'Simplified Chinese',
|
||||
glossary_agent_custom_to_lang: '',
|
||||
glossary_agent_chunk_size: 1000,
|
||||
glossary_agent_concurrent: 5,
|
||||
glossary_agent_temperature: 0.7,
|
||||
glossary_agent_retry: 3,
|
||||
glossary_agent_thinking: 'default',
|
||||
glossary_agent_system_proxy_enable: false,
|
||||
glossary_agent_force_json: false,
|
||||
glossary_agent_rpm: null, // New Glossary Agent RPM
|
||||
glossary_agent_tpm: null // New Glossary Agent TPM
|
||||
});
|
||||
|
||||
// Nested Params for specific workflows
|
||||
@@ -1497,7 +1337,7 @@
|
||||
return (v === null || v === '' || v === 'null') ? null : Number(v);
|
||||
};
|
||||
|
||||
form.workflow_type = get('translator_last_workflow', 'markdown_based');
|
||||
form.workflow_type = get('translator_last_workflow', 'docx');
|
||||
form.auto_workflow_enabled = getBool('translator_auto_workflow_enabled', true);
|
||||
form.convert_engine = get('translator_convert_engin', 'mineru');
|
||||
form.mineru_token = get('translator_mineru_token', '');
|
||||
@@ -1516,12 +1356,12 @@
|
||||
form.formula_ocr = getBool('translator_formula_ocr', true);
|
||||
form.code_ocr = getBool('translator_code_ocr', true);
|
||||
form.skip_translate = getBool('translator_skip_translate', false);
|
||||
form.platform = get('translator_platform_last_platform', 'https://api.302.ai/v1');
|
||||
form.platform = get('translator_platform_last_platform', 'custom');
|
||||
form.system_proxy_enable = getBool('translator_system_proxy_enable', false);
|
||||
form.force_json = getBool('translator_force_json', false);
|
||||
form.to_lang = get('translator_to_lang', 'Simplified Chinese');
|
||||
form.custom_to_lang = get('translator_custom_to_lang', '');
|
||||
form.thinking = get('translator_thinking_mode', 'disable');
|
||||
form.thinking = get('translator_thinking_mode', 'default');
|
||||
form.custom_prompt = get('custom_prompt', '');
|
||||
form.chunk_size = getNum('chunk_size', 1000);
|
||||
form.concurrent = getNum('concurrent', 5);
|
||||
@@ -1539,30 +1379,6 @@
|
||||
form.provider = platObj ? platObj.provider : '';
|
||||
}
|
||||
|
||||
form.glossary_generate_enable = getBool('glossary_generate_enable', false);
|
||||
form.glossary_agent_custom_prompt = get('glossary_agent_custom_prompt', '');
|
||||
form.glossary_agent_config_choice = get('glossary_agent_config_choice', 'same');
|
||||
form.glossary_agent_platform = get('glossary_agent_platform_last_platform', 'https://api.302.ai/v1');
|
||||
form.glossary_agent_to_lang = get('glossary_agent_to_lang', 'Simplified Chinese');
|
||||
form.glossary_agent_custom_to_lang = get('glossary_agent_custom_to_lang', '');
|
||||
form.glossary_agent_chunk_size = getNum('glossary_agent_chunk_size', 1000);
|
||||
form.glossary_agent_concurrent = getNum('glossary_agent_concurrent', 5);
|
||||
form.glossary_agent_temperature = getNum('glossary_agent_temperature', 0.7);
|
||||
form.glossary_agent_retry = getNum('glossary_agent_retry', 3);
|
||||
form.glossary_agent_thinking = get('glossary_agent_thinking_mode', 'default');
|
||||
form.glossary_agent_system_proxy_enable = getBool('glossary_agent_system_proxy_enable', false);
|
||||
form.glossary_agent_force_json = getBool('glossary_agent_force_json', false);
|
||||
form.glossary_agent_rpm = getNumOrNull('glossary_agent_rpm'); // Load Glossary RPM
|
||||
form.glossary_agent_tpm = getNumOrNull('glossary_agent_tpm'); // Load Glossary TPM
|
||||
|
||||
// Determine Glossary Provider
|
||||
const gPlatObj = KNOWN_PLATFORMS.find(p => p.val === form.glossary_agent_platform);
|
||||
if (form.glossary_agent_platform === 'custom') {
|
||||
form.glossary_agent_provider = get('glossary_agent_platform_custom_provider', 'default');
|
||||
} else {
|
||||
form.glossary_agent_provider = gPlatObj ? gPlatObj.provider : '';
|
||||
}
|
||||
|
||||
// Restore workflow specific params
|
||||
['txt', 'xlsx', 'docx', 'srt', 'epub', 'html', 'ass', 'pptx'].forEach(t => {
|
||||
workflowParams[t].insert_mode = get(`translator_${t}_insert_mode`, 'replace');
|
||||
@@ -1574,7 +1390,6 @@
|
||||
|
||||
// Trigger platform updates to load API keys/models
|
||||
updatePlatformParams(form.platform, 'translator_platform', form);
|
||||
updatePlatformParams(form.glossary_agent_platform, 'glossary_agent_platform', form, true);
|
||||
};
|
||||
|
||||
// --- 新增:专门用于将当前 form 数据全部写入 localStorage 的函数 ---
|
||||
@@ -1626,30 +1441,7 @@
|
||||
s('translator_provider', f.provider);
|
||||
if (f.platform === 'custom') s('translator_platform_custom_base_url', f.base_url);
|
||||
|
||||
// 2. 术语表相关
|
||||
s('glossary_generate_enable', f.glossary_generate_enable);
|
||||
s('glossary_agent_custom_prompt', f.glossary_agent_custom_prompt);
|
||||
s('glossary_agent_config_choice', f.glossary_agent_config_choice);
|
||||
s('glossary_agent_platform_last_platform', f.glossary_agent_platform);
|
||||
s('glossary_agent_to_lang', f.glossary_agent_to_lang);
|
||||
s('glossary_agent_custom_to_lang', f.glossary_agent_custom_to_lang);
|
||||
s('glossary_agent_chunk_size', f.glossary_agent_chunk_size);
|
||||
s('glossary_agent_concurrent', f.glossary_agent_concurrent);
|
||||
s('glossary_agent_temperature', f.glossary_agent_temperature);
|
||||
s('glossary_agent_retry', f.glossary_agent_retry);
|
||||
s('glossary_agent_thinking_mode', f.glossary_agent_thinking);
|
||||
s('glossary_agent_system_proxy_enable', f.glossary_agent_system_proxy_enable);
|
||||
s('glossary_agent_force_json', f.glossary_agent_force_json);
|
||||
s('glossary_agent_rpm', f.glossary_agent_rpm || '');
|
||||
s('glossary_agent_tpm', f.glossary_agent_tpm || '');
|
||||
|
||||
// 术语表平台 Key
|
||||
s(`glossary_agent_platform_${f.glossary_agent_platform}_apikey`, f.glossary_agent_key);
|
||||
s(`glossary_agent_platform_${f.glossary_agent_platform}_model_id`, f.glossary_agent_model_id);
|
||||
s('glossary_agent_provider', f.glossary_agent_provider);
|
||||
if (f.glossary_agent_platform === 'custom') s('glossary_agent_platform_custom_base_url', f.glossary_agent_baseurl);
|
||||
|
||||
// 3. 自动循环保存所有具体工作流参数 (txt, docx, xlsx...)
|
||||
// 2. 自动循环保存所有具体工作流参数 (txt, docx, xlsx...)
|
||||
for (const [wfType, params] of Object.entries(workflowParams)) {
|
||||
for (const [key, val] of Object.entries(params)) {
|
||||
s(`translator_${wfType}_${key}`, val);
|
||||
@@ -1657,27 +1449,17 @@
|
||||
}
|
||||
};
|
||||
|
||||
const updatePlatformParams = (plat, prefix, target, isGlossary = false) => {
|
||||
const updatePlatformParams = (plat, prefix, target) => {
|
||||
const get = (k) => localStorage.getItem(k) || '';
|
||||
if (isGlossary) {
|
||||
target.glossary_agent_key = get(`${prefix}_${plat}_apikey`);
|
||||
target.glossary_agent_model_id = get(`${prefix}_${plat}_model_id`);
|
||||
target.glossary_agent_baseurl = plat === 'custom' ? get(`${prefix}_custom_base_url`) : plat;
|
||||
} else {
|
||||
target.api_key = get(`${prefix}_${plat}_apikey`);
|
||||
target.model_id = get(`${prefix}_${plat}_model_id`);
|
||||
target.base_url = plat === 'custom' ? get(`${prefix}_custom_base_url`) : plat;
|
||||
}
|
||||
target.api_key = get(`${prefix}_${plat}_apikey`);
|
||||
target.model_id = get(`${prefix}_${plat}_model_id`);
|
||||
target.base_url = plat === 'custom' ? get(`${prefix}_custom_base_url`) : plat;
|
||||
};
|
||||
|
||||
watch(() => form.platform, (n) => {
|
||||
updatePlatformParams(n, 'translator_platform', form);
|
||||
});
|
||||
|
||||
watch(() => form.glossary_agent_platform, (n) => {
|
||||
updatePlatformParams(n, 'glossary_agent_platform', form, true);
|
||||
});
|
||||
|
||||
const t = (k) => {
|
||||
const dict = i18nData.value[currentLang.value] || i18nData.value['zh'] || {};
|
||||
return dict[k] || k;
|
||||
@@ -1751,12 +1533,11 @@
|
||||
// Dynamic Step Numbering
|
||||
const stepMap = computed(() => {
|
||||
let step = 2;
|
||||
const map = {specific: 0, parsing: 0, ai: 0, trans: 0, glossary: 0};
|
||||
const map = {specific: 0, parsing: 0, ai: 0, trans: 0};
|
||||
if (currentWorkflowConfig.value) map.specific = step++;
|
||||
if (form.workflow_type === 'markdown_based') map.parsing = step++;
|
||||
map.ai = step++;
|
||||
if (!form.skip_translate) map.trans = step++;
|
||||
map.glossary = step++;
|
||||
return map;
|
||||
});
|
||||
|
||||
@@ -1875,33 +1656,11 @@
|
||||
glossary_dict: Object.keys(glossaryData.value).length ? glossaryData.value : null,
|
||||
system_proxy_enable: form.system_proxy_enable,
|
||||
force_json: form.force_json,
|
||||
glossary_generate_enable: form.glossary_generate_enable,
|
||||
workflow_type: form.workflow_type,
|
||||
rpm: emptyToNull(form.rpm),
|
||||
tpm: emptyToNull(form.tpm)
|
||||
};
|
||||
|
||||
// Agent Config
|
||||
if (basePayload.glossary_generate_enable) {
|
||||
const isCustom = form.glossary_agent_config_choice === 'custom';
|
||||
basePayload.glossary_agent_config = {
|
||||
base_url: isCustom ? emptyToNull(form.glossary_agent_baseurl) : basePayload.base_url,
|
||||
api_key: isCustom ? (form.glossary_agent_key || "") : basePayload.api_key,
|
||||
model_id: isCustom ? emptyToNull(form.glossary_agent_model_id) : basePayload.model_id,
|
||||
provider: isCustom ? emptyToNull(form.glossary_agent_provider) : basePayload.provider, // Add provider
|
||||
to_lang: isCustom ? (form.glossary_agent_to_lang === 'custom' ? form.glossary_agent_custom_to_lang : form.glossary_agent_to_lang) : basePayload.to_lang,
|
||||
custom_prompt: emptyToNull(form.glossary_agent_custom_prompt),
|
||||
temperature: isCustom ? Number(form.glossary_agent_temperature) : basePayload.temperature,
|
||||
concurrent: isCustom ? Number(form.glossary_agent_concurrent) : basePayload.concurrent,
|
||||
retry: isCustom ? Number(form.glossary_agent_retry) : basePayload.retry,
|
||||
thinking: isCustom ? form.glossary_agent_thinking : basePayload.thinking,
|
||||
system_proxy_enable: isCustom ? form.glossary_agent_system_proxy_enable : basePayload.system_proxy_enable,
|
||||
chunk_size: isCustom ? Number(form.glossary_agent_chunk_size) : basePayload.chunk_size,
|
||||
force_json: isCustom ? form.glossary_agent_force_json : basePayload.force_json,
|
||||
rpm: isCustom ? emptyToNull(form.glossary_agent_rpm) : basePayload.rpm,
|
||||
tpm: isCustom ? emptyToNull(form.glossary_agent_tpm) : basePayload.tpm
|
||||
};
|
||||
}
|
||||
|
||||
// Specific Workflow Params
|
||||
if (form.workflow_type === 'markdown_based') {
|
||||
@@ -2377,13 +2136,31 @@
|
||||
|
||||
// Backend Metadata
|
||||
try {
|
||||
const [metaRes, enginRes, paramsRes] = await Promise.all([
|
||||
fetch("/service/meta"), fetch('/service/engin-list'), fetch("/service/default-params")
|
||||
const [metaRes, enginRes, paramsRes, configRes] = await Promise.all([
|
||||
fetch("/service/meta"), fetch('/service/engin-list'), fetch("/service/default-params"),
|
||||
fetch("/api/config")
|
||||
]);
|
||||
const meta = await metaRes.json();
|
||||
version.value = meta.version;
|
||||
enginList.value = await enginRes.json();
|
||||
Object.assign(defaultParams, await paramsRes.json());
|
||||
const envConfig = await configRes.json().catch(() => ({}));
|
||||
// 将服务端环境变量作为 localStorage 未设置时的回退默认值
|
||||
if (envConfig.base_url && !localStorage.getItem('translator_platform_custom_base_url')) {
|
||||
localStorage.setItem('translator_platform_custom_base_url', envConfig.base_url);
|
||||
}
|
||||
if (envConfig.api_key && !localStorage.getItem('translator_platform_custom_apikey')) {
|
||||
localStorage.setItem('translator_platform_custom_apikey', envConfig.api_key);
|
||||
}
|
||||
if (envConfig.model_id && !localStorage.getItem('translator_platform_custom_model_id')) {
|
||||
localStorage.setItem('translator_platform_custom_model_id', envConfig.model_id);
|
||||
}
|
||||
if (envConfig.rpm != null && !localStorage.getItem('rpm')) {
|
||||
localStorage.setItem('rpm', String(envConfig.rpm));
|
||||
}
|
||||
if (envConfig.tpm != null && !localStorage.getItem('tpm')) {
|
||||
localStorage.setItem('tpm', String(envConfig.tpm));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Backend init failed", e);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ class MDTranslator(AiTranslator):
|
||||
if not self.skip_translate:
|
||||
agent_config = MDTranslateAgentConfig(custom_prompt=config.custom_prompt,
|
||||
to_lang=config.to_lang,
|
||||
source_lang=config.source_lang,
|
||||
base_url=config.base_url,
|
||||
api_key=config.api_key,
|
||||
model_id=config.model_id,
|
||||
|
||||
@@ -22,6 +22,7 @@ dependencies = [
|
||||
"pypdf>=6.4.2",
|
||||
"regex>=2025.11.3",
|
||||
"charset-normalizer>=3.4.4",
|
||||
"python-dotenv>=1.0.0",
|
||||
]
|
||||
dynamic = ["version"]
|
||||
|
||||
|
||||
10
uv.lock
generated
10
uv.lock
generated
@@ -383,6 +383,7 @@ dependencies = [
|
||||
{ name = "pypdf" },
|
||||
{ name = "pysubs2" },
|
||||
{ name = "python-docx" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "python-pptx" },
|
||||
{ name = "regex" },
|
||||
{ name = "srt" },
|
||||
@@ -422,6 +423,7 @@ requires-dist = [
|
||||
{ name = "pypdf", specifier = ">=6.4.2" },
|
||||
{ name = "pysubs2", specifier = ">=1.8.0" },
|
||||
{ name = "python-docx", specifier = ">=1.2.0" },
|
||||
{ name = "python-dotenv", specifier = ">=1.0.0" },
|
||||
{ name = "python-pptx", specifier = ">=1.0.2" },
|
||||
{ name = "regex", specifier = ">=2025.11.3" },
|
||||
{ name = "srt", specifier = ">=3.5.3" },
|
||||
@@ -1738,14 +1740,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906 },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769 },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291 },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905 },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495 },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256 },
|
||||
|
||||
Reference in New Issue
Block a user