支持mineru 2.7.1

This commit is contained in:
xunbu committed 2026-01-07 19:56:27 +08:00
1 parent 50255069b7
commit 3b13157670
5 files changed
+87 -49

No files matched your search

+9 -3
View File
@@ -337,7 +337,7 @@ class TranslateServiceRequest(BaseModel):
],
)
file_content: str = Field(
..., description="Base64编码的文件内容。", examples=["JVBERi0xLjQK..."]
..., description="Base64编码的文件内容。", examples=["JVBERi0xLjcKJeLjz9MKMSAwIG9iago8PC9..."]
)
payload: TranslatePayload = Field(
..., description="包含工作流类型和相应参数的载荷。"
@@ -666,7 +666,7 @@ async def _perform_translation(
"force_json",
"rpm",
"tpm",
"provider", # Added provider
"provider",
},
exclude_none=True,
)
@@ -688,7 +688,9 @@ async def _perform_translation(
converter_config = ConverterMineruDeployConfig(
base_url=payload.mineru_deploy_base_url,
backend=payload.mineru_deploy_backend,
parse_method=payload.mineru_deploy_parse_method,
formula_enable=payload.mineru_deploy_formula_enable,
table_enable=payload.mineru_deploy_table_enable,
start_page_id=payload.mineru_deploy_start_page_id,
end_page_id=payload.mineru_deploy_end_page_id,
lang_list=payload.mineru_deploy_lang_list,
@@ -2214,8 +2216,10 @@ async def service_flat_translate(
formula_ocr: bool = Form(True, description="[PDF] 是否启用公式识别"),
code_ocr: bool = Form(True, description="[Docling] 是否启用代码块识别"),
mineru_deploy_base_url: str = Form("http://127.0.0.1:8000", description="[MinerU Local] 服务地址"),
mineru_deploy_backend: str = Form("VLM", description="[MinerU Local] 后端类型"),
mineru_deploy_backend: str = Form("hybrid-auto-engine", description="[MinerU Local] 后端类型: hybrid-auto-engine, pipeline 等"),
mineru_deploy_parse_method: str = Form("auto", description="[MinerU Local] 解析方法: auto, txt, ocr"),
mineru_deploy_formula_enable: bool = Form(True, description="[MinerU Local] 是否启用公式"),
mineru_deploy_table_enable: bool = Form(True, description="[MinerU Local] 是否启用表格"),
mineru_deploy_start_page_id: int = Form(0, description="[MinerU Local] 起始页码"),
mineru_deploy_end_page_id: int = Form(99999, description="[MinerU Local] 结束页码"),
mineru_deploy_lang_list: Optional[List[str]] = Form(None, description="[MinerU Local] 语言列表"),
@@ -2317,7 +2321,9 @@ async def service_flat_translate(
# --- MinerU 本地部署参数 ---
"mineru_deploy_base_url": mineru_deploy_base_url,
"mineru_deploy_backend": mineru_deploy_backend,
"mineru_deploy_parse_method": mineru_deploy_parse_method,
"mineru_deploy_formula_enable": mineru_deploy_formula_enable,
"mineru_deploy_table_enable": mineru_deploy_table_enable,
"mineru_deploy_start_page_id": mineru_deploy_start_page_id,
"mineru_deploy_end_page_id": mineru_deploy_end_page_id,
"mineru_deploy_lang_list": mineru_deploy_lang_list,
@@ -2,7 +2,7 @@
# SPDX-License-Identifier: MPL-2.0
import asyncio
from dataclasses import dataclass
from typing import Literal, Hashable
from typing import Literal, Hashable, List
import httpx
@@ -16,24 +16,42 @@ from docutranslate.utils.markdown_utils import embed_inline_image_from_zip
@dataclass(kw_only=True)
class ConverterMineruDeployConfig(X2MarkdownConverterConfig):
base_url: str = "http://127.0.0.1:8000"
output_dir: str = "./output" # 覆盖默认值 ./output
lang_list: list[Literal["ch", "ch_server", "ch_lite", "en", "korean", "japan", "chinese_cht", "ta", "te", "ka", "th", "el", "latin", "arabic", "east_slavic", "cyrillic", "devanagari"]] | None = None
backend: Literal["pipeline", "vlm-transformers", "vlm-mlx-engine", "vlm-vllm-async-engine", "vlm-lmdeploy-engine","vlm-http-client"] = "pipeline"
# parse_method: str = "auto"
output_dir: str = "./output"
# 支持的语言列表 (来自 MinerU API)
lang_list: List[str] | None = None # 默认值在 API 侧处理,这里 None 即可
# 后端引擎选项 (更新适配最新的 MinerU API)
backend: Literal[
"pipeline",
"vlm-auto-engine",
"vlm-http-client",
"hybrid-auto-engine",
"hybrid-http-client"
] = "hybrid-auto-engine"
parse_method: Literal["auto", "txt", "ocr"] = "auto"
formula_enable: bool = True
# table_enable: bool = True
server_url: str | None = None #(Adapted only for vlm-http-client backend)openai compatible server url, e.g., http://127.0.0.1:30000
# return_md: bool = True
# return_middle_json: bool = True
# return_model_output: bool = False
# return_content_list: bool = False
# return_images: bool = True
# response_format_zip: bool = True
table_enable: bool = True
# 用于 vlm-http-client 或 hybrid-http-client 后端
server_url: str | None = None
# 返回选项
return_md: bool = True
return_middle_json: bool = False
return_model_output: bool = False
return_content_list: bool = False
return_images: bool = True
response_format_zip: bool = True
# 页面范围
start_page_id: int = 0
end_page_id: int = 99999
def gethash(self) -> Hashable:
return (self.backend,self.formula_enable,self.start_page_id,self.end_page_id)
return (self.backend, self.formula_enable, self.table_enable,
self.parse_method, self.start_page_id, self.end_page_id)
# 配置HTTP客户端
timeout = httpx.Timeout(
@@ -58,24 +76,34 @@ class ConverterMineruDeploy(X2MarkdownConverter):
self._api_url = f"{self.base_url}/file_parse"
def _build_form_data(self) -> dict:
# httpx 在处理 data 参数时,如果值为 list,会自动展开为多个同名 key (例如 lang_list=ch&lang_list=en)
# 这符合 FastAPI/Starlette 对 List 字段的解析要求
data = {
"output_dir": self.config.output_dir,
"backend": self.config.backend,
"parse_method": "auto",
"formula_enable": self.config.formula_enable,
"table_enable": True,
"server_url": None,
"return_md": True,
"return_middle_json": True,
"return_model_output": False,
"return_content_list": False,
"return_images": True,
"response_format_zip": True,
"parse_method": self.config.parse_method,
# bool 类型在 multipart/form-data 中通常需要转为字符串 'true'/'false',但 httpx 会处理 python bool
"formula_enable": str(self.config.formula_enable).lower(),
"table_enable": str(self.config.table_enable).lower(),
"return_md": str(self.config.return_md).lower(),
"return_middle_json": str(self.config.return_middle_json).lower(),
"return_model_output": str(self.config.return_model_output).lower(),
"return_content_list": str(self.config.return_content_list).lower(),
"return_images": str(self.config.return_images).lower(),
"response_format_zip": str(self.config.response_format_zip).lower(),
"start_page_id": self.config.start_page_id,
"end_page_id": self.config.end_page_id
}
return data
if self.config.lang_list:
data["lang_list"] = self.config.lang_list
else:
data["lang_list"] = ["ch"] # 默认值
if self.config.server_url:
data["server_url"] = self.config.server_url
return data
def convert(self, d: Document) -> MarkdownDocument:
self.logger.info("开始解析文件")
@@ -88,11 +116,11 @@ class ConverterMineruDeploy(X2MarkdownConverter):
)
response.raise_for_status() # 检查是否有错误
# Mineru API 返回 zip 时包含图片和 md
md = embed_inline_image_from_zip(response.content, None)
self.logger.info("已转化为markdown")
return MarkdownDocument.from_bytes(md.encode(), suffix=".md", stem=d.stem)
async def convert_async(self, d: Document) -> MarkdownDocument:
self.logger.info("开始解析文件")
files = [("files", (d.name, d.content, "application/octet-stream"))]
@@ -110,9 +138,3 @@ class ConverterMineruDeploy(X2MarkdownConverter):
def support_format(self) -> list[str]:
return [".pdf", ".doc", ".docx", ".ppt", ".pptx", ".png", ".jpg", ".jpeg"]
if __name__ == '__main__':
d = Document.from_path(r"C:\Users\jxgm\Desktop\testfiles\table.pdf")
config=ConverterMineruDeployConfig()
converter = ConverterMineruDeploy(config=config)
converter.convert(d)
+18 -12
View File
@@ -252,17 +252,27 @@ class MarkdownWorkflowParams(BaseWorkflowParams):
"http://127.0.0.1:8000",
description="[仅当 convert_engine='mineru_deploy'] 本地部署的 MinerU 服务地址。",
)
# --- UPDATED BACKEND LIST ---
mineru_deploy_backend: Literal[
"pipeline",
"vlm-transformers",
"vlm-mlx-engine",
"vlm-vllm-async-engine",
"vlm-lmdeploy-engine",
"vlm-auto-engine",
"vlm-http-client",
"hybrid-auto-engine",
"hybrid-http-client"
] = Field(
"pipeline",
"hybrid-auto-engine",
description="[仅当 convert_engine='mineru_deploy'] 本地部署的 MinerU 服务使用的后端。",
)
# --- NEW PARAMETERS START ---
mineru_deploy_parse_method: Literal["auto", "txt", "ocr"] = Field(
"auto",
description="[仅当 convert_engine='mineru_deploy'] 解析方法: auto, txt, ocr"
)
mineru_deploy_table_enable: bool = Field(
True,
description="[仅当 convert_engine='mineru_deploy'] 本地部署的服务是否启用表格解析。",
)
# --- NEW PARAMETERS END ---
mineru_deploy_formula_enable: bool = Field(
True,
description="[仅当 convert_engine='mineru_deploy'] 本地部署的服务是否启用公式解析。",
@@ -275,13 +285,13 @@ class MarkdownWorkflowParams(BaseWorkflowParams):
)
mineru_deploy_lang_list: Optional[List[str]] = Field(
None,
description="[仅当 convert_engine='mineru_deploy' 且 backend='pipeline'] 语言列表。",
examples=[None],
description="[仅当 convert_engine='mineru_deploy'] 语言列表, 默认 ['ch']。",
examples=[["ch", "en"]],
)
# 修改: 默认值改为 ""
mineru_deploy_server_url: Optional[str] = Field(
default="",
description="[仅当 convert_engine='mineru_deploy' 且 backend='vlm-http-client'] Server URL.",
description="[仅当 convert_engine='mineru_deploy' 且 backend为http-client相关时] Server URL.",
)
@model_validator(mode="after")
@@ -312,10 +322,6 @@ class TextWorkflowParams(BaseWorkflowParams):
"\n",
description="当 insert_mode 为 'append' 或 'prepend' 时,用于分隔原文和译文的分隔符。",
)
segment_mode: Literal["line", "paragraph", "none"] = Field(
"line",
description="分段模式。'line':按行分段(每行独立翻译),'paragraph':按段落分段(连续非空行合并为段落),'none':不分段(全文视为一个段落)。",
)
class JsonWorkflowParams(BaseWorkflowParams):
+5 -1
View File
@@ -65,6 +65,8 @@
"mineruDeployLangListLabel": "语言列表 (Pipeline模式)",
"mineruDeployServerUrlLabel": "Server URL",
"mineruDeployServerUrlPlaceholder": "例如: http://127.0.0.1:30000",
"mineruDeployParseMethodLabel": "解析方法 (Parse Method)",
"mineruDeployTableEnableLabel": "表格识别 (Table Recognition)",
"mineruDeployStartPageLabel": "起始页码",
"mineruDeployEndPageLabel": "结束页码",
"mineruDeployFormulaEnableLabel": "启用公式解析",
@@ -137,7 +139,7 @@
"previewTranslated": "译文",
"closeBtn": "关闭",
"tutorialModalTitle": "使用教程",
"tutorialModalBody": "<p><i class=\"bi bi-camera-video me-2\"></i>视频教程可以在B站搜索 <a href=\"https://search.bilibili.com/all?keyword=docutranslate\" target=\"_blank\">docutranslate</a> 获取。</p><p>欢迎使用 DocuTranslate!请按照以下步骤完成文档翻译:</p><ol><li><strong><i class=\"bi bi-diagram-3 me-2\"></i>第一步:选择工作流</strong><p class=\"mt-2\">在左侧配置面板的顶部,首先选择最适合您文件类型的处理流程。</p><div class=\"alert alert-info mt-2\" role=\"alert\"><i class=\"bi bi-lightbulb-fill me-2\"></i>提示: 默认已开启“自动选择工作流”。您只需上传文件,系统会自动为您匹配合适的工作流,简化操作。</div><ul><li><b>转Markdown再翻译</b>: 适用于翻译PDF、markdown、图片等文件。这是最通用和强大的模式。</li><li><b>纯文本翻译</b>: 用于翻译 <code>.txt</code> 纯文本文件。</li><li><b>EPUB翻译</b>: 用于翻译 <code>.epub</code> 电子书文件。</li><li><b>DOCX翻译</b>: 用于翻译 <code>.docx</code> Word文档。</li><li><b>XLSX翻译</b>: 用于翻译 <code>.xlsx</code> 或 <code>.csv</code> 电子表格文件。</li><li><b>PPTX翻译</b>: 用于翻译 <code>.pptx</code> 幻灯片文件。</li><li><b>SRT字幕翻译</b>: 用于翻译 <code>.srt</code> 字幕文件。</li><li><b>ASS字幕翻译</b>: 用于翻译 <code>.ass</code> 特效字幕文件。</li><li><b>JSON翻译</b>: 用于翻译 <code>.json</code> 文件中的特定字段。</li><li><b>HTML翻译</b>: 用于翻译 <code>.html</code> 网页文件。</li></ul></li><li><strong><i class=\"bi bi-gear-fill me-2\"></i>第二步:配置参数</strong><p class=\"mt-2\">选择工作流后,下方会显示相关的配置选项。请依次完成设置(所有配置都会自动保存在您的浏览器中):</p><p class=\"mb-2\"><strong>A. 工作流特定选项</strong> (根据您第一步的选择出现):</p><ul class=\"ms-4\"><li><u>如果选择“转Markdown再翻译”</u>,请配置 <strong>解析配置</strong>:<ul><li><strong>解析引擎</strong>: 选择一个引擎将您的文件(如PDF)转换为适合翻译的Markdown格式。如果您的文件已经是Markdown格式,则无需选择。</li><li><strong>Mineru Token</strong>: 如果您选择 <code>minerU</code> 引擎,需要在此处填入您的Token。</li></ul></li><li><u>如果选择“纯文本/DOCX/XLSX/PPTX/SRT/ASS/EPUB/HTML”</u>,请配置其 <strong>翻译选项</strong>:<ul><li><strong>插入模式</strong>: 定义翻译结果如何放入文档。您可以选择直接“替换”原文,或是在原文之后“附加”,或是在原文之前“前置”。</li><li><strong>分隔符</strong>: 当选择“附加”或“前置”模式时,此项用于在原文和译文之间插入分隔符(例如ASS格式常用 <code>\\N</code>,EPUB格式常用 <code>&lt;br /&gt;</code> 作为换行分隔符)。</li></ul></li><li><u>如果选择“JSON翻译”</u>,请配置 <strong>JSON路径</strong>:<ul><li><strong>需要翻译的JSON路径</strong>: 每行输入一个 <a href=\"https://goessner.net/articles/JsonPath/\" target=\"_blank\">JSONPath</a> 表达式,将翻译所有与路径匹配的对象中的字符串。例如:<code>$.*</code>(翻译全部字符串),<code>$..description</code>(翻译所有键为<code>description</code>的值)。</li></ul></li></ul><p class=\"mb-2 mt-3\"><strong>B. 通用选项</strong> (适用于所有工作流):</p><ul class=\"ms-4\"><li><strong>翻译模型</strong>:<ul><li><strong>选择平台/API 地址/API Key/模型ID</strong>: 配置您希望使用的AI翻译服务。模型能力指令遵循越强,<strong>出错</strong>、<strong>漏翻</strong>的概率越低。</li><li><strong>跳过翻译</strong>: 勾选此项后,将只执行文档解析和格式转换,不调用AI进行翻译。</li></ul></li><li><strong>翻译配置</strong>:<ul><li><strong>目标语言</strong>: 指定翻译的目标语言。</li><li><strong>自定义Prompt</strong>: 可选,添加额外指令,如“人名保持原文不翻译”。</li><li><strong>思考模式</strong>: 针对部分支持混合推理的模型进行设置,建议选择“禁用(推荐)”。</li><li><strong>分块大小/并发数等</strong>: 高级参数,用于调整性能和API请求行为,通常保持默认即可。</li></ul></li><li><strong>术语表</strong>:<ul><li><strong>上传术语表 (可选)</strong>: 上传CSV文件(需包含'src'和'dst'列)来保证特定术语翻译的统一性和准确性。</li><li><strong>自动生成术语表</strong>: 启用后,程序会先从原文中提取术语并生成一个术语表,然后再进行翻译。</li></ul></li></ul></li><li><strong><i class=\"bi bi-file-earmark-arrow-up-fill me-2\"></i>第三步:上传文件</strong><p class=\"mt-2\">在右侧的任务列表中,点击或拖拽您的文档到文件上传区域。</p></li><li><strong><i class=\"bi bi-pLine truncated
"tutorialModalBody": "<p><i class=\"bi bi-camera-video me-2\"></i>视频教程可以在B站搜索 <a href=\"https://search.bilibili.com/all?keyword=docutranslate\" target=\"_blank\">docutranslate</a> 获取。</p><p>欢迎使用 DocuTranslate!请按照以下步骤完成文档翻译:</p><ol><li><strong><i class=\"bi bi-diagram-3 me-2\"></i>第一步:选择工作流</strong><p class=\"mt-2\">在左侧配置面板的顶部,首先选择最适合您文件类型的处理流程。</p><div class=\"alert alert-info mt-2\" role=\"alert\"><i class=\"bi bi-lightbulb-fill me-2\"></i>提示: 默认已开启“自动选择工作流”。您只需上传文件,系统会自动为您匹配合适的工作流,简化操作。</div><ul><li><b>转Markdown再翻译</b>: 适用于翻译PDF、markdown、图片等文件。这是最通用和强大的模式。</li><li><b>纯文本翻译</b>: 用于翻译 <code>.txt</code> 纯文本文件。</li><li><b>EPUB翻译</b>: 用于翻译 <code>.epub</code> 电子书文件。</li><li><b>DOCX翻译</b>: 用于翻译 <code>.docx</code> Word文档。</li><li><b>XLSX翻译</b>: 用于翻译 <code>.xlsx</code> 或 <code>.csv</code> 电子表格文件。</li><li><b>PPTX翻译</b>: 用于翻译 <code>.pptx</code> 幻灯片文件。</li><li><b>SRT字幕翻译</b>: 用于翻译 <code>.srt</code> 字幕文件。</li><li><b>ASS字幕翻译</b>: 用于翻译 <code>.ass</code> 特效字幕文件。</li><li><b>JSON翻译</b>: 用于翻译 <code>.json</code> 文件中的特定字段。</li><li><b>HTML翻译</b>: 用于翻译 <code>.html</code> 网页文件。</li></ul></li><li><strong><i class=\"bi bi-gear-fill me-2\"></i>第二步:配置参数</strong><p class=\"mt-2\">选择工作流后,下方会显示相关的配置选项。请依次完成设置(所有配置都会自动保存在您的浏览器中):</p><p class=\"mb-2\"><strong>A. 工作流特定选项</strong> (根据您第一步的选择出现):</p><ul class=\"ms-4\"><li><u>如果选择“转Markdown再翻译”</u>,请配置 <strong>解析配置</strong>:<ul><li><strong>解析引擎</strong>: 选择一个引擎将您的文件(如PDF)转换为适合翻译的Markdown格式。如果您的文件已经是Markdown格式,则无需选择。</li><li><strong>Mineru Token</strong>: 如果您选择 <code>minerU</code> 引擎,需要在此处填入您的Token。</li></ul></li><li><u>如果选择“纯文本/DOCX/XLSX/PPTX/SRT/ASS/EPUB/HTML”</u>,请配置其 <strong>翻译选项</strong>:<ul><li><strong>插入模式</strong>: 定义翻译结果如何放入文档。您可以选择直接“替换”原文,或是在原文之后“附加”,或是在原文之前“前置”。</li><li><strong>分隔符</strong>: 当选择“附加”或“前置”模式时,此项用于在原文和译文之间插入分隔符(例如ASS格式常用 <code>\\N</code>,EPUB格式常用 <code>&lt;br /&gt;</code> 作为换行分隔符)。</li></ul></li><li><u>如果选择“JSON翻译”</u>,请配置 <strong>JSON路径</strong>:<ul><li><strong>需要翻译的JSON路径</strong>: 每行输入一个 <a href=\"https://goessner.net/articles/JsonPath/\" target=\"_blank\">JSONPath</a> 表达式,将翻译所有与路径匹配的对象中的字符串。例如:<code>$.*</code>(翻译全部字符串),<code>$..description</code>(翻译所有键为<code>description</code>的值)。</li></ul></li></ul><p class=\"mb-2 mt-3\"><strong>B. 通用选项</strong> (适用于所有工作流):</p><ul class=\"ms-4\"><li><strong>翻译模型</strong>:<ul><li><strong>选择平台/API 地址/API Key/Model ID</strong>: 配置您希望使用的AI翻译服务。模型能力指令遵循越强,<strong>出错</strong>、<strong>漏翻</strong>的概率越低。</li><li><strong>跳过翻译</strong>: 勾选此项后,将只执行文档解析和格式转换,不调用AI进行翻译。</li></ul></li><li><strong>翻译配置</strong>:<ul><li><strong>目标语言</strong>: 指定翻译的目标语言。</li><li><strong>自定义Prompt</strong>: 可选,添加额外指令,如“人名保持原文不翻译”。</li><li><strong>思考模式</strong>: 针对部分支持混合推理的模型进行设置,建议选择“禁用(推荐)”。</li><li><strong>分块大小/并发数等</strong>: 高级参数,用于调整性能和API请求行为,通常保持默认即可。</li></ul></li><li><strong>术语表</strong>:<ul><li><strong>上传术语表 (可选)</strong>: 上传CSV文件(需包含'src'和'dst'列)来保证特定术语翻译的统一性和准确性。</li><li><strong>自动生成术语表</strong>: 启用后,程序会先从原文中提取术语并生成一个术语表,然后再进行翻译。</li></ul></li></ul></li><li><strong><i class=\"bi bi-file-earmark-arrow-up-fill me-2\"></i>第三步:上传文件</strong><p class=\"mt-2\">在右侧的任务列表中,点击或拖拽您的文档到文件上传区域。</p></li><li><strong><i class=\"bi bi-pLine truncated
"tutorialUnderstandBtn": "我明白了",
"contributorsModalTitle": "感谢贡献",
"contributorsPara1": "DocuTranslate是一个开源项目!大家的需求与使用是项目进步的动力。",
@@ -236,6 +238,8 @@
"mineruDeployLangListLabel": "Language List (Pipeline Mode)",
"mineruDeployServerUrlLabel": "Server URL",
"mineruDeployServerUrlPlaceholder": "e.g., http://127.0.0.1:30000",
"mineruDeployParseMethodLabel": "Parse Method",
"mineruDeployTableEnableLabel": "Table Recognition",
"mineruDeployStartPageLabel": "Start Page",
"mineruDeployEndPageLabel": "End Page",
"mineruDeployFormulaEnableLabel": "Enable Formula Parsing",
+1 -1
View File
@@ -1 +1 @@
<!DOCTYPE html> <html lang="zh-CN" data-bs-theme="auto"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>DocuTranslate - 交互式文档翻译</title> <link rel="icon" href="/static/favicon.ico" type="image/x-icon"> <!-- Bootstrap CSS --> <link href="/static/bootstrap.css" rel="stylesheet" crossorigin="anonymous"> <!-- Bootstrap Icons --> <link rel="stylesheet" href="/static/bootstrap-icons.css"> <style> /* 复用原版 CSS */ body { background-color: var(--bs-body-bg); } button:focus { outline: none !important; box-shadow: none !important; } .main-container { display: flex; flex-direction: column; height: 100vh; padding-top: 1rem; padding-bottom: 1rem; } .settings-panel { height: calc(100vh - 2rem); overflow-y: auto; padding-right: 15px; } .task-area { height: calc(100vh - 2rem); overflow-y: auto; } .task-card { transition: all 0.3s ease-in-out; } .log-area { height: 150px; background-color: var(--bs-tertiary-bg); border: 1px solid var(--bs-border-color); border-radius: .375rem; font-family: monospace; font-size: 0.8rem; padding: 10px; overflow-y: auto; white-space: pre-wrap; word-break: break-all; } .log-area-wrapper { position: relative; } .copy-log-btn { position: absolute; bottom: 5px; right: 5px; z-index: 10; opacity: 0.5; transition: all 0.2s ease-in-out; padding: 0.1rem 0.4rem; line-height: 1; } .copy-log-btn:hover { opacity: 1; } .file-drop-area { border: 2px dashed var(--bs-secondary-bg); border-radius: .375rem; padding: 2rem; text-align: center; cursor: pointer; transition: all 0.2s ease-in-out; background-color: var(--bs-body-bg); } .file-drop-area.drag-over { border-color: var(--bs-primary); background-color: var(--bs-secondary-bg); } .file-drop-area.file-selected { border-style: solid; border-color: var(--bs-success); background-color: var(--bs-success-bg-subtle); } .file-drop-area.input-error { border-color: var(--bs-danger); } #previewOffcanvas { --bs-offcanvas-width: 95vw; max-width: 1600px; } .preview-split-container { display: flex; flex-direction: row; height: 90%; } .preview-pane-wrapper { display: flex; flex-direction: column; overflow: hidden; width: 100%; height: 100%; } .preview-pane-wrapper .preview-pane { flex-grow: 1; border: 1px solid var(--bs-border-color); border-radius: .375rem; overflow: auto; position: relative; } .gutter { background-color: var(--bs-tertiary-bg); background-repeat: no-repeat; background-position: 50%; } .gutter.gutter-horizontal { cursor: col-resize; border-left: 1px solid var(--bs-border-color); border-right: 1px solid var(--bs-border-color); } .gutter.gutter-vertical { cursor: row-resize; border-top: 1px solid var(--bs-border-color); border-bottom: 1px solid var(--bs-border-color); } .preview-pane iframe { width: 100%; height: 100%; border: none; } .preview-pane pre { margin: 0; padding: 1rem; background-color: var(--bs-body-bg); white-space: pre; } .bottom-left-controls { position: fixed; bottom: 1rem; left: 1rem; z-index: 1050; display: flex; gap: 0.5rem; } .step-number { margin-right: 0.25rem; } #translatedPreviewContainer .preview-pane { overflow: hidden; } #translatedPreviewContainer .preview-pane iframe { display: block; width: 100%; height: 100%; } .lang-list-container { max-height: 200px; overflow-y: auto; border: 1px solid var(--bs-border-color); border-radius: 0.375rem; paddinLine truncated
<!DOCTYPE html> <html lang="zh-CN" data-bs-theme="auto"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>DocuTranslate - 交互式文档翻译</title> <link rel="icon" href="/static/favicon.ico" type="image/x-icon"> <!-- Bootstrap CSS --> <link href="/static/bootstrap.css" rel="stylesheet" crossorigin="anonymous"> <!-- Bootstrap Icons --> <link rel="stylesheet" href="/static/bootstrap-icons.css"> <style> /* 复用原版 CSS */ body { background-color: var(--bs-body-bg); } button:focus { outline: none !important; box-shadow: none !important; } .main-container { display: flex; flex-direction: column; height: 100vh; padding-top: 1rem; padding-bottom: 1rem; } .settings-panel { height: calc(100vh - 2rem); overflow-y: auto; padding-right: 15px; } .task-area { height: calc(100vh - 2rem); overflow-y: auto; } .task-card { transition: all 0.3s ease-in-out; } .log-area { height: 150px; background-color: var(--bs-tertiary-bg); border: 1px solid var(--bs-border-color); border-radius: .375rem; font-family: monospace; font-size: 0.8rem; padding: 10px; overflow-y: auto; white-space: pre-wrap; word-break: break-all; } .log-area-wrapper { position: relative; } .copy-log-btn { position: absolute; bottom: 5px; right: 5px; z-index: 10; opacity: 0.5; transition: all 0.2s ease-in-out; padding: 0.1rem 0.4rem; line-height: 1; } .copy-log-btn:hover { opacity: 1; } .file-drop-area { border: 2px dashed var(--bs-secondary-bg); border-radius: .375rem; padding: 2rem; text-align: center; cursor: pointer; transition: all 0.2s ease-in-out; background-color: var(--bs-body-bg); } .file-drop-area.drag-over { border-color: var(--bs-primary); background-color: var(--bs-secondary-bg); } .file-drop-area.file-selected { border-style: solid; border-color: var(--bs-success); background-color: var(--bs-success-bg-subtle); } .file-drop-area.input-error { border-color: var(--bs-danger); } #previewOffcanvas { --bs-offcanvas-width: 95vw; max-width: 1600px; } .preview-split-container { display: flex; flex-direction: row; height: 90%; } .preview-pane-wrapper { display: flex; flex-direction: column; overflow: hidden; width: 100%; height: 100%; } .preview-pane-wrapper .preview-pane { flex-grow: 1; border: 1px solid var(--bs-border-color); border-radius: .375rem; overflow: auto; position: relative; } .gutter { background-color: var(--bs-tertiary-bg); background-repeat: no-repeat; background-position: 50%; } .gutter.gutter-horizontal { cursor: col-resize; border-left: 1px solid var(--bs-border-color); border-right: 1px solid var(--bs-border-color); } .gutter.gutter-vertical { cursor: row-resize; border-top: 1px solid var(--bs-border-color); border-bottom: 1px solid var(--bs-border-color); } .preview-pane iframe { width: 100%; height: 100%; border: none; } .preview-pane pre { margin: 0; padding: 1rem; background-color: var(--bs-body-bg); white-space: pre; } .bottom-left-controls { position: fixed; bottom: 1rem; left: 1rem; z-index: 1050; display: flex; gap: 0.5rem; } .step-number { margin-right: 0.25rem; } #translatedPreviewContainer .preview-pane { overflow: hidden; } #translatedPreviewContainer .preview-pane iframe { display: block; width: 100%; height: 100%; } .lang-list-container { max-height: 200px; overflow-y: auto; border: 1px solid var(--bs-border-color); border-radius: 0.375rem; paddinLine truncated