解决了打包问题

This commit is contained in:
xunbu
2025-05-21 20:49:00 +08:00
parent cbb5345446
commit 380b4d7dc9
11 changed files with 2626 additions and 159 deletions

125
.idea/workspace.xml generated
View File

@@ -5,11 +5,15 @@
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="6b18b44a-df57-4212-a857-9e291ebe5dd2" name="更改" comment=""> <list default="true" id="6b18b44a-df57-4212-a857-9e291ebe5dd2" name="更改" comment="">
<change afterPath="$PROJECT_DIR$/lite_onefile.spec" afterDir="false" /> <change afterPath="$PROJECT_DIR$/docutranslate/static/DocuTranslate.ico" afterDir="false" />
<change afterPath="$PROJECT_DIR$/full_onefile.spec" afterDir="false" />
<change afterPath="$PROJECT_DIR$/requirements.txt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
<change beforePath="$PROJECT_DIR$/docutranslate/app.py" beforeDir="false" afterPath="$PROJECT_DIR$/docutranslate/app.py" afterDir="false" /> <change beforePath="$PROJECT_DIR$/docutranslate/app.py" beforeDir="false" afterPath="$PROJECT_DIR$/docutranslate/app.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/docutranslate/global_values/conditional_import.py" beforeDir="false" afterPath="$PROJECT_DIR$/docutranslate/global_values/conditional_import.py" afterDir="false" /> <change beforePath="$PROJECT_DIR$/docutranslate/converter/__init__.py" beforeDir="false" afterPath="$PROJECT_DIR$/docutranslate/converter/__init__.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/docutranslate/static/index.html" beforeDir="false" afterPath="$PROJECT_DIR$/docutranslate/static/index.html" afterDir="false" /> <change beforePath="$PROJECT_DIR$/docutranslate/static/index.html" beforeDir="false" afterPath="$PROJECT_DIR$/docutranslate/static/index.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/full.spec" beforeDir="false" afterPath="$PROJECT_DIR$/full.spec" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pyproject.toml" beforeDir="false" afterPath="$PROJECT_DIR$/pyproject.toml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/pyproject.toml" beforeDir="false" afterPath="$PROJECT_DIR$/pyproject.toml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/uv.lock" beforeDir="false" afterPath="$PROJECT_DIR$/uv.lock" afterDir="false" /> <change beforePath="$PROJECT_DIR$/uv.lock" beforeDir="false" afterPath="$PROJECT_DIR$/uv.lock" afterDir="false" />
</list> </list>
@@ -41,76 +45,76 @@
<option name="hideEmptyMiddlePackages" value="true" /> <option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" /> <option name="showLibraryContents" value="true" />
</component> </component>
<component name="PropertiesComponent">{ <component name="PropertiesComponent"><![CDATA[{
&quot;keyToString&quot;: { "keyToString": {
&quot;DefaultHtmlFileTemplate&quot;: &quot;HTML File&quot;, "DefaultHtmlFileTemplate": "HTML File",
&quot;JavaScript 调试.output.html (1).executor&quot;: &quot;Run&quot;, "JavaScript 调试.output.html (1).executor": "Run",
&quot;JavaScript 调试.output.html.executor&quot;: &quot;Run&quot;, "JavaScript 调试.output.html.executor": "Run",
&quot;JavaScript 调试.regex.md_中文.html.executor&quot;: &quot;Run&quot;, "JavaScript 调试.regex.md_中文.html.executor": "Run",
&quot;JavaScript 调试.regex_中文.html.executor&quot;: &quot;Run&quot;, "JavaScript 调试.regex_中文.html.executor": "Run",
&quot;JavaScript 调试.test.html.executor&quot;: &quot;Run&quot;, "JavaScript 调试.test.html.executor": "Run",
&quot;JavaScript 调试.test2.html.executor&quot;: &quot;Run&quot;, "JavaScript 调试.test2.html.executor": "Run",
&quot;JavaScript 调试.test2_英文.html.executor&quot;: &quot;Run&quot;, "JavaScript 调试.test2_英文.html.executor": "Run",
&quot;JavaScript 调试.test4-1_中文.html.executor&quot;: &quot;Run&quot;, "JavaScript 调试.test4-1_中文.html.executor": "Run",
&quot;JavaScript 调试.互联网认证授权机制.html.executor&quot;: &quot;Run&quot;, "JavaScript 调试.互联网认证授权机制.html.executor": "Run",
&quot;JavaScript 调试.互联网认证授权机制_英文.html.executor&quot;: &quot;Run&quot;, "JavaScript 调试.互联网认证授权机制_英文.html.executor": "Run",
&quot;JavaScript 调试.毕业论文_英文.html.executor&quot;: &quot;Run&quot;, "JavaScript 调试.毕业论文_英文.html.executor": "Run",
&quot;ModuleVcsDetector.initialDetectionPerformed&quot;: &quot;true&quot;, "ModuleVcsDetector.initialDetectionPerformed": "true",
&quot;Python 测试.Python 测试 (markdown_mask.py 内).executor&quot;: &quot;Run&quot;, "Python 测试.Python 测试 (markdown_mask.py 内).executor": "Run",
&quot;Python 测试.markdown_mask.Test.test_basic_link_masking 的 Python 测试.executor&quot;: &quot;Run&quot;, "Python 测试.markdown_mask.Test.test_basic_link_masking 的 Python 测试.executor": "Run",
&quot;Python 测试.pytest (test_html.py 内).executor&quot;: &quot;Run&quot;, "Python 测试.pytest (test_html.py 内).executor": "Run",
&quot;Python.1test.executor&quot;: &quot;Run&quot;, "Python.1test.executor": "Run",
&quot;Python.2test2 (1).executor&quot;: &quot;Run&quot;, "Python.2test2 (1).executor": "Run",
&quot;Python.PDFtranslater (1).executor&quot;: &quot;Run&quot;, "Python.PDFtranslater (1).executor": "Run",
&quot;Python.PDFtranslater (2).executor&quot;: &quot;Run&quot;, "Python.PDFtranslater (2).executor": "Run",
&quot;Python.agent.executor&quot;: &quot;Debug&quot;, "Python.agent.executor": "Debug",
&quot;Python.agent_utils.executor&quot;: &quot;Run&quot;, "Python.agent_utils.executor": "Run",
&quot;Python.app (1).executor&quot;: &quot;Run&quot;, "Python.app (1).executor": "Run",
&quot;Python.app.executor&quot;: &quot;Run&quot;, "Python.app.executor": "Run",
&quot;Python.app2.executor&quot;: &quot;Run&quot;, "Python.app2.executor": "Run",
&quot;Python.app_test (1).executor&quot;: &quot;Run&quot;, "Python.app_test (1).executor": "Run",
&quot;Python.convert.executor&quot;: &quot;Run&quot;, "Python.convert.executor": "Run",
&quot;Python.converter_docling.executor&quot;: &quot;Run&quot;, "Python.converter_docling.executor": "Run",
&quot;Python.converter_mineru.executor&quot;: &quot;Run&quot;, "Python.converter_mineru.executor": "Run",
&quot;Python.markdown_splitter.executor&quot;: &quot;Debug&quot;, "Python.markdown_splitter.executor": "Debug",
&quot;Python.markdown_utils.executor&quot;: &quot;Run&quot;, "Python.markdown_utils.executor": "Run",
&quot;Python.test.executor&quot;: &quot;Run&quot;, "Python.test.executor": "Run",
&quot;Python.test1.executor&quot;: &quot;Run&quot;, "Python.test1.executor": "Run",
&quot;Python.test2.executor&quot;: &quot;Run&quot;, "Python.test2.executor": "Run",
&quot;Python.test3.executor&quot;: &quot;Run&quot;, "Python.test3.executor": "Run",
&quot;Python.test4.executor&quot;: &quot;Run&quot;, "Python.test4.executor": "Run",
&quot;Python.testhtml.executor&quot;: &quot;Run&quot;, "Python.testhtml.executor": "Run",
&quot;Python.translater.executor&quot;: &quot;Run&quot;, "Python.translater.executor": "Run",
&quot;Python.切分测试.executor&quot;: &quot;Run&quot;, "Python.切分测试.executor": "Run",
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;, "RunOnceActivity.ShowReadmeOnStart": "true",
&quot;RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager&quot;: &quot;true&quot;, "RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager": "true",
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;, "RunOnceActivity.git.unshallow": "true",
&quot;git-widget-placeholder&quot;: &quot;dev&quot;, "git-widget-placeholder": "dev",
&quot;last_opened_file_path&quot;: &quot;C:/Users/jxgm/Desktop/translate/docutranslate&quot;, "last_opened_file_path": "C:/Users/jxgm/Desktop/translate/docutranslate",
&quot;list.type.of.created.stylesheet&quot;: &quot;CSS&quot;, "list.type.of.created.stylesheet": "CSS",
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;, "node.js.detected.package.eslint": "true",
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;, "node.js.detected.package.tslint": "true",
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;, "node.js.selected.package.eslint": "(autodetect)",
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;, "node.js.selected.package.tslint": "(autodetect)",
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;, "nodejs_package_manager_path": "npm",
&quot;settings.editor.selected.configurable&quot;: &quot;preferences.pluginManager&quot;, "settings.editor.selected.configurable": "preferences.pluginManager",
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot; "vue.rearranger.settings.migration": "true"
} }
}</component> }]]></component>
<component name="RecentsManager"> <component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS"> <key name="CopyFile.RECENT_KEYS">
<recent name="C:\Users\jxgm\Desktop\translate\docutranslate" /> <recent name="C:\Users\jxgm\Desktop\translate\docutranslate" />
<recent name="C:\Users\jxgm\Desktop\translate\docutranslate\docutranslate\static" />
<recent name="C:\Users\jxgm\Desktop\translate\docutranslate\dist\DocuTranslate" /> <recent name="C:\Users\jxgm\Desktop\translate\docutranslate\dist\DocuTranslate" />
<recent name="C:\Users\jxgm\Desktop\FileTranslate\dist\DocuTranslate" /> <recent name="C:\Users\jxgm\Desktop\FileTranslate\dist\DocuTranslate" />
<recent name="C:\Users\jxgm\Desktop\FileTranslate\tests\files" /> <recent name="C:\Users\jxgm\Desktop\FileTranslate\tests\files" />
<recent name="C:\Users\jxgm\Desktop\FileTranslate\docutranslate\agents" />
</key> </key>
<key name="MoveFile.RECENT_KEYS"> <key name="MoveFile.RECENT_KEYS">
<recent name="C:\Users\jxgm\Desktop\translate\docutranslate" />
<recent name="C:\Users\jxgm\Desktop\FileTranslate\src" /> <recent name="C:\Users\jxgm\Desktop\FileTranslate\src" />
<recent name="C:\Users\jxgm\Desktop\FileTranslate" /> <recent name="C:\Users\jxgm\Desktop\FileTranslate" />
<recent name="C:\Users\jxgm\Desktop\FileTranslate\dist\DocuTranslate" /> <recent name="C:\Users\jxgm\Desktop\FileTranslate\dist\DocuTranslate" />
<recent name="C:\Users\jxgm\Desktop\FileTranslate\dist\app" /> <recent name="C:\Users\jxgm\Desktop\FileTranslate\dist\app" />
<recent name="C:\Users\jxgm\Desktop\FileTranslate\tests\files" />
</key> </key>
</component> </component>
<component name="RunManager" selected="Python.app_test (1)"> <component name="RunManager" selected="Python.app_test (1)">
@@ -607,6 +611,7 @@
<workItem from="1747740341909" duration="145000" /> <workItem from="1747740341909" duration="145000" />
<workItem from="1747752718385" duration="81000" /> <workItem from="1747752718385" duration="81000" />
<workItem from="1747754618316" duration="16566000" /> <workItem from="1747754618316" duration="16566000" />
<workItem from="1747828300140" duration="3391000" />
</task> </task>
<servers /> <servers />
</component> </component>
@@ -625,7 +630,7 @@
</option> </option>
</component> </component>
<component name="com.intellij.coverage.CoverageDataManagerImpl"> <component name="com.intellij.coverage.CoverageDataManagerImpl">
<SUITE FILE_PATH="coverage/filetranslate$app_test__1_.coverage" NAME="app_test (1) 覆盖结果" MODIFIED="1747818930270" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/tests" /> <SUITE FILE_PATH="coverage/filetranslate$app_test__1_.coverage" NAME="app_test (1) 覆盖结果" MODIFIED="1747831001962" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/tests" />
<SUITE FILE_PATH="coverage/filetranslate$test.coverage" NAME="test 覆盖结果" MODIFIED="1747472297913" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/tests" /> <SUITE FILE_PATH="coverage/filetranslate$test.coverage" NAME="test 覆盖结果" MODIFIED="1747472297913" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/tests" />
<SUITE FILE_PATH="coverage/filetranslate$convert.coverage" NAME="convert 覆盖结果" MODIFIED="1746963490689" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/docutranslate/utils" /> <SUITE FILE_PATH="coverage/filetranslate$convert.coverage" NAME="convert 覆盖结果" MODIFIED="1746963490689" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/docutranslate/utils" />
<SUITE FILE_PATH="coverage/PDFtranslate$PDFtranslater__1_.coverage" NAME="PDFtranslater (1) 覆盖结果" MODIFIED="1746633258205" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/pdftranslate_packages" /> <SUITE FILE_PATH="coverage/PDFtranslate$PDFtranslater__1_.coverage" NAME="PDFtranslater (1) 覆盖结果" MODIFIED="1746633258205" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/pdftranslate_packages" />

