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=
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -14,3 +14,6 @@ docutranslate/output/
|
|||||||
.idea/
|
.idea/
|
||||||
#claude
|
#claude
|
||||||
.claude/
|
.claude/
|
||||||
|
/.omc/
|
||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ class AgentConfig:
|
|||||||
rpm: int | None = None # 每分钟请求数限制
|
rpm: int | None = None # 每分钟请求数限制
|
||||||
tpm: int | None = None # 每分钟Token数限制
|
tpm: int | None = None # 每分钟Token数限制
|
||||||
provider: ProviderType | None = None
|
provider: ProviderType | None = None
|
||||||
|
source_lang: str | None = None # qwen-mt: 源语言
|
||||||
|
|
||||||
|
|
||||||
class TotalErrorCounter:
|
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.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.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_target_lang = getattr(config, "to_lang", None)
|
||||||
self.mt_domains = getattr(config, "custom_prompt", 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:
|
def _estimate_tokens(self, text: str) -> int:
|
||||||
"""
|
"""
|
||||||
@@ -588,7 +590,7 @@ class Agent:
|
|||||||
return _MT_LANG_ALIASES[key]
|
return _MT_LANG_ALIASES[key]
|
||||||
return lang_text
|
return lang_text
|
||||||
|
|
||||||
def _build_mt_translation_options(self) -> dict:
|
def _build_mt_translation_options(self, prompt: str = "") -> dict:
|
||||||
translation_options = {}
|
translation_options = {}
|
||||||
|
|
||||||
source_lang = self._normalize_mt_lang(self.mt_source_lang)
|
source_lang = self._normalize_mt_lang(self.mt_source_lang)
|
||||||
@@ -603,6 +605,15 @@ class Agent:
|
|||||||
if domains:
|
if domains:
|
||||||
translation_options["domains"] = 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
|
return translation_options
|
||||||
|
|
||||||
def _build_mt_user_prompt(self, prompt: str, system_prompt: str) -> str:
|
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)},
|
{"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:
|
if translation_options:
|
||||||
data["translation_options"] = translation_options
|
data["translation_options"] = translation_options
|
||||||
return headers, data
|
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)
|
@app.get("/", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def main_page():
|
async def main_page():
|
||||||
index_path = Path(STATIC_DIR) / "index.html"
|
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
|
# 1. Markdown Based Workflow
|
||||||
if isinstance(payload, MarkdownWorkflowParams):
|
if isinstance(payload, MarkdownWorkflowParams):
|
||||||
translator_args = payload.model_dump(
|
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",
|
"temperature", "thinking", "chunk_size", "concurrent", "glossary_dict", "timeout",
|
||||||
"retry", "system_proxy_enable", "force_json", "rpm", "tpm", "provider"},
|
"retry", "system_proxy_enable", "force_json", "rpm", "tpm", "provider"},
|
||||||
exclude_none=True,
|
exclude_none=True,
|
||||||
|
|||||||
@@ -157,6 +157,9 @@ class BaseWorkflowParams(BaseModel):
|
|||||||
custom_prompt: Optional[str] = Field(
|
custom_prompt: Optional[str] = Field(
|
||||||
default="", description="用户自定义的翻译Prompt。", alias="custom_prompt"
|
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(
|
glossary_dict: Optional[Dict[str, str]] = Field(
|
||||||
None, description="术语表字典,key为原文,value为译文。", examples=[None]
|
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-FileCopyrightText: 2025 QinHan
|
||||||
# SPDX-License-Identifier: MPL-2.0
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
import os
|
from docutranslate.environment import DOCUTRANSLATE_PROXY_ENABLED
|
||||||
|
|
||||||
from .conditional_import import available_packages, conditional_import
|
from .conditional_import import available_packages, conditional_import
|
||||||
|
|
||||||
USE_PROXY = (
|
USE_PROXY = DOCUTRANSLATE_PROXY_ENABLED
|
||||||
True
|
|
||||||
if (
|
|
||||||
os.getenv("DOCUTRANSLATE_PROXY_ENABLED")
|
|
||||||
and os.getenv("DOCUTRANSLATE_PROXY_ENABLED").lower() == "true"
|
|
||||||
)
|
|
||||||
else False
|
|
||||||
)
|
|
||||||
if USE_PROXY:
|
if USE_PROXY:
|
||||||
print(f"USE_PROXY:{USE_PROXY}")
|
print(f"USE_PROXY:{USE_PROXY}")
|
||||||
|
|||||||
@@ -198,6 +198,7 @@ class Client:
|
|||||||
retry: Optional[int] = None,
|
retry: Optional[int] = None,
|
||||||
thinking: Optional[ThinkingMode] = None,
|
thinking: Optional[ThinkingMode] = None,
|
||||||
custom_prompt: Optional[str] = None,
|
custom_prompt: Optional[str] = None,
|
||||||
|
source_lang: Optional[str] = None,
|
||||||
system_proxy_enable: Optional[bool] = None,
|
system_proxy_enable: Optional[bool] = None,
|
||||||
force_json: Optional[bool] = None,
|
force_json: Optional[bool] = None,
|
||||||
rpm: Optional[int] = None,
|
rpm: Optional[int] = None,
|
||||||
@@ -264,6 +265,7 @@ class Client:
|
|||||||
retry: Optional[int] = None,
|
retry: Optional[int] = None,
|
||||||
thinking: Optional[ThinkingMode] = None,
|
thinking: Optional[ThinkingMode] = None,
|
||||||
custom_prompt: Optional[str] = None,
|
custom_prompt: Optional[str] = None,
|
||||||
|
source_lang: Optional[str] = None,
|
||||||
system_proxy_enable: Optional[bool] = None,
|
system_proxy_enable: Optional[bool] = None,
|
||||||
force_json: Optional[bool] = None,
|
force_json: Optional[bool] = None,
|
||||||
rpm: Optional[int] = None,
|
rpm: Optional[int] = None,
|
||||||
|
|||||||
@@ -596,6 +596,7 @@
|
|||||||
<option value="Portuguese">葡萄牙文(Português)</option>
|
<option value="Portuguese">葡萄牙文(Português)</option>
|
||||||
<option value="Arabic">阿拉伯文(العَرَبِيَّة)</option>
|
<option value="Arabic">阿拉伯文(العَرَبِيَّة)</option>
|
||||||
<option value="Vietnamese">越南文(tiếng Việt)</option>
|
<option value="Vietnamese">越南文(tiếng Việt)</option>
|
||||||
|
<option value="Indonesian">印尼文(Bahasa Indonesia)</option>
|
||||||
<option value="custom">{{ t('targetLanguageCustom') }}</option>
|
<option value="custom">{{ t('targetLanguageCustom') }}</option>
|
||||||
</select>
|
</select>
|
||||||
<div class="mt-2" v-if="form.to_lang === 'custom'">
|
<div class="mt-2" v-if="form.to_lang === 'custom'">
|
||||||
@@ -673,7 +674,7 @@
|
|||||||
<h2 class="accordion-header">
|
<h2 class="accordion-header">
|
||||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
||||||
data-bs-target="#collapseGlossary">
|
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')
|
class="bi bi-journal-bookmark me-2"></i><span>{{ t('glossaryGenTitle')
|
||||||
}}</span></strong>
|
}}</span></strong>
|
||||||
</button>
|
</button>
|
||||||
@@ -698,148 +699,6 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1447,25 +1306,6 @@
|
|||||||
retry: 3,
|
retry: 3,
|
||||||
rpm: null, // New RPM
|
rpm: null, // New RPM
|
||||||
tpm: null, // New TPM
|
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
|
// Nested Params for specific workflows
|
||||||
@@ -1497,7 +1337,7 @@
|
|||||||
return (v === null || v === '' || v === 'null') ? null : Number(v);
|
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.auto_workflow_enabled = getBool('translator_auto_workflow_enabled', true);
|
||||||
form.convert_engine = get('translator_convert_engin', 'mineru');
|
form.convert_engine = get('translator_convert_engin', 'mineru');
|
||||||
form.mineru_token = get('translator_mineru_token', '');
|
form.mineru_token = get('translator_mineru_token', '');
|
||||||
@@ -1516,12 +1356,12 @@
|
|||||||
form.formula_ocr = getBool('translator_formula_ocr', true);
|
form.formula_ocr = getBool('translator_formula_ocr', true);
|
||||||
form.code_ocr = getBool('translator_code_ocr', true);
|
form.code_ocr = getBool('translator_code_ocr', true);
|
||||||
form.skip_translate = getBool('translator_skip_translate', false);
|
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.system_proxy_enable = getBool('translator_system_proxy_enable', false);
|
||||||
form.force_json = getBool('translator_force_json', false);
|
form.force_json = getBool('translator_force_json', false);
|
||||||
form.to_lang = get('translator_to_lang', 'Simplified Chinese');
|
form.to_lang = get('translator_to_lang', 'Simplified Chinese');
|
||||||
form.custom_to_lang = get('translator_custom_to_lang', '');
|
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.custom_prompt = get('custom_prompt', '');
|
||||||
form.chunk_size = getNum('chunk_size', 1000);
|
form.chunk_size = getNum('chunk_size', 1000);
|
||||||
form.concurrent = getNum('concurrent', 5);
|
form.concurrent = getNum('concurrent', 5);
|
||||||
@@ -1539,30 +1379,6 @@
|
|||||||
form.provider = platObj ? platObj.provider : '';
|
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
|
// Restore workflow specific params
|
||||||
['txt', 'xlsx', 'docx', 'srt', 'epub', 'html', 'ass', 'pptx'].forEach(t => {
|
['txt', 'xlsx', 'docx', 'srt', 'epub', 'html', 'ass', 'pptx'].forEach(t => {
|
||||||
workflowParams[t].insert_mode = get(`translator_${t}_insert_mode`, 'replace');
|
workflowParams[t].insert_mode = get(`translator_${t}_insert_mode`, 'replace');
|
||||||
@@ -1574,7 +1390,6 @@
|
|||||||
|
|
||||||
// Trigger platform updates to load API keys/models
|
// Trigger platform updates to load API keys/models
|
||||||
updatePlatformParams(form.platform, 'translator_platform', form);
|
updatePlatformParams(form.platform, 'translator_platform', form);
|
||||||
updatePlatformParams(form.glossary_agent_platform, 'glossary_agent_platform', form, true);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- 新增:专门用于将当前 form 数据全部写入 localStorage 的函数 ---
|
// --- 新增:专门用于将当前 form 数据全部写入 localStorage 的函数 ---
|
||||||
@@ -1626,30 +1441,7 @@
|
|||||||
s('translator_provider', f.provider);
|
s('translator_provider', f.provider);
|
||||||
if (f.platform === 'custom') s('translator_platform_custom_base_url', f.base_url);
|
if (f.platform === 'custom') s('translator_platform_custom_base_url', f.base_url);
|
||||||
|
|
||||||
// 2. 术语表相关
|
// 2. 自动循环保存所有具体工作流参数 (txt, docx, xlsx...)
|
||||||
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...)
|
|
||||||
for (const [wfType, params] of Object.entries(workflowParams)) {
|
for (const [wfType, params] of Object.entries(workflowParams)) {
|
||||||
for (const [key, val] of Object.entries(params)) {
|
for (const [key, val] of Object.entries(params)) {
|
||||||
s(`translator_${wfType}_${key}`, val);
|
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) || '';
|
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.api_key = get(`${prefix}_${plat}_apikey`);
|
||||||
target.model_id = get(`${prefix}_${plat}_model_id`);
|
target.model_id = get(`${prefix}_${plat}_model_id`);
|
||||||
target.base_url = plat === 'custom' ? get(`${prefix}_custom_base_url`) : plat;
|
target.base_url = plat === 'custom' ? get(`${prefix}_custom_base_url`) : plat;
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(() => form.platform, (n) => {
|
watch(() => form.platform, (n) => {
|
||||||
updatePlatformParams(n, 'translator_platform', form);
|
updatePlatformParams(n, 'translator_platform', form);
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(() => form.glossary_agent_platform, (n) => {
|
|
||||||
updatePlatformParams(n, 'glossary_agent_platform', form, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
const t = (k) => {
|
const t = (k) => {
|
||||||
const dict = i18nData.value[currentLang.value] || i18nData.value['zh'] || {};
|
const dict = i18nData.value[currentLang.value] || i18nData.value['zh'] || {};
|
||||||
return dict[k] || k;
|
return dict[k] || k;
|
||||||
@@ -1751,12 +1533,11 @@
|
|||||||
// Dynamic Step Numbering
|
// Dynamic Step Numbering
|
||||||
const stepMap = computed(() => {
|
const stepMap = computed(() => {
|
||||||
let step = 2;
|
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 (currentWorkflowConfig.value) map.specific = step++;
|
||||||
if (form.workflow_type === 'markdown_based') map.parsing = step++;
|
if (form.workflow_type === 'markdown_based') map.parsing = step++;
|
||||||
map.ai = step++;
|
map.ai = step++;
|
||||||
if (!form.skip_translate) map.trans = step++;
|
if (!form.skip_translate) map.trans = step++;
|
||||||
map.glossary = step++;
|
|
||||||
return map;
|
return map;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1875,33 +1656,11 @@
|
|||||||
glossary_dict: Object.keys(glossaryData.value).length ? glossaryData.value : null,
|
glossary_dict: Object.keys(glossaryData.value).length ? glossaryData.value : null,
|
||||||
system_proxy_enable: form.system_proxy_enable,
|
system_proxy_enable: form.system_proxy_enable,
|
||||||
force_json: form.force_json,
|
force_json: form.force_json,
|
||||||
glossary_generate_enable: form.glossary_generate_enable,
|
|
||||||
workflow_type: form.workflow_type,
|
workflow_type: form.workflow_type,
|
||||||
rpm: emptyToNull(form.rpm),
|
rpm: emptyToNull(form.rpm),
|
||||||
tpm: emptyToNull(form.tpm)
|
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
|
// Specific Workflow Params
|
||||||
if (form.workflow_type === 'markdown_based') {
|
if (form.workflow_type === 'markdown_based') {
|
||||||
@@ -2377,13 +2136,31 @@
|
|||||||
|
|
||||||
// Backend Metadata
|
// Backend Metadata
|
||||||
try {
|
try {
|
||||||
const [metaRes, enginRes, paramsRes] = await Promise.all([
|
const [metaRes, enginRes, paramsRes, configRes] = await Promise.all([
|
||||||
fetch("/service/meta"), fetch('/service/engin-list'), fetch("/service/default-params")
|
fetch("/service/meta"), fetch('/service/engin-list'), fetch("/service/default-params"),
|
||||||
|
fetch("/api/config")
|
||||||
]);
|
]);
|
||||||
const meta = await metaRes.json();
|
const meta = await metaRes.json();
|
||||||
version.value = meta.version;
|
version.value = meta.version;
|
||||||
enginList.value = await enginRes.json();
|
enginList.value = await enginRes.json();
|
||||||
Object.assign(defaultParams, await paramsRes.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) {
|
} catch (e) {
|
||||||
console.error("Backend init failed", e);
|
console.error("Backend init failed", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class MDTranslator(AiTranslator):
|
|||||||
if not self.skip_translate:
|
if not self.skip_translate:
|
||||||
agent_config = MDTranslateAgentConfig(custom_prompt=config.custom_prompt,
|
agent_config = MDTranslateAgentConfig(custom_prompt=config.custom_prompt,
|
||||||
to_lang=config.to_lang,
|
to_lang=config.to_lang,
|
||||||
|
source_lang=config.source_lang,
|
||||||
base_url=config.base_url,
|
base_url=config.base_url,
|
||||||
api_key=config.api_key,
|
api_key=config.api_key,
|
||||||
model_id=config.model_id,
|
model_id=config.model_id,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ dependencies = [
|
|||||||
"pypdf>=6.4.2",
|
"pypdf>=6.4.2",
|
||||||
"regex>=2025.11.3",
|
"regex>=2025.11.3",
|
||||||
"charset-normalizer>=3.4.4",
|
"charset-normalizer>=3.4.4",
|
||||||
|
"python-dotenv>=1.0.0",
|
||||||
]
|
]
|
||||||
dynamic = ["version"]
|
dynamic = ["version"]
|
||||||
|
|
||||||
|
|||||||
10
uv.lock
generated
10
uv.lock
generated
@@ -383,6 +383,7 @@ dependencies = [
|
|||||||
{ name = "pypdf" },
|
{ name = "pypdf" },
|
||||||
{ name = "pysubs2" },
|
{ name = "pysubs2" },
|
||||||
{ name = "python-docx" },
|
{ name = "python-docx" },
|
||||||
|
{ name = "python-dotenv" },
|
||||||
{ name = "python-pptx" },
|
{ name = "python-pptx" },
|
||||||
{ name = "regex" },
|
{ name = "regex" },
|
||||||
{ name = "srt" },
|
{ name = "srt" },
|
||||||
@@ -422,6 +423,7 @@ requires-dist = [
|
|||||||
{ name = "pypdf", specifier = ">=6.4.2" },
|
{ name = "pypdf", specifier = ">=6.4.2" },
|
||||||
{ name = "pysubs2", specifier = ">=1.8.0" },
|
{ name = "pysubs2", specifier = ">=1.8.0" },
|
||||||
{ name = "python-docx", specifier = ">=1.2.0" },
|
{ name = "python-docx", specifier = ">=1.2.0" },
|
||||||
|
{ name = "python-dotenv", specifier = ">=1.0.0" },
|
||||||
{ name = "python-pptx", specifier = ">=1.0.2" },
|
{ name = "python-pptx", specifier = ">=1.0.2" },
|
||||||
{ name = "regex", specifier = ">=2025.11.3" },
|
{ name = "regex", specifier = ">=2025.11.3" },
|
||||||
{ name = "srt", specifier = ">=3.5.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/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/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/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/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/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 },
|
{ 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