10 KiB
📘 LLM 论文图书馆 — 代码全景 & 维护手册
最后更新:2026-06-09 | 馆长必读。每次改代码前先看这里。 本文档是代码的"说明书"——解释每个文件做什么、怎么改、踩过什么坑。
🗺️ 整体架构
用户浏览器 → Nginx (443, OpenResty) → Docker (127.0.0.1:8741→8000)
└── FastAPI (uvicorn)
├── /api/* → REST API
├── /papers/* → PDF 本地代理
└── / → 静态前端
数据流: papers.json 是唯一数据源。前端通过 /api/modules/{id} 拉取完整模块数据(含论文列表),在浏览器端渲染。
📁 文件清单 & 职责
后端 — api/
| 文件 | 行数 | 职责 | 改这里时注意 |
|---|---|---|---|
server.py |
~600 | FastAPI 主程序:路由、鉴权、PDF 代理、翻译触发 | 核心文件。 所有 API 在此。修改路由/鉴权/PDF 逻辑都在这里 |
downloader.py |
~200 | 批量下载 arXiv + HuggingFace PDF 到本地缓存 | 调用 httpx,有重试机制。下载到 papers/arxiv/ 和 papers/hf/ |
backfill.py |
~50 | 快速补全缺失的 arXiv PDF | 比 downloader 轻量,用 wget。适合 cron 定期跑 |
batch_translate.py |
~70 | 用 pdf2zh + DeepSeek 批量翻译 | 从 papers.json 读取论文,跳过已有译文。输出到 papers/translated/ |
parse_papers.py |
~110 | 从 llm_library.html 解析论文数据 → papers.json |
旧版工具,用于从原始 HTML 模板提取数据 |
extract_data.py |
~105 | 备用提取脚本(与 parse_papers 功能相似) | 路径硬编码了 /app/working/workspaces/default/ |
check_trans.py |
~15 | 测试翻译 API 连通性 | 仅开发调试用 |
前端 — static/
| 文件 | 行数 | 职责 | 改这里时注意 |
|---|---|---|---|
index.html |
~330 | HTML 壳(header + search + grid + overlay modal) | 注意: index_template.html 和它内容相同。修改时两个都要同步?→ 实际上只有 index.html 被服务。index_template.html 是旧版模板 |
app.js |
~285 | 全部前端逻辑 | ES6 模块风格(但不是 module)。无框架依赖,纯 vanilla JS。 |
style.css |
~315 | 暗色主题 CSS | 9 种模块配色 + 卡片光效 + PDF 阅读器样式 |
pdf.min.js |
~40K | PDF.js 库 | 外部依赖,不要手动改 |
数据 — data/
| 文件 | 说明 |
|---|---|
papers.json |
唯一数据源。 结构:{ module_id: { name, icon, desc, areas: [{ id, name, mainline[], branches[], forward[] }] } } |
配置 & 部署
| 文件 | 说明 |
|---|---|
Dockerfile |
Python 3.11-slim,安装 poppler-utils + torch CPU + pdf2zh |
start.sh |
容器启动脚本:检查 API Key、安装依赖、启动 uvicorn |
requirements.txt |
FastAPI + uvicorn + httpx + pydantic + tqdm |
nginx.conf |
参考配置(非容器内使用,宿主机 Nginx 反向代理) |
.env.example |
环境变量模板 |
proxy_conf.txt |
代理配置备注 |
🔌 API 路由速查
读操作(无需鉴权)
| 方法 | 路径 | 返回 | 备注 |
|---|---|---|---|
| GET | /api/health |
{"status":"ok"} |
健康检查 |
| GET | /api/stats |
模块数/领域数/论文总数 | 首页统计 |
| GET | /api/modules |
模块列表(含 paper_count) | 用于渲染首页卡片 |
| GET | /api/modules/{id} |
完整模块数据(含所有论文) | 点击卡片后调用 |
| GET | /api/papers?q=...&module=...&tag=... |
搜索结果列表 | 搜索栏 / 标签过滤 |
| GET | /papers/arxiv/{id}.pdf |
PDF 文件 | 本地缓存代理。也支持无 .pdf 后缀 |
| GET | /papers/hf/{name}.pdf |
PDF 文件 | HuggingFace 缓存代理 |
| GET | /papers/translated/{id}.pdf |
翻译 PDF | 中文译文 |
| GET | /api/translated/{id} |
{"exists":bool} |
检查译文是否存在 |
| GET | /api/translate/{id} |
段落级翻译 JSON | MyMemory 免费 API,文本翻译 |
| GET | /api/translate/{id}/status |
缓存状态 | 检查段落翻译缓存 |
| GET | /api/translate/status |
正在翻译的论文列表 | pdf2zh 翻译队列 |
写操作(需 API Key:Bearer 头 或 ?api_key= 参数)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/papers |
新增论文 |
| PUT | /api/papers?module_id=...&area_id=...&title=... |
修改论文 |
| DELETE | /api/papers?module_id=...&area_id=...&title=... |
删除论文 |
| POST | /api/translate/{paper_id} |
触发 pdf2zh 翻译 |
| POST | /api/download/{arxiv_id} |
按需下载单篇 PDF |
🧩 前端逻辑流程
1. init()
├── buildStatusBar() → 底部连通性检测条
├── fetch('/api/modules') → 获取模块列表
├── renderCards(mods) → 渲染 9 张卡片
├── attachGlowTracking() → 鼠标光效
└── checkSources() → ping arxiv + hf
2. 用户点击卡片
└── openModule(modId)
├── fetch('/api/modules/{id}') → 获取完整数据(缓存到 moduleData)
└── renderPapers(area)
├── section-label: 主线论文 / 支线论文 / 前瞻探索
└── paper-item: 年份 | 标题 | 作者 | venue | tags | 按钮
3. 用户点击「📄 阅读」
└── openPdfBtn(btn)
└── openPdf(url, title)
├── 创建/复用 #pdfOverlay
├── getLocalPdfUrl() → 优先走本地缓存 /papers/arxiv/{id}.pdf
└── iframe 加载 PDF,失败时回退到 arXiv 直链
4. 用户点击「📖 译文」
└── 同上,URL 指向 /papers/translated/{id}.pdf
🔒 安全机制
| 机制 | 位置 | 说明 |
|---|---|---|
| API Key 鉴权 | server.py:verify_api_key() |
所有 POST/PUT/DELETE 需 Bearer token 或 ?api_key= |
| 路径遍历防护 | server.py:safe_paper_id() |
正则 ^[a-zA-Z0-9_.\-]+$,禁止 .. |
| PDF 路径校验 | server.py:safe_pdf_path() |
resolve 后检查是否在 base 目录内 |
| CORS | 全局 allow_origins=["*"] |
当前宽松,如需收紧改这里 |
| 速率限制 | nginx.conf |
宿主机 Nginx 层 limit_req zone=api:10m rate=10r/s |
⚠️ 已知问题 & 注意事项
1. index.html vs index_template.html 双胞胎
两个文件内容几乎相同。index_template.html 是早期版本,被 extract_data.py 引用但路径硬编码为 /app/working/workspaces/default/llm_library.html。实际服务的是 index.html。 如果改前端 HTML 结构,只需改 index.html。
2. MyMemory 免费翻译 vs pdf2zh
GET /api/translate/{arxiv_id} 用的是 MyMemory 免费 API(质量一般),而真正的翻译是通过 POST /api/translate/{paper_id} 触发 pdf2zh + DeepSeek(生成中文 PDF)。两者是不同的翻译通道,不要混淆。
3. 按需下载实际跑全量
POST /api/download/{arxiv_id} 目前的实现是运行 downloader.py 下载所有未缓存的论文,而不是只下载请求的那一篇。这是个已知的粗糙实现。
4. 翻译覆盖率
翻译 PDF 存储在容器内 /app/papers/translated/,通过 volume 挂载持久化。翻译 API (POST /api/translate/{paper_id}) 会检查是否已翻译,避免重复。
5. 搜索是简单子串匹配
GET /api/papers?q=... 用 Python in 操作符在 title + authors 中做子串匹配。不支持模糊搜索、中文分词或 relevance ranking。对于 189 篇论文规模够用,但超过 500 篇可能需要改进。
6. 数据一致性
papers.json 是唯一数据源。新增论文通过 API 写操作 → 自动更新 JSON → 前端下次请求时自动获取最新数据。不要手动编辑 production 上的 papers.json 绕过 API,可能导致并发写入问题。
🔄 修改代码后的部署流程
1. 本地修改代码(在 paper_librarian/llm-library/ 下)
2. git add . && git commit -m "..." && git push origin main
3. scp 到宿主机:scp -i hk_server_key -P 16844 <file> root@103.112.185.16:/opt/llm-library/<path>
4. docker cp 到容器:ssh ... "docker cp /opt/llm-library/<path> llm-library:/app/<path>"
5. 重启容器:ssh ... "docker restart llm-library"
6. 用无头浏览器验证网站各功能正常
注意: API 文件(server.py)改动后需要重启容器。静态文件(*.html, *.js, *.css)也需要重启才能生效(因为被 FastAPI StaticFiles 挂载)。
🧪 无头浏览器验证清单
每次部署后,用 headless browser 验证以下功能:
- 首页加载:9 张模块卡片正常渲染
- 连通性检测条:arXiv 和 HF 状态显示正常
- 点击任意模块卡片 → 弹窗打开,tabs 切换正常
- 论文列表:主线/支线/前瞻 三个 section 显示正确
- 「📄 阅读」按钮:PDF iframe 能加载(至少一篇测试)
- 「📖 译文」按钮:翻译 PDF 能加载(如果存在)
- 搜索功能:输入关键词能返回结果
- Esc 键关闭弹窗 / PDF
- 底部栏链接:spdis.space 可点击
📊 性能基线
| 指标 | 当前值 | 备注 |
|---|---|---|
| 论文总数 | ~189 | papers.json ~200KB |
| API 响应时间 | <50ms | 纯内存 JSON 读取 |
| PDF 代理延迟 | <100ms | 本地文件系统读取 |
| 翻译耗时 | 3-8 min/篇 | pdf2zh + DeepSeek,取决于论文长度 |
| 容器镜像 | ~4.3GB | 含 PyTorch CPU + ONNX 模型 |
🔧 常见维护操作
新增论文
curl -X POST https://llmlibrary.spdis.space/api/papers?api_key=KEY \
-H "Content-Type: application/json" \
-d '{"module_id":"arch","area_id":"attention","section":"mainline","title":"...","authors":"...","year":2026,"venue":"arXiv","arxiv":"2601.01234","tags":["前沿"]}'
修改论文标签
curl -X PUT "https://llmlibrary.spdis.space/api/papers?api_key=KEY&module_id=arch&area_id=attention&title=Attention Is All You Need" \
-H "Content-Type: application/json" \
-d '{"tags":["起点","关键节点"]}'
触发翻译
curl -X POST "https://llmlibrary.spdis.space/api/translate/1706.03762?api_key=KEY"
检查翻译状态
# 查看所有翻译 PDF 数量
ssh ... "docker exec llm-library ls /app/papers/translated/ | wc -l"
# 查看翻译队列
curl https://llmlibrary.spdis.space/api/translate/status