论文 PDF 翻译与辅助阅读工具(自用)。
核心思路:不重造翻译内核,只补它的盲区。
- 翻译 + 排版保留 → 复用 pdf2zh-next(BabelDOC 内核)。 它已实现正文、图注、表格、图内有文本层文字的翻译。
- 我们的增量 → BabelDOC 处理不了的 text-as-path 图内文字(轴标签/图例/刻度), 以及它没有的 Web 阅读体验。
详见 docs/technical-design.md(含 M0/M1/M2 全部实测报告)。
| 能力 | 说明 |
|---|---|
| 翻译 | 上传 PDF → 保持排版的双语/纯译文 PDF |
| 图表 | 提取图表 → OCR 识别图内文字 → 擦除回填中文;原图/译文对照 |
| 助手 | 问答 / 总结 / 资料筛选,答案带引用编号,点击跳回原文高亮 |
| 阅读 | PDF.js 虚拟滚动、三态切换(原文/纯译文/双语)、缩放 |
| 术语表 | BabelDOC 自动提取,可下载 |
| 进度 | 翻译与图表处理都有 SSE 实时进度,可取消 |
| 能力 | 用途 | 示例 |
|---|---|---|
| 问答 | 针对论文提问 | 「预训练用了哪两个任务?」→ 答案每个结论后带 [p3-c7] |
| 总结 | 结构化摘要 | 一句话 / 问题 / 方法 / 结果 / 贡献 / 局限(JSON 渲染成卡片) |
| 资料筛选 | 找「论文里讲 X 的部分」 | 按主题返回按相关度排序的段落 + 为什么相关 |
核心体验:答案里的 [p3-c2] 是可点击的——点一下 PDF 就滚到那段并高亮。
这是它区别于普通聊天窗口的地方:答案可验证。
实现上没有用向量检索:论文正文通常 8k–20k tokens,模型能整篇吃下, 而且看全文比看检索片段准得多(也省掉 embedding 依赖)。
需要 uv 和 Node.js。
# 后端(首次会下载布局模型约 337MB 到 ~/.cache/babeldoc/)
uv sync --python 3.12
uv run pdf2zh_next --warmup # 可选:预热模型
uv run uvicorn backend.main:app --host 127.0.0.1 --port 8000
# 前端(另开一个终端)
cd web && npm install
npx vite --port 5173打开 http://127.0.0.1:5173/ ,上传 PDF,点「翻译」;翻译完成后点「图表」提取并翻译图内文字。
两种方式:前端「⚙ 模型设置」直接切(推荐),或改 config.toml 手动配。
cp config.toml.example config.toml # 两者都已被 .gitignore 排除配置分层:
| 数据 | 存哪 | 说明 |
|---|---|---|
| provider 定义(base_url / model / key) | config.toml |
单一配置源,含敏感信息 |
| 用户选择(翻译/问答各用哪个) | SQLite | 页面上改,立即生效 |
开箱可用:不建 config.toml 也能跑 —— 默认用 pdf2zh-next 官方免费翻译服务(免 key)。
支持的 provider 类型(只有三种):
| type | 覆盖 | 例子 |
|---|---|---|
openai_compat |
绝大多数厂商 | DeepSeek、SiliconFlow、Ollama、OpenAI、自建 vLLM |
cli |
订阅制工具 | claude -p(Claude Code)、codex exec、任意 CLI |
free_proxy |
内置免费服务 | pdf2zh-next 官方 chatproxy |
当前默认(实测数据见技术方案 §21):
provider_translate = "siliconflowfree" # 1-3s/条,批量场景
provider_chat = "claude" # 15-45s,比 codex 快且更完整| 能力 | Codex | Claude |
|---|---|---|
| 问答 | 54s | 26s |
| 总结 | 50s (2 条贡献) | 45s (4 条贡献) |
| 资料筛选 | 43s | 15s |
用途分离:翻译(短文本、要便宜快)和问答(长上下文、要强)可以指向不同 provider:
[llm]
provider_translate = "siliconflowfree" # 快:1-3s/条
provider_chat = "codex" # 强:35-48s/条
[llm.providers.codex] # 已登录的 Codex 订阅
type = "cli"
command = "codex exec --skip-git-repo-check -o {outfile}"
mode = "arg"实测的取舍:Codex 做图表翻译质量没问题,但一张复杂图有 100+ 条标签, 按 40s/条算要一小时以上 —— 翻译必须用轻量服务,重模型留给问答。
CLI 类 provider 的 {outfile} 技巧:codex exec 会在 stdout 打印 session id、
tokens used、warnings 等噪音。用它的 -o 选项把「最后一条消息」单独写文件即可拿到干净输出:
command = "codex exec --skip-git-repo-check -o {outfile}"Claude Code(需要先 claude auth login):
[llm.providers.claude]
type = "cli"
command = "claude -p"
mode = "arg" # prompt 自动追加到命令末尾
⚠️ 重要限制:siliconflowfree是受限制的翻译代理,只接受特定格式的 翻译 prompt(其他内容返回400 Keyword not allowed)。 做问答必须配真正的 LLM provider。详见技术方案 §17.4。
为什么不默认用 Google:实测会触发 HTTP 429 限流(见技术方案 §12.3)。
backend/
├── main.py FastAPI 端点
├── tasks.py 后台任务 + SSE 进度广播(翻译 / 图表 / 助手共用)
├── models.py / db.py SQLModel + SQLite
├── config.py 路径与默认配置
├── ir/ 文档结构解析(M3 地基)
│ ├── parse.py 段落切分 + 章节树 + bbox
│ └── store.py IR 落盘缓存
├── assist/ 辅助阅读(M3)
│ └── service.py 问答 / 总结 / 资料筛选 + 引用解析
├── llm/ 统一 LLM 接入层
│ ├── base.py Message / Usage / Provider 协议
│ ├── openai_compat.py 覆盖 DeepSeek/Ollama/任意自建
│ ├── cli.py 覆盖 Claude/Codex 订阅
│ ├── free_proxy.py 官方免费翻译服务
│ └── registry.py 配置加载 + 按用途解析
├── pipeline/translate.py pdf2zh_next 官方 API 封装
└── figures/ M2 图内文字翻译
├── detect.py 图表区域检测
├── ocr.py macOS Vision OCR(分区调用)
├── labels.py 文字提取 + 代码块过滤
├── render.py 擦除 + 回填
├── validate.py 翻译结果校验闸门(数字/相似度/代码)
├── translate.py 图表翻译(走 LLM 层)
├── service.py 编排
└── store.py JSON 索引 + 图片存储
web/
├── src/App.tsx 主界面
├── src/api.ts API 客户端 + SSE 订阅
└── src/components/
├── PdfViewer.tsx PDF.js 渲染(虚拟滚动 + 引用高亮)
├── FigurePanel.tsx 图表面板
└── AssistPanel.tsx 助手面板(问答/总结/筛选)
config.toml.example LLM 配置模板
docs/technical-design.md 技术方案与实测报告(§1-19)
几个踩过的坑,改代码前值得先看:
ConfigManager.initialize_config()会解析sys.argv—— 在 uvicorn 进程里会SystemExit。pipeline/translate.py里做了临时遮蔽,别删。- FastAPI 同步端点里不能调度 asyncio 任务 ——
/translate必须保持async def。 - OCR 必须分区 —— 整张大图识别率只有分区的 1/12(技术方案 §14.2)。
- 去重不能只看 bbox —— 必须配合文本相关性判断,否则会误删大量真实短标签(§14.3)。
- 擦了没填最危险 —— 所有回填路径都保证「有译文 → 才擦 → 才填」(§5.4.5)。
- 翻译结果必须过校验闸门 —— 数字一致性 / 相似度 / 代码模式,任一不过就保留原文
(§16.1)。闸门放在
figures/validate.py,改动那块务必跑一下test_validate的用例。 - 回填中文不要手工算坐标 —— CJK 字体 ascender 留白会让中文系统性偏下; 字号也不能直接用 bbox 高(英文 bbox 含行距,中文是方块字)。(§16.2)
- PDF.js 文本层有三个隐式依赖,少一个就"框选对不上字" ——
--scale-factor(不设则 span 的字号声明整条失效、退回body的 14px)、lang(不设则浏览器给「量宽度」和「渲染」选两套字体,实测宽度差 5.9%)、 以及字号必须是整数像素(非小数会让 Chromium 的跨元素拖选失效,译文页整页拖不动)。 排查与验证数据见技术方案 §24、§26。
见技术方案 §16.6。主要剩余限制:
- OCR 漏检/误识仍存在 —— 校验保证了「不乱写」,但不保证「全覆盖」。 未被识别或被拒绝的标签会保留英文原文。
- 部分轴标题被 OCR 切成多段时,可能只有一段被翻译。
已修复(见 §16):数字被改写、中文叠字、字号偏大、图表任务无取消入口。
AGPL-3.0 —— 与所依赖的 pdf2zh-next / BabelDOC 保持一致。
本项目调用 pdf2zh-next 的公开 Python API,没有复制它的源码; 选同一个许可证是为了消除「分发一个 AGPL 依赖」这件事上的任何争议。 如果你把它跑成对外的网络服务,同样需要公开你的修改。