# 📘 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. 搜索实现(2026-06-09 已修复) 搜索已在 2026-06-09 升级为**实时下拉结果面板**。输入 ≥2 字符后,200ms debounce 后调用 `/api/papers?q=`,在下拉面板中显示匹配论文(标题高亮、模块/领域、年份、标签)。卡片同步绿框高亮。点击结果项跳转到对应模块弹窗。点外部或 Escape 关闭。 ### 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 root@103.112.185.16:/opt/llm-library/ 4. docker cp 到容器:ssh ... "docker cp /opt/llm-library/ llm-library:/app/" 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 模型 | --- ## 🔧 常见维护操作 ### 新增论文 ```bash 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":["前沿"]}' ``` ### 修改论文标签 ```bash 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":["起点","关键节点"]}' ``` ### 触发翻译 ```bash curl -X POST "https://llmlibrary.spdis.space/api/translate/1706.03762?api_key=KEY" ``` ### 检查翻译状态 ```bash # 查看所有翻译 PDF 数量 ssh ... "docker exec llm-library ls /app/papers/translated/ | wc -l" # 查看翻译队列 curl https://llmlibrary.spdis.space/api/translate/status ```