feat: refactor summarizer and PDF extraction pipeline

- Split summarizer into summary_generator and summary_persister modules
- Refactor pdf_image_extractor to two-phase pipeline with PicoDet layout detection
- Add layout_detector service for PicoDet-S_layout_3cls integration
- Add exceptions module with ConflictError and NotFoundError
- Improve admin dashboard with better statistics and task management
- Add design review document with system optimization suggestions
- Add new tests for crawler, pdf_downloader, pipeline, and summary_utils
- Update dependencies and configuration
- Clean up dead code and improve error handling
This commit is contained in:
2026-06-13 13:16:47 +08:00
parent e2f0e1a8be
commit 21f16e6756
43 changed files with 3304 additions and 1494 deletions
+93 -52
View File
@@ -7,7 +7,7 @@
## 功能特性
- **每日抓取**:按日期拉取 HuggingFace Daily Papers,提取元数据并入库,自动去重与重试。
- **AI 中文总结**:下载 PDF调用 `pi` CLI 为每篇论文生成结构化中文解读(动机、方法、结果、局限性等),完成后清理临时文件。
- **AI 中文总结**:下载 PDF通过 `pi` `claude` 后端为每篇论文生成结构化中文解读(动机、方法、结果、局限性等),完成后清理临时文件。
- **浏览与详情**:首页按日期导航、论文详情页展示元数据与总结,提供未总结论文的英文原文回退。
- **搜索**:基于 SQLite FTS5 的关键词搜索(BM25 排序、片段高亮),覆盖标题、摘要、作者、标签与总结正文。
- **语义搜索**(可选):ChromaDB 向量数据库实现相似度搜索,优雅降级至 FTS5。
@@ -15,9 +15,10 @@
- **趋势看板**:Chart.js 驱动的可视化统计(日论文量、Top 标签、投票分布、总结完成率)。
- **个人化**:收藏、阅读状态、个人笔记与阅读列表。
- **RSS 订阅**:最近 7 天论文的 RSS 2.0 输出,支持标签过滤。
- **管理后台**Token 鉴权的手动抓取、总结、清扫、删除与日志查看接口
- **定时调度**APScheduler 内嵌调度,默认每日 08:00 自动抓取与总结(TaskLock 防重)。
- **管理后台**Session 认证的 Web 管理界面(仪表盘、论文管理、日志查看、手动操作)
- **定时调度**APScheduler 内嵌调度,默认每日自动抓取与总结(TaskLock 防重)。
- **LaTeX 图片提取**:下载 arXiv 源码,扫描 `.tex` 文件提取论文图片用于详情页展示。
- **布局检测**(可选):ONNX 模型识别 PDF 页面布局,提升图片提取精度。
- **HTMX 局部更新**:收藏切换等操作无需整页刷新。
- **键盘快捷键**`Ctrl+K``/` 聚焦搜索框。
@@ -31,7 +32,7 @@
| 模板 | Jinja2(服务端渲染) |
| 前端 | HTMX · 原生 JS · Chart.js · 自定义 CSS"kami" 纸质风格) |
| 数据库 | SQLite + SQLAlchemy · SQLite FTS5(全文搜索) |
| AI 总结 | `pi` CLI(外部工具 |
| AI 总结 | `pi` CLI`claude` CLI(可配置后端 |
| 语义搜索 | ChromaDB(可选) |
| 调度 | APScheduler(内嵌单进程) |
| CLI | Typer |
@@ -53,33 +54,39 @@ paper/
│ ├── main.py # FastAPI 入口(lifespan 管理)
│ ├── config.py # pydantic-settings 配置加载
│ ├── database.py # SQLAlchemy 引擎、会话与 FTS5
│ ├── models.py # 11 个 ORM 模型
│ ├── models.py # 11 个 ORM 模型 + 1 枚举
│ ├── utils.py # 共享工具函数
│ ├── exceptions.py # 统一业务异常体系
│ ├── cli.py # Typer CLIcrawl / summarize / init-db
│ │
│ ├── routes/ # 页面与 API 路由
│ │ ├── __init__.py
│ │ ├── pages.py # 首页、日期页、论文详情
│ │ ├── admin.py # Token 鉴权管理接口
│ │ ├── pages.py # 首页、日期页、论文详情、相似推荐
│ │ ├── admin.py # Session 认证管理后台
│ │ ├── search.py # 搜索、阅读列表、RSS
│ │ ├── user.py # 收藏、阅读状态、笔记 API
│ │ ├── trends.py # 趋势看板
│ │ └── compare.py # 论文对比页
│ │
│ ├── services/ # 业务逻辑层
│ │ ├── __init__.py
│ │ ├── crawler.py # HuggingFace API 爬虫
│ │ ├── summarizer.py # AI 总结编排
│ │ ├── summarizer.py # AI 总结编排(调度层)
│ │ ├── summary_generator.py # 总结生成与重试
│ │ ├── summary_persister.py # 总结持久化与文件管理
│ │ ├── summary_utils.py # 总结工具(prompt 构建、PDF 提取)
│ │ ├── pi_client.py # pi CLI 封装 + JSON 提取
│ │ ├── claude_backend.py # claude CLI 后端
│ │ ├── searcher.py # FTS5 + 语义搜索
│ │ ├── schemas.py # Pydantic 总结校验
│ │ ├── cleaner.py # 临时文件清理 + 日期范围删除
│ │ ├── scheduler.py # APScheduler 每日管线
│ │ ├── pipeline.py # 抓取 + 总结流水线编排
│ │ ├── admin.py # 管理后台查询与统计
│ │ ├── user_data.py # 收藏、阅读状态、笔记
│ │ ├── embedder.py # ChromaDB 向量索引
│ │ ├── trends.py # 趋势统计聚合
│ │ ├── pdf_downloader.py # PDF + LaTeX 源码下载
│ │ ├── pi_client.py # pi CLI 封装 + JSON 提取
│ │ └── image_extractor.py # LaTeX 图片提取
│ │ ├── pdf_image_extractor.py # LaTeX 图片提取 + 图表关联
│ │ └── layout_detector.py # ONNX 布局检测(可选)
│ │
│ ├── templates/ # Jinja2 模板
│ │ ├── base.html
@@ -89,24 +96,40 @@ paper/
│ │ ├── reading_list.html
│ │ ├── compare.html
│ │ ├── trends.html
│ │ ├── login.html
│ │ ├── admin_dashboard.html
│ │ ├── admin_papers.html
│ │ ├── admin_logs.html
│ │ └── partials/paper_card.html
│ │ └── partials/
│ │ ├── admin_subnav.html
│ │ ├── paper_card.html
│ │ └── summary_list.html
│ │
│ └── static/
│ ├── css/style.css # 自定义 CSSkami 风格)
└── js/app.js # 键盘快捷键
│ ├── css/
│ ├── style.css # 自定义 CSSkami 风格)
│ │ └── admin.css # 管理后台样式
│ ├── js/
│ │ ├── app.js # 键盘快捷键
│ │ ├── date-picker.js # 日期导航
│ │ └── lightbox.js # 图片灯箱
│ └── favicon.svg
├── data/ # 运行时数据(已 gitignore
│ ├── db/papers.db # SQLite 数据库
│ ├── papers/{arxiv_id}/ # 长期资产(meta.json / summary.json / 图片)
│ ├── tmp/{arxiv_id}/ # 临时下载(流程完成后清理)
── chroma/ # ChromaDB 向量库(可选)
── chroma/ # ChromaDB 向量库(可选)
│ └── models/ # ONNX 模型(布局检测)
├── scripts/
│ ├── init_db.py # 数据库初始化
── manual_crawl.py # 手动抓取脚本
── manual_crawl.py # 手动抓取脚本
│ ├── export_picodet_onnx.py # 导出布局检测 ONNX 模型
│ ├── reextract_images.py # 批量重新提取图片
│ └── validate_summary.py # 校验总结 JSON 结构
├── tests/ # 9 个测试模块
├── tests/ # 13 个测试模块
│ ├── conftest.py # 测试夹具(内存 DB、样本数据)
│ └── test_*.py # 各模块测试
@@ -120,21 +143,23 @@ paper/
### 1. 准备环境
- Python **3.12+**
- 可选:[`pi`](https://www.npmjs.com/package/@mariozechner/pi-coding-agent) CLI(用于 AI 总结)
- [uv](https://docs.astral.sh/uv/) 包管理器
- 可选:[`pi`](https://www.npmjs.com/package/@mariozechner/pi-coding-agent) CLI 或 [`claude`](https://claude.ai/code) CLI(用于 AI 总结)
### 2. 安装依赖
```bash
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
cp .env.example .env
uv sync
```
### 3. 配置环境变量
```bash
cp .env.example .env
# 编辑 .env,至少修改 ADMIN_TOKEN
# 编辑 .env,至少修改以下三项
ADMIN_USERNAME=admin
ADMIN_PASSWORD=your_secure_password
SECRET_KEY=your_random_secret_key
```
关键配置项:
@@ -145,19 +170,26 @@ cp .env.example .env
| `APP_DEBUG` | `false` | 调试模式(开启 uvicorn reload |
| `BASE_URL` | `http://127.0.0.1:8000` | 站点根 URL(用于 RSS 生成) |
| `APP_TIMEZONE` | `Asia/Shanghai` | 时区 |
| `ADMIN_TOKEN` | `change-me` | **必须修改** — 管理接口鉴权 |
| `ADMIN_USERNAME` | `admin` | 管理后台用户名 |
| `ADMIN_PASSWORD` | — | 管理后台密码 |
| `SECRET_KEY` | `change-me` | Session 签名密钥 |
| `HF_API_BASE` | `https://huggingface.co/api` | HuggingFace API 地址 |
| `HF_PROXY` | — | HTTP 代理 |
| `TOP_N` | `20` | 每日抓取 Top N 论文 |
| `HTTP_TIMEOUT_SECONDS` | `30` | HTTP 请求超时 |
| `HTTP_MAX_RETRIES` | `3` | HTTP 最大重试次数 |
| `SUMMARY_BACKEND` | `pi` | 总结后端:`pi``claude` |
| `PI_BIN` | — | `pi` CLI 路径 |
| `CLAUDE_BIN` | `claude` | `claude` CLI 路径 |
| `SUMMARY_SKILL` | `daily-paper-summary` | pi 总结技能名 |
| `SUMMARY_CONCURRENCY` | `3` | 最大并行总结数 |
| `SUMMARY_TIMEOUT_SECONDS` | `300` | 单篇总结超时 |
| `SUMMARY_MAX_RETRIES` | `1` | 总结最大重试次数 |
| `SUMMARY_TIMEOUT_SECONDS` | `1200` | 单篇总结超时 |
| `SUMMARY_MAX_RETRIES` | `2` | 总结最大重试次数 |
| `SUMMARY_PDF_MODE` | `auto` | PDF 传递方式:`auto` / `inject` / `search` |
| `UPVOTE_REFRESH_DAYS` | `7` | 自动刷新最近 N 天论文的 upvotes |
| `PDF_DOWNLOAD_TIMEOUT` | `120` | PDF 下载超时(秒) |
| `SCHEDULER_ENABLED` | `false` | 启用每日自动抓取 |
| `SCHEDULE_HOUR` / `SCHEDULE_MINUTE` | `8` / `0` | 定时任务时间(APP_TIMEZONE |
| `SCHEDULE_HOUR` / `SCHEDULE_MINUTE` | `4` / `0` | 定时任务时间(APP_TIMEZONE |
| `APP_WORKERS` | `1` | Uvicorn worker 数(必须为 1 |
| `DATABASE_URL` | `sqlite:///data/db/papers.db` | 数据库路径 |
| `CHROMA_ENABLED` | `false` | 启用语义搜索 |
@@ -166,18 +198,19 @@ cp .env.example .env
| `EMBED_API_KEY` | — | Embedding API Key |
| `EMBED_MODEL` | — | Embedding 模型名 |
| `EMBED_DIMENSIONS` | `0` | 向量维度 |
| `LAYOUT_MODEL_PATH` | `data/models/picodet_layout_3cls.onnx` | ONNX 布局检测模型路径(可选) |
| `LAYOUT_THRESHOLD` | `0.5` | 布局检测置信度阈值(可选) |
### 4. 初始化数据库
```bash
python scripts/init_db.py
# 或:python -m app.cli init-db
uv run python -m app.cli init-db
```
### 5. 启动服务
```bash
uvicorn app.main:app --host 127.0.0.1 --port 8000
uv run python -m app.main
```
> 调度器依赖单 worker:不可使用 `--workers > 1`,否则每日任务会被重复触发。
@@ -188,51 +221,59 @@ uvicorn app.main:app --host 127.0.0.1 --port 8000
## 常用命令
### 手动抓取指定日期
### 手动抓取
```bash
python scripts/manual_crawl.py 2025-01-15
# 或
python -m app.cli crawl 2025-01-15 --top 20
# 自动探测今天/昨天
uv run python -m app.cli crawl
# 指定日期
uv run python -m app.cli crawl 2025-01-15 --top 20
# 强制重抓(即使已有数据)
uv run python -m app.cli crawl --force
```
### 手动触发总结
```bash
# 单篇
python -m app.cli summarize 2401.01234
uv run python -m app.cli summarize 2401.01234
# 批量(所有待总结论文)
python -m app.cli summarize
uv run python -m app.cli summarize
# 指定后端和 PDF 模式
uv run python -m app.cli summarize --backend claude --pdf-mode inject
```
### 管理接口(Token 鉴权)
### 管理后台
```bash
# 抓取今日论文
curl -X POST "http://127.0.0.1:8000/admin/crawl" \
-H "Authorization: Bearer $ADMIN_TOKEN"
打开浏览器访问 `http://127.0.0.1:8000/admin/login`,使用 `.env` 中配置的用户名密码登录。
# 批量总结
curl -X POST "http://127.0.0.1:8000/admin/summarize" \
-H "Authorization: Bearer $ADMIN_TOKEN"
# 单篇总结
curl -X POST "http://127.0.0.1:8000/admin/summarize/2401.01234" \
-H "Authorization: Bearer $ADMIN_TOKEN"
```
管理后台包含:
- **仪表盘**:统计卡、调度器控制、存储信息、最近活动
- **论文管理**:搜索、筛选、单篇/批量删除
- **日志**:运行日志、总结状态、失败重试
### 运行测试
```bash
pytest
uv run pytest
```
### 代码检查
```bash
uv run ruff format # 格式化代码
uv run ruff check --fix # 自动修复 lint 问题
```
---
## 安全提示
- `ADMIN_TOKEN` 是管理接口的唯一鉴权凭证,请使用强随机值并妥善保管
- 管理后台使用 Session 认证,请务必在 `.env` 中设置强密码和随机 `SECRET_KEY`
- 默认仅监听 `127.0.0.1`,如需内网访问请配合反向代理与 HTTPS。
- 项目面向本地 / 内网部署,不包含多用户账号体系与公网防护。