Files
llm-library/CODEBASE.md

230 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 📘 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 KeyBearer 头 或 `?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 <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 模型 |
---
## 🔧 常见维护操作
### 新增论文
```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
```