feat: initial project structure

- Add FastAPI app with paper browsing UI and REST API
- Add crawler service and database models
- Add scripts for DB init and manual crawl
- Add docs (api-and-ui, data-model, services)
- Add requirements and project config
This commit is contained in:
2026-06-05 21:56:40 +08:00
commit f1be24ab83
26 changed files with 2557 additions and 0 deletions
+224
View File
@@ -0,0 +1,224 @@
# API 路由与页面设计
> 本文档定义页面路由、JSON API、管理接口、用户流程和验收标准。
---
## 1. 页面路由
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/` | 重定向到 `/day/{today}` |
| GET | `/day/{date}` | 指定日期论文列表 |
| GET | `/paper/{arxiv_id}` | 论文详情 |
| GET | `/search` | 搜索页和搜索结果 |
| GET | `/reading-list` | 收藏和阅读列表 |
| GET | `/admin/logs` | 管理日志页,需要 token |
| GET | `/rss.xml` | RSS Feed |
后续增强:
- `/trends`
- `/compare?ids=id1,id2`
- `/similar/{arxiv_id}`
---
## 2. 数据 API
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/papers?date=&tag=&q=` | 论文列表 |
| GET | `/api/paper/{arxiv_id}` | 单篇论文详情 |
| GET | `/api/dates` | 有数据的日期列表 |
| GET | `/api/tags` | 标签及计数 |
| GET | `/api/stats` | 统计信息 |
| GET | `/api/search?q=&tag=` | FTS5 搜索 |
---
## 3. 用户数据 API
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/bookmark/{arxiv_id}` | 收藏/取消收藏 |
| POST | `/api/reading-status/{arxiv_id}` | 更新阅读状态 |
| GET | `/api/note/{arxiv_id}` | 获取笔记 |
| POST | `/api/note/{arxiv_id}` | 保存笔记 |
请求和响应使用 JSON。无账号体系,数据写入本地 SQLite。
安全边界:
- 默认 `APP_HOST=127.0.0.1` 时,用户数据 API 只服务本机访问。
- 如果绑定到非本地地址,用户数据写接口需要启用 same-origin 检查或 token。
---
## 4. 管理接口
所有管理接口都需要:
```text
Authorization: Bearer <ADMIN_TOKEN>
```
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/admin/crawl` | 手动抓取指定日期,默认今天 |
| POST | `/admin/summarize/{arxiv_id}` | 手动总结或重跑单篇 |
| POST | `/admin/summarize` | 批量总结 pending 论文 |
| POST | `/admin/cleanup` | 清理临时文件 |
| POST | `/admin/delete` | 删除指定日期范围内的数据 |
| GET | `/admin/logs` | 查看任务日志 |
### `/admin/delete` 请求体
```json
{
"date_start": "2026-06-01",
"date_end": "2026-06-05",
"include_notes": true,
"confirm": "DELETE"
}
```
`confirm` 必须为 `DELETE`,否则拒绝执行。
---
## 5. 页面状态
### 首页 / 日期页
每张论文卡片展示:
- 中文标题;没有总结时展示英文标题。
- 一句话摘要;没有总结时展示英文 abstract 截断。
- 标签、作者、upvotes、难度。
- 总结状态:未总结、总结中、失败、已完成。
- 收藏按钮、阅读状态入口、详情链接。
### 详情页
详情页按状态降级:
| 状态 | 展示 |
|------|------|
| 无总结 | 英文标题、作者、摘要、HF/arXiv 链接、手动总结按钮 |
| processing | 元数据 + “正在生成总结” |
| failed | 元数据 + 错误类型 + 手动重跑按钮 |
| done/normal | 完整中文结构化解读 |
| done/degraded | 展示已有内容,缺失模块标注不完整 |
| done/low | 顶部质量提示 + 已有内容 |
详情模块:
- 一句话摘要
- 预置知识
- 研究动机
- 核心方法
- 实验结果
- 局限和改进方向
- 原文链接
- 收藏、阅读状态、个人笔记
### 搜索页
MVP 只提供关键词搜索:
- 搜索框。
- 标签筛选。
- 结果按相关性和日期排序。
- 命中片段高亮。
语义搜索作为后续增强,UI 上先不展示模式切换。
### 阅读列表
筛选项:
- 全部收藏。
- 未读。
- 已读摘要。
- 已读原文。
- 有笔记。
- 标签。
---
## 6. 用户流程
```text
访问 /
-> /day/{today}
-> 浏览论文卡片
-> 点击论文进入 /paper/{arxiv_id}
-> 收藏 / 修改阅读状态 / 写笔记
-> 搜索 /search?q=...
-> 阅读列表 /reading-list
```
管理员流程:
```text
POST /admin/crawl
-> 抓取论文并入库
-> POST /admin/summarize
-> 生成总结
-> POST /admin/cleanup
-> 查看 /admin/logs
```
删除流程:
```text
POST /admin/delete
-> 校验 token 和 confirm
-> 删除日期范围内论文、索引、用户数据、本地文件
-> 写入删除记录和日志
```
---
## 7. MVP 验收标准
### 抓取
- 指定日期能抓取 HF Daily Papers 前 N 篇。
- 同一天重复抓取不会重复插入。
- 空日期返回成功状态和 0 篇日志。
- 网络失败有 timeout、重试和错误日志。
### 总结
- 单篇总结失败不会影响其他论文。
- 必填字段缺失时自动重试一次。
- 重试失败后标记 `permanent_failure`
- 总结成功后页面、FTS 索引和 summary.json 同步更新。
- 成功或失败后都会清理 PDF/源码临时文件。
### 页面
- 首页能显示未总结、总结中、失败、完成状态。
- 详情页无总结时仍可阅读英文元数据。
- degraded/low 总结有清晰提示。
- 移动端不出现主要内容横向溢出。
### 搜索
- 能搜索标题、摘要、作者、标签、中文总结。
- 删除论文后搜索结果不再出现该论文。
### 管理
- 无 token 不能调用管理接口。
- token 错误返回 401。
- 删除接口没有 `confirm=DELETE` 时拒绝执行。
- 删除指定日期范围后,页面、搜索索引、用户数据和本地文件保持一致。
### 调度
- 单 worker 下每日任务只执行一次。
- 多 worker 或非本地 host 配置存在风险时,应用启动给出明确告警或拒绝启动。
- `/` 的 today 和每日调度日期都按 `APP_TIMEZONE` 计算。
+394
View File
@@ -0,0 +1,394 @@
# 数据模型
> 本文档定义 SQLite 表、summary.json schema、索引同步、校验和删除策略。
---
## 1. 设计原则
1. SQLite 是主存储,页面和 API 优先从 SQLite 读取。
2. PDF、LaTeX 源码等下载文件是临时资产,解析和总结完成后清理。
3. `meta.json``summary.json``raw_output.txt` 可作为可读备份保存在 `data/papers/{arxiv_id}/`
4. 作者和标签使用规范化表,避免 JSON 字符串聚合困难。
5. FTS5 由独立索引表维护,写入/更新/删除论文时同步更新。
6. ChromaDB 是后续增强,不能成为 MVP 页面渲染的必要依赖。
7. 每个 SQLite 连接必须执行 `PRAGMA foreign_keys=ON`,确保级联删除生效。
---
## 2. 数据库表
### papers — 论文主表
```sql
CREATE TABLE papers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
arxiv_id TEXT UNIQUE NOT NULL,
title_en TEXT NOT NULL,
title_zh TEXT,
abstract TEXT,
published_at DATE,
paper_date DATE NOT NULL,
crawled_at DATETIME NOT NULL,
upvotes INTEGER DEFAULT 0,
hf_url TEXT,
arxiv_url TEXT,
pdf_url TEXT,
source_url TEXT,
asset_status TEXT DEFAULT 'not_downloaded', -- not_downloaded / ready / failed / cleaned
asset_error TEXT,
meta_path TEXT,
summary_path TEXT,
raw_output_path TEXT,
summary_quality TEXT -- normal / degraded / low
);
```
手动删除采用物理删除。删除审计写入 `data_delete_jobs``crawl_logs`
### paper_authors — 作者表
```sql
CREATE TABLE paper_authors (
id INTEGER PRIMARY KEY AUTOINCREMENT,
paper_id INTEGER NOT NULL REFERENCES papers(id) ON DELETE CASCADE,
name TEXT NOT NULL,
position INTEGER DEFAULT 0,
UNIQUE(paper_id, name)
);
```
### paper_tags — 标签表
```sql
CREATE TABLE paper_tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
paper_id INTEGER NOT NULL REFERENCES papers(id) ON DELETE CASCADE,
tag TEXT NOT NULL,
source TEXT DEFAULT 'hf', -- hf / ai / user
UNIQUE(paper_id, tag, source)
);
```
### paper_summaries — 结构化总结表
```sql
CREATE TABLE paper_summaries (
paper_id INTEGER PRIMARY KEY REFERENCES papers(id) ON DELETE CASCADE,
one_line TEXT,
difficulty TEXT,
prerequisites_json TEXT,
motivation_problem TEXT,
motivation_goal TEXT,
motivation_gap TEXT,
method_overview TEXT,
method_key_idea TEXT,
method_steps_json TEXT,
method_novelty TEXT,
results_main_json TEXT,
results_benchmarks_json TEXT,
limitations_json TEXT,
weaknesses_json TEXT,
future_work_json TEXT,
reproducibility TEXT,
full_json TEXT NOT NULL,
updated_at DATETIME NOT NULL
);
```
结构化字段用于页面、对比、搜索和排序;`full_json` 保留完整原始结构。
### summary_status — 总结状态
```sql
CREATE TABLE summary_status (
id INTEGER PRIMARY KEY AUTOINCREMENT,
paper_id INTEGER NOT NULL REFERENCES papers(id) ON DELETE CASCADE,
status TEXT NOT NULL, -- pending / processing / done / failed / permanent_failure
quality TEXT, -- normal / degraded / low
error_type TEXT, -- pdf_download_failed / timeout / process_error / json_not_found / json_invalid / field_missing / schema_error / unknown
error TEXT,
retry_count INTEGER DEFAULT 0,
raw_output_saved BOOLEAN DEFAULT FALSE,
started_at DATETIME,
completed_at DATETIME,
UNIQUE(paper_id)
);
```
### papers_fts — 全文搜索索引
```sql
CREATE VIRTUAL TABLE papers_fts USING fts5(
title_en,
title_zh,
abstract,
authors,
tags,
summary_text,
tokenize='unicode61'
);
```
使用普通 FTS5 表,由应用层显式维护。普通 FTS5 会复制一份索引文本,数据量可接受,换取简单可靠的更新和删除语义:
- 新增论文:插入标题、摘要、作者、标签。
- 总结完成:更新中文标题和 `summary_text`
- 收藏/笔记变更:不进入 FTS,避免个人笔记污染论文搜索。
- 删除论文:同步删除对应 FTS row。
写入时必须使用 `papers.id` 作为 FTS rowid
```sql
INSERT INTO papers_fts(rowid, title_en, title_zh, abstract, authors, tags, summary_text)
VALUES (:paper_id, :title_en, :title_zh, :abstract, :authors, :tags, :summary_text);
```
更新时可使用普通 `UPDATE`,也可先按 rowid 删除再插入。删除论文时执行:
```sql
DELETE FROM papers_fts WHERE rowid = :paper_id;
```
### crawl_logs — 任务日志
```sql
CREATE TABLE crawl_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task TEXT NOT NULL, -- crawl / summarize / cleanup / delete / scheduler
status TEXT NOT NULL, -- running / success / failed
date DATE,
papers_found INTEGER,
papers_new INTEGER,
error TEXT,
started_at DATETIME NOT NULL,
completed_at DATETIME
);
```
### task_locks — 任务锁
```sql
CREATE TABLE task_locks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task TEXT NOT NULL,
lock_key TEXT NOT NULL, -- 通常是日期,如 2026-06-05
status TEXT NOT NULL, -- running / finished / failed
owner TEXT,
acquired_at DATETIME NOT NULL,
released_at DATETIME
);
CREATE UNIQUE INDEX uq_task_locks_running
ON task_locks(task, lock_key)
WHERE status = 'running';
```
防重入规则:启动任务前插入 `status='running'` 的锁;插入失败说明同一任务正在运行,直接跳过或返回 409。任务完成后更新为 `finished``failed`
### user_bookmarks — 收藏
```sql
CREATE TABLE user_bookmarks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
paper_id INTEGER NOT NULL REFERENCES papers(id) ON DELETE CASCADE,
note TEXT,
created_at DATETIME NOT NULL,
UNIQUE(paper_id)
);
```
### user_reading_status — 阅读状态
```sql
CREATE TABLE user_reading_status (
id INTEGER PRIMARY KEY AUTOINCREMENT,
paper_id INTEGER NOT NULL REFERENCES papers(id) ON DELETE CASCADE,
status TEXT NOT NULL, -- unread / skimmed / read_summary / read_full
updated_at DATETIME NOT NULL,
UNIQUE(paper_id)
);
```
### user_notes — 个人笔记
```sql
CREATE TABLE user_notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
paper_id INTEGER NOT NULL REFERENCES papers(id) ON DELETE CASCADE,
content TEXT NOT NULL,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
UNIQUE(paper_id)
);
```
### data_delete_jobs — 手动删除记录
```sql
CREATE TABLE data_delete_jobs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date_start DATE NOT NULL,
date_end DATE NOT NULL,
include_notes BOOLEAN DEFAULT TRUE,
paper_count INTEGER DEFAULT 0,
status TEXT NOT NULL, -- running / success / failed
error TEXT,
started_at DATETIME NOT NULL,
completed_at DATETIME
);
```
---
## 3. summary.json Schema
```python
from pydantic import BaseModel, Field, field_validator
class Prerequisites(BaseModel):
concepts: list[str] = Field(default_factory=list)
level: str = ""
class Motivation(BaseModel):
problem: str
goal: str = ""
gap: str = ""
class Method(BaseModel):
overview: str = ""
key_idea: str
steps: list[str] = Field(default_factory=list)
novelty: str = ""
class Results(BaseModel):
main_findings: list[str] = Field(default_factory=list)
benchmarks: list[dict] = Field(default_factory=list)
limitations: list[str] = Field(default_factory=list)
class Improvements(BaseModel):
weaknesses: list[str] = Field(default_factory=list)
future_work: list[str] = Field(default_factory=list)
reproducibility: str = ""
class SummarySchema(BaseModel):
title_zh: str
one_line: str
tags: list[str]
difficulty: str = ""
paper_date: str | None = None
prerequisites: Prerequisites = Field(default_factory=Prerequisites)
motivation: Motivation
method: Method
results: Results = Field(default_factory=Results)
improvements: Improvements = Field(default_factory=Improvements)
@field_validator("title_zh", "one_line")
@classmethod
def non_empty_text(cls, value: str) -> str:
if not value or not value.strip():
raise ValueError("field cannot be empty")
return value.strip()
@field_validator("tags")
@classmethod
def non_empty_tags(cls, value: list[str]) -> list[str]:
tags = [tag.strip() for tag in value if tag and tag.strip()]
if not tags:
raise ValueError("tags cannot be empty")
return tags
```
实际实现时还要给 `Motivation.problem``Method.key_idea` 加同样的非空校验,空字符串视为 `field_missing`
### 字段分级
| 级别 | 字段 | 处理 |
|------|------|------|
| 必填 | `title_zh`, `one_line`, `tags`, `motivation.problem`, `method.key_idea` | 缺失则失败并重试 |
| 重要 | `motivation.goal`, `motivation.gap`, `method.overview`, `results.main_findings` | 缺失可入库,标记 `degraded` |
| 可选 | `benchmarks`, `limitations`, `improvements`, `prerequisites` | 缺失用默认值 |
---
## 4. 校验和错误处理
### 状态流转
```text
pending -> processing -> done
└-> failed -> pending retry -> processing
└-> permanent_failure
```
### 错误分级
| error_type | 场景 | 自动重试 |
|------------|------|----------|
| timeout | pi 超时 | 是 |
| pdf_download_failed | PDF 下载失败或文件不可读 | 是 |
| process_error | pi 进程非 0 退出 | 是 |
| json_not_found | 输出中找不到 JSON | 是 |
| json_invalid | JSON 解析失败 | 是 |
| field_missing | 必填字段缺失 | 是 |
| schema_error | 字段类型不合法 | 是 |
| unknown | 未分类异常 | 是 |
最大自动重试次数为 1。重试后仍失败则标记 `permanent_failure`,管理后台可手动重跑。
### 质量分级
| quality | 条件 | 页面表现 |
|---------|------|----------|
| normal | 必填和重要字段完整 | 完整展示 |
| degraded | 必填完整,重要字段部分缺失 | 缺失模块显示“不完整” |
| low | 字段存在但内容明显空洞 | 顶部提示“AI 总结质量较低” |
---
## 5. 删除和清理策略
### 临时文件清理
每篇论文处理完成后删除:
- `data/tmp/{arxiv_id}/paper.pdf`
- `data/tmp/{arxiv_id}/source/`
- 其他下载中间文件
总结失败时也应清理下载文件,但保留 `raw_output.txt` 和错误日志。
### 手动删除指定日期范围
管理员可删除 `paper_date` 落在指定范围内的数据。删除流程:
1. 查询目标论文。
2. 删除用户收藏、阅读状态、笔记。
3. 删除 summary/status/authors/tags。
4. 删除 FTS5 索引。
5. 删除 `data/papers/{arxiv_id}/``data/tmp/{arxiv_id}/`
6. 物理删除 `papers` 记录。
7. 写入 `data_delete_jobs``crawl_logs`
如后续需要可恢复删除,再引入 `deleted_at` 软删除字段;MVP 不实现。
---
## 6. ChromaDB 增强设计
ChromaDB 不进入 MVP。接入时只索引 `paper_summaries` 中的高信号字段:
- 中文标题
- 英文标题
- 标签
- 一句话摘要
- `motivation_problem`
- `method_key_idea`
向量维度必须和 `EMBED_MODEL` 匹配。写入前校验 embedding 长度,不匹配则跳过语义索引并记录日志,不影响普通页面和 FTS 搜索。
+269
View File
@@ -0,0 +1,269 @@
# 服务模块详解
> 本文档描述各服务模块的职责、输入输出、失败处理和实现约束。
---
## 1. 爬虫服务
**职责**:从 HuggingFace Daily Papers 获取论文列表,写入元数据。PDF 不在抓取阶段长期保存。
### 数据源
- Daily Papers API`GET https://huggingface.co/api/daily_papers?date=YYYY-MM-DD`
- PDF`https://arxiv.org/pdf/{arxiv_id}.pdf`(总结阶段按需下载)
- 源码(后续增强):`https://arxiv.org/e-print/{arxiv_id}`
HuggingFace 官方 Hub API 文档说明 `/api/daily_papers` 支持 `date` 查询参数。
### 规则
- `arxiv_id` 是唯一键。
- 重复抓取同一天时,已有论文只更新 upvotes、标签等可变元数据,不重复插入。
- 网络请求必须设置 timeout、User-Agent、重试次数。
- API 返回空列表时记录成功日志,不视为失败。
- 抓取阶段不下载 PDF;总结阶段 PDF 下载失败时更新 `asset_status=failed``summary_status.error_type=pdf_download_failed`
### 接口
```python
async def fetch_daily(date: str, top_n: int) -> list[PaperMeta]: ...
async def upsert_papers(papers: list[PaperMeta]) -> list[Paper]: ...
```
---
## 2. AI 总结服务
**职责**:调用 pi CLI,把单篇论文转成结构化中文总结。
### 调用原则
- 一篇论文一次 pi 调用。
- 并发数由 `SUMMARY_CONCURRENCY` 控制,默认 3。
- 单篇超时由 `SUMMARY_TIMEOUT_SECONDS` 控制,默认 300 秒。
- pi 路径通过 `PI_BIN` 配置,当前可以先使用宿主机路径;跑通后再抽象部署方式。
- PDF 在总结开始前按需下载到 `data/tmp/{arxiv_id}/paper.pdf`,总结成功或失败后清理。
### 调用示例
```bash
pi -p --skill daily-paper-summary \
"请深度解读以下论文,并按指定 JSON schema 输出:
@data/papers/2401.12345/meta.json
@data/tmp/2401.12345/paper.pdf"
```
### 流程
```text
取 pending 论文
-> 下载 PDF 到 data/tmp/{arxiv_id}/paper.pdf
-> status=processing
-> 调 pi
-> 提取 JSON
-> Pydantic 校验
-> 写 summary.json
-> 写 paper_summaries / paper_tags / papers_fts
-> status=done
-> 清理 PDF/源码临时文件
```
失败时保存 raw output、更新 `summary_status`,并清理下载文件。
PDF 下载失败不调用 pi,直接记录 `pdf_download_failed` 并进入重试流程。
---
## 3. 搜索服务
**职责**:MVP 提供 FTS5 关键词搜索;后续接入 ChromaDB 语义搜索。
### FTS5 搜索
索引字段:
- 英文标题
- 中文标题
- 英文摘要
- 作者
- 标签
- 中文总结正文
应用层负责同步 FTS
```python
def build_fts_document(paper: Paper, summary: PaperSummary | None) -> FtsDocument:
summary_text = ""
if summary:
summary_text = " ".join([
summary.one_line or "",
summary.motivation_problem or "",
summary.motivation_goal or "",
summary.method_overview or "",
summary.method_key_idea or "",
" ".join(summary.results_main or []),
])
return FtsDocument(...)
```
### ChromaDB 语义搜索(后续)
接入时要求:
- `CHROMA_ENABLED=true` 才初始化。
- embedding API 失败不能影响总结入库。
- embedding 维度和配置不匹配时记录日志并跳过。
- 使用当前 ChromaDB 官方 API 重新确认查询和过滤语法后实现。
---
## 4. 页面渲染服务
**职责**:从 SQLite 读取数据并渲染 Jinja2 模板。
kami 只作为风格参考:
- 参考纸张质感、留白、字体层级和墨蓝强调色。
- 不调用 kami,不依赖 kami 生成页面。
- CSS 放在 `app/static/css/style.css`,按本项目页面实际结构维护。
页面必须支持降级状态:
- 无总结:显示英文元数据和“AI 总结尚未生成”。
- 总结失败:显示错误类型和手动重跑入口。
- degraded/low:显示提示,但仍展示已有内容。
---
## 5. 用户数据服务
**职责**:本地个人化数据,无账号体系。
功能:
- 收藏/取消收藏。
- 阅读状态:`unread``skimmed``read_summary``read_full`
- 个人 Markdown 笔记。
- 阅读列表:按收藏、状态、标签、日期筛选。
所有用户数据跟随论文删除一起删除。
---
## 6. 清理和删除服务
**职责**:清理临时文件,并支持管理员手动删除指定日期范围内的数据。
### 临时文件清理
触发时机:
- 单篇总结成功后。
- 单篇总结失败后。
- 每日任务结束后兜底扫描 `data/tmp/`
### 手动删除
接口:
```python
async def delete_papers_by_date_range(
date_start: date,
date_end: date,
include_notes: bool = True,
) -> DeleteResult: ...
```
要求:
- 删除前统计目标论文数量。
- 删除 DB 记录、FTS 索引、本地文件。
- 删除失败时记录具体 arXiv ID 和错误。
- 日期范围必须有限制,避免误删全部数据;管理接口需要二次确认参数。
---
## 7. 调度服务
**职责**:自动执行每日抓取和总结。
### 约束
- 应用以单 worker 运行。
- `APP_WORKERS` 必须为 1,或 `SCHEDULER_ENABLED=false`
- 启动时检查运行中任务,避免重复执行。
- 同一日期同一任务使用数据库锁或日志状态防重入。
- 推荐使用 `task_locks` 表;抢锁失败时,自动任务跳过,管理接口返回 409。
### 每日流程
```text
08:00
-> 按 APP_TIMEZONE 计算 today
-> crawl(date=today)
-> summarize pending papers
-> cleanup tmp files
-> write logs
```
手动触发方式:
- CLI`python -m app.cli crawl --date YYYY-MM-DD`
- API`POST /admin/crawl`
---
## 8. 管理和安全服务
**职责**:保护所有有副作用的管理操作。
### 鉴权
管理接口必须要求 `ADMIN_TOKEN`
```text
Authorization: Bearer <ADMIN_TOKEN>
```
受保护接口:
- `POST /admin/crawl`
- `POST /admin/summarize/{arxiv_id}`
- `POST /admin/summarize`
- `POST /admin/cleanup`
- `POST /admin/delete`
- `GET /admin/logs`
如果 `ADMIN_TOKEN` 为空或为默认值 `change-me`,应用启动时应警告;如果 `APP_HOST` 不是 `127.0.0.1`,应拒绝启动或要求显式确认。
用户数据接口默认仅面向本地使用。如果 `APP_HOST=127.0.0.1`,收藏、阅读状态、笔记接口不额外要求 token;如果绑定到非本地地址,应启用 same-origin 检查或要求 `ADMIN_TOKEN`,避免内网其他人修改本地笔记。
---
## 9. RSS 服务
**职责**:输出最近论文的 RSS Feed。
MVP 只做 `/rss.xml`
- 默认最近 7 天。
- 支持 `?tag=RAG`
- 有中文标题则用中文标题,否则用英文标题。
- 详情链接指向本站 `/paper/{arxiv_id}`
Atom 和 JSON Feed 作为后续增强。
---
## 10. 后续增强服务
这些能力暂不进入 MVP
- LaTeX 图片提取。
- ChromaDB 语义搜索。
- 相似论文推荐。
- 趋势看板。
- 论文对比页。
实现前需要重新评估数据量、API 成本、页面复杂度和验收标准。