View File

@@ -19,18 +19,18 @@
# 安装 # 安装
使用pip 使用pip
`pip install docutranslate` 1. `pip install docutranslate`
`pip install docling`#如果使用docling进行文档解析 2. `pip install docling`#如果使用docling进行文档解析
使用uv 使用uv
`uv init` 1. `uv init`
`uv add docutranslate` 2. `uv add docutranslate`
`uv add docling`#如果使用docling进行文档解析 3. `uv add docling`#如果使用docling进行文档解析
使用git 使用git
`git clone https://github.com/xunbu/docutranslate.git` 1. `git clone https://github.com/xunbu/docutranslate.git`
`uv sync` 2. `uv sync`
3. `uv pip install -U -e .`
# 支持的文件格式 # 支持的文件格式
| 输入格式 | 输出格式 | | 输入格式 | 输出格式 |

View File

@@ -2,6 +2,7 @@ import asyncio
import io import io
import logging import logging
import time import time
from contextlib import asynccontextmanager
from pathlib import Path from pathlib import Path
from typing import List, Dict, Any, Optional from typing import List, Dict, Any, Optional
from urllib.parse import quote from urllib.parse import quote
@@ -10,17 +11,12 @@ import uvicorn
from fastapi import FastAPI, File, Form, UploadFile, Request, HTTPException from fastapi import FastAPI, File, Form, UploadFile, Request, HTTPException
from fastapi.responses import HTMLResponse, JSONResponse, StreamingResponse,FileResponse from fastapi.responses import HTMLResponse, JSONResponse, StreamingResponse,FileResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from docutranslate import FileTranslater # Assuming FileTranslater is in docutranslate module from docutranslate import FileTranslater
from docutranslate.logger import translater_logger from docutranslate.logger import translater_logger
from docutranslate.utils.resource_utils import resource_path from docutranslate.utils.resource_utils import resource_path
from docutranslate.global_values import available_packages from docutranslate.global_values import available_packages
DOCLING_EXIST=True if available_packages.get("docling") else False DOCLING_EXIST=True if available_packages.get("docling") else False
app = FastAPI()
STATIC_DIR=resource_path("static")
app.mount("/static",StaticFiles(directory=STATIC_DIR), name="static")
# --- 全局配置 --- # --- 全局配置 ---
log_queue: Optional[asyncio.Queue] = None log_queue: Optional[asyncio.Queue] = None
@@ -69,8 +65,8 @@ class QueueAndHistoryHandler(logging.Handler):
# --- 应用生命周期事件 --- # --- 应用生命周期事件 ---
@app.on_event("startup") @asynccontextmanager
async def startup_event(): async def lifespan(app: FastAPI):
global log_queue global log_queue
app.state.main_event_loop = asyncio.get_running_loop() app.state.main_event_loop = asyncio.get_running_loop()
log_queue = asyncio.Queue() log_queue = asyncio.Queue()
@@ -94,7 +90,13 @@ async def startup_event():
break break
translater_logger.info("应用启动完成,日志队列/历史处理器已正确配置。") translater_logger.info("应用启动完成,日志队列/历史处理器已正确配置。")
yield
app = FastAPI(lifespan=lifespan)
STATIC_DIR=resource_path("static")
app.mount("/static",StaticFiles(directory=STATIC_DIR), name="static")
# --- Background Task Logic --- # --- Background Task Logic ---
async def _perform_translation(params: Dict[str, Any], file_contents: bytes, original_filename: str): async def _perform_translation(params: Dict[str, Any], file_contents: bytes, original_filename: str):
@@ -403,7 +405,7 @@ async def download_html(filename_with_ext: str):
headers={"Content-Disposition": f"attachment; filename*=UTF-8''{quote(actual_filename, safe='', encoding='utf-8')}"} headers={"Content-Disposition": f"attachment; filename*=UTF-8''{quote(actual_filename, safe='', encoding='utf-8')}"}
) )
#TODO:端口被占用时使用其他端口
def run_app(): def run_app():
print("正在启动 DocuTranslate WebUI") print("正在启动 DocuTranslate WebUI")
print("请访问 http://127.0.0.1:8010 ctrl+点击链接即可打开)") print("请访问 http://127.0.0.1:8010 ctrl+点击链接即可打开)")

View File

@@ -4,3 +4,6 @@ from .converter_mineru import ConverterMineru
from docutranslate.global_values import conditional_import from docutranslate.global_values import conditional_import
if conditional_import("docling"): if conditional_import("docling"):
from .converter_docling import ConverterDocling from .converter_docling import ConverterDocling
# 打包docling时取消下面一行注释
# from .converter_docling import ConverterDocling

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DocuTranslate</title> <title>DocuTranslate</title>
<link rel="icon" href="/static/DocuTranslate.ico" type="image/x-icon">
<link rel="stylesheet" href="/static/pico.css"> <link rel="stylesheet" href="/static/pico.css">
<style> <style>
body { body {
@@ -134,6 +135,7 @@
margin-left: 1.5rem; /* Space from title */ margin-left: 1.5rem; /* Space from title */
/* Pico styles will apply display:flex, gap */ /* Pico styles will apply display:flex, gap */
} }
.preview-view-mode-buttons button { .preview-view-mode-buttons button {
font-size: 0.85rem; /* Smaller buttons for toggle */ font-size: 0.85rem; /* Smaller buttons for toggle */
padding: 0.4rem 0.8rem; padding: 0.4rem 0.8rem;
@@ -184,6 +186,7 @@
overflow: auto; overflow: auto;
background-color: #fff; background-color: #fff;
} }
.preview-pane pre { .preview-pane pre {
padding: 10px; padding: 10px;
white-space: pre-wrap; white-space: pre-wrap;
@@ -243,29 +246,36 @@
.form-grid { .form-grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
#previewContainer { #previewContainer {
flex-direction: column; flex-direction: column;
height: auto; height: auto;
} }
.modal-content { .modal-content {
height: 95vh; height: 95vh;
} }
.preview-pane { .preview-pane {
min-height: 300px; min-height: 300px;
} }
#previewTitleBar { #previewTitleBar {
flex-wrap: wrap; /* Allow wrapping for smaller screens */ flex-wrap: wrap; /* Allow wrapping for smaller screens */
} }
.preview-view-mode-buttons { .preview-view-mode-buttons {
margin-left: 0; margin-left: 0;
margin-top: 0.5rem; /* Space when wrapped */ margin-top: 0.5rem; /* Space when wrapped */
width: 100%; /* Take full width when wrapped */ width: 100%; /* Take full width when wrapped */
justify-content: center; justify-content: center;
} }
#closeModalBtnInTitle { #closeModalBtnInTitle {
order: -1; /* Move close button to top left on wrap if needed, or adjust layout */ order: -1; /* Move close button to top left on wrap if needed, or adjust layout */
margin-left: auto; /* Keep it to the right */ margin-left: auto; /* Keep it to the right */
} }
#previewModalTitle { #previewModalTitle {
width: 100%; /* Allow title to take width if buttons wrap below */ width: 100%; /* Allow title to take width if buttons wrap below */
text-align: center; text-align: center;
@@ -321,9 +331,9 @@
</div> </div>
</div> </div>
<details> <details>
<summary>文档转换引擎配置</summary> <summary>文档解析引擎配置</summary>
<div class="form-group"> <div class="form-group">
<label for="convert_engin">转换引擎</label> <label for="convert_engin">解析引擎</label>
<select id="convert_engin" name="convert_engin"> <select id="convert_engin" name="convert_engin">
<option value="mineru" selected>minerU</option> <option value="mineru" selected>minerU</option>
<option value="docling" id="docling">Docling</option> <option value="docling" id="docling">Docling</option>
@@ -342,9 +352,7 @@
<summary>翻译API配置</summary> <summary>翻译API配置</summary>
<div class="form-grid"> <div class="form-grid">
<div class="form-group"> <div class="form-group">
<label for="platform_select">AI 平台 <a id="api_href" class="no-style" href="/" <label for="platform_select">AI 平台 </label>
target="_blank"
title="获取API-KEY">🔗</a></label>
<select id="platform_select" name="platform_select_ui"> <select id="platform_select" name="platform_select_ui">
<option value="custom">自定义接口</option> <option value="custom">自定义接口</option>
<option value="https://api.openai.com/v1">OpenAI</option> <option value="https://api.openai.com/v1">OpenAI</option>
@@ -363,7 +371,9 @@
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="apikey">API 密钥</label> <label for="apikey">API 密钥<a id="api_href" class="no-style" href="/"
target="_blank"
title="获取API-KEY">🔗</a></label>
<input type="password" id="apikey" name="apikey" placeholder="平台对应的API Key" required> <input type="password" id="apikey" name="apikey" placeholder="平台对应的API Key" required>
</div> </div>
<div class="form-group"> <div class="form-group">
@@ -393,7 +403,8 @@
<div class="modal-content"> <div class="modal-content">
<div id="previewTitleBar"> <div id="previewTitleBar">
<h3 id="previewModalTitle">双语预览</h3> <h3 id="previewModalTitle">双语预览</h3>
<div class="preview-view-mode-buttons button-group" style="margin-top:0;"> <!-- Pico .button-group applied --> <div class="preview-view-mode-buttons button-group" style="margin-top:0;">
<!-- Pico .button-group applied -->
<button id="setBilingualViewBtn" role="button" class="primary">双语</button> <button id="setBilingualViewBtn" role="button" class="primary">双语</button>
<button id="setTranslatedOnlyViewBtn" role="button" class="outline">译文</button> <button id="setTranslatedOnlyViewBtn" role="button" class="outline">译文</button>
</div> </div>
@@ -629,10 +640,12 @@
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
fileDropArea.addEventListener(eventName, preventDefaults, false); fileDropArea.addEventListener(eventName, preventDefaults, false);
}); });
function preventDefaults(e) { function preventDefaults(e) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
} }
['dragenter', 'dragover'].forEach(eventName => { ['dragenter', 'dragover'].forEach(eventName => {
fileDropArea.addEventListener(eventName, () => { fileDropArea.addEventListener(eventName, () => {
if (!fileDropArea.classList.contains('file-selected')) { if (!fileDropArea.classList.contains('file-selected')) {
@@ -759,8 +772,14 @@
if (fileType.startsWith('text/') || ['md', 'json', 'xml', 'log', 'py', 'js', 'css', 'java', 'c', 'cpp', 'h', 'hpp', 'cs', 'rb', 'php', 'swift', 'kt', 'go', 'rs', 'ts'].includes(fileExtension)) { if (fileType.startsWith('text/') || ['md', 'json', 'xml', 'log', 'py', 'js', 'css', 'java', 'c', 'cpp', 'h', 'hpp', 'cs', 'rb', 'php', 'swift', 'kt', 'go', 'rs', 'ts'].includes(fileExtension)) {
const pre = document.createElement('pre'); const pre = document.createElement('pre');
reader.onload = (e) => { pre.textContent = e.target.result; originalPreviewPane.appendChild(pre); }; reader.onload = (e) => {
reader.onerror = () => { pre.textContent = '无法读取原文文件内容。'; originalPreviewPane.appendChild(pre); }; pre.textContent = e.target.result;
originalPreviewPane.appendChild(pre);
};
reader.onerror = () => {
pre.textContent = '无法读取原文文件内容。';
originalPreviewPane.appendChild(pre);
};
reader.readAsText(originalFile); reader.readAsText(originalFile);
} else if (fileType === 'application/pdf' || fileType === 'text/html' || fileExtension === 'html' || fileExtension === 'htm') { } else if (fileType === 'application/pdf' || fileType === 'text/html' || fileExtension === 'html' || fileExtension === 'htm') {
const iframe = document.createElement('iframe'); const iframe = document.createElement('iframe');
@@ -798,7 +817,9 @@
try { try {
translatedPreviewFrame.contentWindow.document.title = currentFileName + '_translated'; translatedPreviewFrame.contentWindow.document.title = currentFileName + '_translated';
URL.revokeObjectURL(blobUrl); URL.revokeObjectURL(blobUrl);
} catch (e) { console.warn('无法设置译文iframe标题或释放Blob URL', e); } } catch (e) {
console.warn('无法设置译文iframe标题或释放Blob URL', e);
}
}; };
setPreviewDisplayMode('bilingual'); // Default to bilingual view setPreviewDisplayMode('bilingual'); // Default to bilingual view
modal.style.display = 'block'; modal.style.display = 'block';
@@ -945,31 +966,31 @@
fileNameDisplay.classList.add('input-error-text'); fileNameDisplay.classList.add('input-error-text');
fileDropArea.classList.add('input-error'); fileDropArea.classList.add('input-error');
fileDropPrompt.classList.remove('hidden'); fileDropPrompt.classList.remove('hidden');
if(!firstErrorElement) firstErrorElement = fileDropArea; if (!firstErrorElement) firstErrorElement = fileDropArea;
} }
if (convertEnginSelect.value === 'mineru' && !mineruTokenInput.value.trim()) { if (convertEnginSelect.value === 'mineru' && !mineruTokenInput.value.trim()) {
currentStatusMsg += (currentStatusMsg ? ' ' : '') + '使用 Mineru 引擎时,必须填写 Mineru Token。'; currentStatusMsg += (currentStatusMsg ? ' ' : '') + '使用 Mineru 引擎时,必须填写 Mineru Token。';
mineruTokenInput.classList.add('input-error'); mineruTokenInput.classList.add('input-error');
if(!firstErrorElement) firstErrorElement = mineruTokenInput; if (!firstErrorElement) firstErrorElement = mineruTokenInput;
} }
if (!apikeyInput.value.trim()) { if (!apikeyInput.value.trim()) {
currentStatusMsg += (currentStatusMsg ? ' ' : '') + 'API 密钥不能为空。'; currentStatusMsg += (currentStatusMsg ? ' ' : '') + 'API 密钥不能为空。';
apikeyInput.classList.add('input-error'); apikeyInput.classList.add('input-error');
if(!firstErrorElement) firstErrorElement = apikeyInput; if (!firstErrorElement) firstErrorElement = apikeyInput;
} }
if (!modelInput.value.trim()) { if (!modelInput.value.trim()) {
currentStatusMsg += (currentStatusMsg ? ' ' : '') + '模型 ID 不能为空。'; currentStatusMsg += (currentStatusMsg ? ' ' : '') + '模型 ID 不能为空。';
modelInput.classList.add('input-error'); modelInput.classList.add('input-error');
if(!firstErrorElement) firstErrorElement = modelInput; if (!firstErrorElement) firstErrorElement = modelInput;
} }
if (platformSelect.value === 'custom' && !baseUrlInput.value.trim()) { if (platformSelect.value === 'custom' && !baseUrlInput.value.trim()) {
currentStatusMsg += (currentStatusMsg ? ' ' : '') + '自定义接口时API 地址不能为空。'; currentStatusMsg += (currentStatusMsg ? ' ' : '') + '自定义接口时API 地址不能为空。';
baseUrlInput.classList.add('input-error'); baseUrlInput.classList.add('input-error');
if(!firstErrorElement) firstErrorElement = baseUrlInput; if (!firstErrorElement) firstErrorElement = baseUrlInput;
} }
if(firstErrorElement){ if (firstErrorElement) {
statusMsg.textContent = currentStatusMsg; statusMsg.textContent = currentStatusMsg;
statusMsg.className = 'error-message'; statusMsg.className = 'error-message';
firstErrorElement.focus(); firstErrorElement.focus();

View File

@@ -4,12 +4,10 @@ from PyInstaller.utils.hooks import collect_all
datas = [('./.venv/Lib/site-packages/docling_parse/pdf_resources_v2', 'docling_parse/pdf_resources_v2'), datas = [('./.venv/Lib/site-packages/docling_parse/pdf_resources_v2', 'docling_parse/pdf_resources_v2'),
('./docutranslate/static', 'docutranslate/static')] ('./docutranslate/static', 'docutranslate/static')]
binaries = [] binaries = []
hiddenimports = [] hiddenimports=[]
tmp_ret = collect_all('easyocr') for i in ['easyocr','docling']:
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2] tmp_ret = collect_all(i)
tmp_ret = collect_all('docling') datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
a = Analysis( a = Analysis(
['./docutranslate/app.py'], ['./docutranslate/app.py'],

46
full_onefile.spec Normal file
View File

@@ -0,0 +1,46 @@
# -*- mode: python ; coding: utf-8 -*-
from PyInstaller.utils.hooks import collect_all
datas = [('./.venv/Lib/site-packages/docling_parse/pdf_resources_v2', 'docling_parse/pdf_resources_v2'),
('./docutranslate/static', 'docutranslate/static')]
binaries = []
hiddenimports=[]
for i in ['easyocr','docling']:
tmp_ret = collect_all(i)
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
a = Analysis(
['docutranslate\\app.py'],
pathex=[],
binaries=binaries,
datas=datas,
hiddenimports=hiddenimports,
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='DocuTranslate_full',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=['DocuTranslate.ico'],
)

View File

@@ -5,10 +5,22 @@ description = "文件翻译工具"
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"
dependencies = [ dependencies = [
"httpx", "httpx>=0.28.1",
"markdown2", "markdown2>=2.5.3",
"fastapi[standard]>=0.115.12", "fastapi[standard]>=0.115.12",
"pyinstaller>=6.13.0", "transformers==4.51.3",
]
[project.optional-dependencies]
docling = [
"docling"
]
[dependency-groups]
dev = [
"docling",
"pyinstaller",
] ]
[project.scripts] [project.scripts]
docutranslate = "docutranslate.cli:main" docutranslate = "docutranslate.cli:main"

369
requirements.txt Normal file
View File

@@ -0,0 +1,369 @@
# This file was autogenerated by uv via the following command:
# uv pip compile pyproject.toml -o requirements.txt
altgraph==0.17.4
# via pyinstaller
annotated-types==0.7.0
# via pydantic
anyio==4.9.0
# via
# httpx
# starlette
# watchfiles
attrs==25.3.0
# via
# jsonlines
# jsonschema
# referencing
beautifulsoup4==4.13.4
# via docling
certifi==2025.4.26
# via
# docling
# httpcore
# httpx
# requests
charset-normalizer==3.4.2
# via requests
click==8.1.8
# via
# docling
# rich-toolkit
# typer
# uvicorn
colorama==0.4.6
# via
# click
# tqdm
# uvicorn
dill==0.4.0
# via multiprocess
dnspython==2.7.0
# via email-validator
docling==2.33.0
# via docutranslate (pyproject.toml)
docling-core==2.31.1
# via
# docling
# docling-ibm-models
# docling-parse
docling-ibm-models==3.4.3
# via docling
docling-parse==4.0.1
# via docling
easyocr==1.7.2
# via docling
email-validator==2.2.0
# via fastapi
et-xmlfile==2.0.0
# via openpyxl
fastapi==0.115.12
# via docutranslate (pyproject.toml)
fastapi-cli==0.0.7
# via fastapi
filelock==3.18.0
# via
# huggingface-hub
# torch
# transformers
filetype==1.2.0
# via docling
fsspec==2025.5.0
# via
# huggingface-hub
# torch
h11==0.16.0
# via
# httpcore
# uvicorn
httpcore==1.0.9
# via httpx
httptools==0.6.4
# via uvicorn
httpx==0.28.1
# via
# docutranslate (pyproject.toml)
# fastapi
huggingface-hub==0.31.4
# via
# docling
# docling-ibm-models
# tokenizers
# transformers
idna==3.10
# via
# anyio
# email-validator
# httpx
# requests
imageio==2.37.0
# via scikit-image
jinja2==3.1.6
# via
# fastapi
# torch
jsonlines==3.1.0
# via docling-ibm-models
jsonref==1.1.0
# via docling-core
jsonschema==4.23.0
# via docling-core
jsonschema-specifications==2025.4.1
# via jsonschema
latex2mathml==3.78.0
# via docling-core
lazy-loader==0.4
# via scikit-image
lxml==5.4.0
# via
# docling
# python-docx
# python-pptx
markdown-it-py==3.0.0
# via rich
markdown2==2.5.3
# via docutranslate (pyproject.toml)
marko==2.1.3
# via docling
markupsafe==3.0.2
# via jinja2
mdurl==0.1.2
# via markdown-it-py
mpire==2.10.2
# via semchunk
mpmath==1.3.0
# via sympy
multiprocess==0.70.18
# via mpire
networkx==3.4.2
# via
# scikit-image
# torch
ninja==1.11.1.4
# via easyocr
numpy==2.2.6
# via
# docling-ibm-models
# easyocr
# imageio
# opencv-python-headless
# pandas
# safetensors
# scikit-image
# scipy
# shapely
# tifffile
# torchvision
# transformers
opencv-python-headless==4.11.0.86
# via
# docling-ibm-models
# easyocr
openpyxl==3.1.5
# via docling
packaging==25.0
# via
# huggingface-hub
# lazy-loader
# pyinstaller
# pyinstaller-hooks-contrib
# scikit-image
# transformers
pandas==2.2.3
# via
# docling
# docling-core
pefile==2023.2.7
# via pyinstaller
pillow==11.2.1
# via
# docling
# docling-core
# docling-ibm-models
# docling-parse
# easyocr
# imageio
# python-pptx
# scikit-image
# torchvision
pluggy==1.6.0
# via docling
pyclipper==1.3.0.post6
# via easyocr
pydantic==2.11.4
# via
# docling
# docling-core
# docling-ibm-models
# docling-parse
# fastapi
# pydantic-settings
pydantic-core==2.33.2
# via pydantic
pydantic-settings==2.9.1
# via docling
pygments==2.19.1
# via
# mpire
# rich
pyinstaller==6.13.0
# via docutranslate (pyproject.toml)
pyinstaller-hooks-contrib==2025.4
# via pyinstaller
pylatexenc==2.10
# via docling
pypdfium2==4.30.1
# via docling
python-bidi==0.6.6
# via easyocr
python-dateutil==2.9.0.post0
# via pandas
python-docx==1.1.2
# via docling
python-dotenv==1.1.0
# via
# pydantic-settings
# uvicorn
python-multipart==0.0.20
# via fastapi
python-pptx==1.0.2
# via docling
pytz==2025.2
# via pandas
pywin32==310
# via
# docling-parse
# mpire
pywin32-ctypes==0.2.3
# via pyinstaller
pyyaml==6.0.2
# via
# docling-core
# easyocr
# huggingface-hub
# transformers
# uvicorn
referencing==0.36.2
# via
# jsonschema
# jsonschema-specifications
regex==2024.11.6
# via transformers
requests==2.32.3
# via
# docling
# huggingface-hub
# transformers
rich==14.0.0
# via
# rich-toolkit
# typer
rich-toolkit==0.14.6
# via fastapi-cli
rpds-py==0.25.0
# via
# jsonschema
# referencing
rtree==1.4.0
# via docling
safetensors==0.5.3
# via
# docling-ibm-models
# transformers
scikit-image==0.25.2
# via easyocr
scipy==1.15.3
# via
# docling
# easyocr
# scikit-image
semchunk==2.2.2
# via docling-core
setuptools==80.8.0
# via
# pyinstaller
# pyinstaller-hooks-contrib
# torch
shapely==2.1.1
# via easyocr
shellingham==1.5.4
# via typer
six==1.17.0
# via python-dateutil
sniffio==1.3.1
# via anyio
soupsieve==2.7
# via beautifulsoup4
starlette==0.46.2
# via fastapi
sympy==1.14.0
# via torch
tabulate==0.9.0
# via
# docling-core
# docling-parse
tifffile==2025.5.10
# via scikit-image
tokenizers==0.21.1
# via transformers
torch==2.7.0
# via
# docling-ibm-models
# easyocr
# safetensors
# torchvision
torchvision==0.22.0
# via
# docling-ibm-models
# easyocr
tqdm==4.67.1
# via
# docling
# docling-ibm-models
# huggingface-hub
# mpire
# semchunk
# transformers
transformers==4.51.3
# via
# docutranslate (pyproject.toml)
# docling-core
# docling-ibm-models
typer==0.15.4
# via
# docling
# docling-core
# fastapi-cli
typing-extensions==4.13.2
# via
# anyio
# beautifulsoup4
# docling-core
# fastapi
# huggingface-hub
# pydantic
# pydantic-core
# python-docx
# python-pptx
# referencing
# rich-toolkit
# torch
# typer
# typing-inspection
typing-inspection==0.4.0
# via
# pydantic
# pydantic-settings
tzdata==2025.2
# via pandas
urllib3==2.4.0
# via requests
uvicorn==0.34.2
# via
# fastapi
# fastapi-cli
watchfiles==1.0.5
# via uvicorn
websockets==15.0.1
# via uvicorn
xlsxwriter==3.2.3
# via python-pptx

2093
uv.lock generated

File diff suppressed because it is too large Load Diff