Files
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
2026-08-07 12:53:47 +08:00

1 line
33 KiB
JSON
Raw Permalink 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.
{"repo": "040822/arxiv", "files": {"AGENTS.md": {"9a6fa88937fddd2f1f13699a5b87b71cf86a0a5c": {"t": 1786068449.715782, "n": 23667, "h": "7fb22cbc96e8a049", "c": "# AGENTS.md — AI 论文数据库项目维护文档\n\n本文件供 AI Agent 阅读,用于理解项目结构、代码逻辑和开发规范,以便后续维护和开发新功能。\n\n> **📚 完整文档:** 详细的用户手册、开发者指南、API 文档等请参阅 [`docs/`](docs/) 目录。\n> - [docs/agent-guide.md](docs/agent-guide.md) — AI Agent 快速入门\n> - [docs/developer-guide.md](docs/developer-guide.md) — 开发者指南\n> - [docs/api-reference.md](docs/api-reference.md) — API 接口文档\n> - [docs/architecture.md](docs/architecture.md) — 项目架构说明\n\n---\n\n## 1. 项目概述\n\n自动从 arXiv 抓取 AI/机器人领域论文,调用 OpenAI 兼容 API 做基础分析(标签、AI 评级、中文摘要、简评)和按需深度阅读(Q&A),存入 SQLite 数据库,通过 Flask Web 界面浏览。论文评级由 AI 初评,用户可手动修正。用户还可以在单篇论文学习页中基于 PDF 全文进行自由讨论、主动问答练习和苏格拉底追问。\n\n**技术栈:** Python 3.10+ / Flask / SQLite / APScheduler / arxiv-py / OpenAI SDK / PyMuPDF\n\n**分支策略:**\n- `master` — 稳定版本\n- `dev` — 开发分支\n\n---\n\n## 2. 文件结构与职责\n\n```\narxiv/\n├── config.py # 硬编码配置(分类、标签候选、路径)\n├── settings.py # 运行时配置兼容 shim(实现位于 source/settings/)\n├── database.py # SQLite 操作兼容 shim(实现位于 source/storage/)\n├── fetcher.py # arXiv API 论文抓取(去重、按分类拉取)\n├── analyzer.py # AI 分析与论文学习逻辑(PDF全文、Q&A、对话、问答反馈)\n├── backup.py # WebDAV 云同步备份(SQLite 快照、zip 打包、上传/清理)\n├── email_report.py # 每日报告邮件发送(SMTP、邮件HTML包装、站内链接重写)\n├── pdf_reader.py # PDF 下载与文本提取(PyMuPDF,缓存到 data/pdf_cache/)\n├── app.py # Flask 启动兼容 shim(实现位于 source/web/)\n├── source/\n│ ├── settings/ # 值转换、默认值、归一化、思考协议、store、供应商、Prompt、运行时配置\n│ ├── imports/ # 手动论文链接/PDF 元数据预览解析与安全 URL 校验\n│ ├── storage/ # 托管连接、顺序迁移/快照、论文、分析、日志、报告、学习记录\n│ ├── reports/ # Web 日报 HTML 渲染\n│ ├── pipeline/ # 定时/手动组合流水线与 APScheduler\n│ └── web/ # Flask application assembly、Blueprint 路由(含 import_api)、鉴权与进度\n├── main.py # CLI 入口(fetch/analyze/run)\n├── requirements.txt # Python 依赖\n├── README.md # 用户文档\n├── AGENTS.md # 本文件(AI维护文档)\n├── templates/ # Jinja2 HTML 模板\n│ ├── index.html # 首页(论文列表、操作面板)\n│ ├── browse.html # 分类浏览(多条件筛选)\n│ ├── search.html # 搜索页\n│ ├── paper.html # 论文详情(含编辑功能)\n│ ├── paper_chat.html # 论文学习页(对话、问答、苏格拉底追问)\n│ ├── about.html # 公开项目宣传页(研究闭环、优势、竞品定位)\n│ ├── vision.html # 实验室愿景页(科研价值、竞品格局、发展路线)\n│ ├── settings.html # 设置页(含独立的定时任务、邮件和执行日志管理)\n│ └── tasks.html # 论文处理页(抓取、分析、报告、两步手动导入)\n├── static/style.css # 全局样式\n├── static/promo.css # 宣传页独立样式(公开版 + 深色愿景版)\n├── data/ # 运行时数据(不提交到git)\n│ ├── papers.db # SQLite 数据库\n│ ├── settings.json # 运行时配置\n│ ├── pdf_cache/ # 可重建 PDF 缓存\n│ └── paper_files/ # 手动上传的持久 PDF(不随缓存清理)\n```\n\n---\n\n## 3. 数据库结构\n\n### papers 表\n```sql\nCREATE TABLE papers (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n paper_key TEXT UNIQUE NOT NULL, -- 所有来源通用的内部路由标识\n arxiv_id TEXT UNIQUE, -- 仅 arXiv 论文有值\n source_type TEXT NOT NULL, -- arxiv/openreview/doi/web/upload\n source_id TEXT, -- 来源稳定标识;与 source_type 联合唯一\n ingest_mode TEXT NOT NULL, -- feed/manual;定时任务只处理 feed\n title TEXT NOT NULL,\n authors TEXT NOT NULL, -- JSON 数组\n abstract TEXT NOT NULL,\n categories TEXT NOT NULL, -- JSON 数组\n primary_category TEXT,\n url TEXT, -- 来源页面链接\n pdf_url TEXT, -- 远程 PDF 链接\n venue TEXT,\n published_date TEXT,\n updated_date TEXT,\n pdf_local_path TEXT, -- data/ 下的持久 PDF 相对路径\n pdf_sha256 TEXT, -- 非空时唯一\n pdf_size_bytes INTEGER,\n hidden INTEGER DEFAULT 0, -- 1=隐藏\n created_at TEXT DEFAULT CURRENT_TIMESTAMP\n);\n```\n\n### analysis 表\n```sql\nCREATE TABLE analysis (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n paper_id INTEGER NOT NULL, -- FK -> papers.id;uq_analysis_paper_id 保证每篇论文唯一\n tags TEXT, -- JSON 数组,如 [\"VLA\",\"World Model\"]\n summary_cn TEXT, -- Abstract 中文翻译\n summary_en TEXT, -- 英文摘要(当前未使用)\n rating INTEGER DEFAULT 0, -- AI 初评 + 用户可手动修正(0-5 星)\n legacy_ai_rating INTEGER, -- 历史 AI 自动评级备份\n rating_restored_from_legacy INTEGER DEFAULT 0, -- 是否已从历史 AI 评级恢复\n value_comment TEXT, -- 评价\n qa_analysis TEXT, -- Q&A 深度阅读(Markdown 格式)\n recommendation_score INTEGER, -- 个性化推荐分(0-100)\n recommendation_reason TEXT, -- 推荐理由\n recommendation_interest_hash TEXT, -- 对应研究兴趣哈希\n recommendation_analyzed_at TEXT, -- 推荐评分时间\n analyzed_at TEXT DEFAULT CURRENT_TIMESTAMP\n);\n```\n\n### schema_migrations 表\n```sql\nCREATE TABLE schema_migrations (\n version INTEGER PRIMARY KEY, -- 连续迁移版本\n name TEXT NOT NULL UNIQUE, -- 稳定迁移名称\n applied_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP\n);\n```\n\n### task_logs 表\n```sql\nCREATE TABLE task_logs (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n task_name TEXT NOT NULL, -- daily_pipeline/fetch/analyze/generate/run/webdav_backup/email_report\n status TEXT NOT NULL DEFAULT 'running', -- running/success/warning/error/skipped/interrupted\n message TEXT,\n detail TEXT,\n started_at TEXT DEFAULT CURRENT_TIMESTAMP,\n finished_at TEXT,\n duration_sec REAL\n);\n```\n\n### task_log_steps 表\n```sql\nCREATE TABLE task_log_steps (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n task_log_id INTEGER NOT NULL, -- FK -> task_logs.id (CASCADE DELETE)\n step_key TEXT NOT NULL, -- fetch/analyze/recommend/report/email/backup\n step_name TEXT NOT NULL,\n position INTEGER NOT NULL,\n status TEXT DEFAULT 'pending', -- pending/running/success/warning/error/skipped/interrupted\n message TEXT,\n detail TEXT,\n started_at TEXT,\n finished_at TEXT,\n duration_sec REAL,\n UNIQUE(task_log_id, step_key)\n);\n```\n\n### 论文学习表\n```sql\nCREATE TABLE paper_chat_messages (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n paper_id INTEGER NOT NULL, -- FK -> papers.id (CASCADE DELETE)\n role TEXT NOT NULL, -- user/assistant\n content TEXT NOT NULL,\n created_at TEXT DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE TABLE paper_quiz_sessions (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n paper_id INTEGER NOT NULL, -- FK -> papers.id (CASCADE DELETE)\n mode TEXT NOT NULL, -- quick3/standard6/socratic\n status TEXT DEFAULT 'active',\n created_at TEXT DEFAULT CURRENT_TIMESTAMP,\n updated_at TEXT DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE TABLE paper_quiz_questions (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n session_id INTEGER NOT NULL, -- FK -> paper_quiz_sessions.id (CASCADE DELETE)\n position INTEGER NOT NULL,\n question TEXT NOT NULL,\n expected_points TEXT, -- JSON 数组或 socratic 标记\n created_at TEXT DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE TABLE paper_quiz_attempts (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n question_id INTEGER NOT NULL, -- FK -> paper_quiz_questions.id (CASCADE DELETE)\n answer_text TEXT NOT NULL,\n score INTEGER DEFAULT 0, -- 0-5\n feedback_json TEXT, -- correct/missing/misconception/improved_answer\n created_at TEXT DEFAULT CURRENT_TIMESTAMP\n);\n```\n\n删除论文时,以上学习记录会随 papers 外键级联删除。\n\n---\n\n## 4. 核心数据流\n\n### 4.1 论文抓取流程\n```\nfetcher.fetch_latest_papers()\n → 遍历 ARXIV_CATEGORIES\n → arxiv.Client 按 submittedDate 降序拉取\n → 去重:seen_ids 内存去重 + paper_exists() 数据库去重\n → insert_paper() 写入 papers 表\n → 返回新增论文列表\n```\n\n### 4.2 AI 分析流程\n```\nanalyzer.analyze_pending_papers(limit, concurrency)\n → get_unanalyzed_papers() 获取未分析论文\n → ThreadPoolExecutor 并发执行 analyze_paper()\n → get_ai_task_config(\"basic_analysis\") 获取基础分析模型与参数\n → get_prompt_profile(\"basic_analysis\") 获取稳定 Prompt 前缀\n → 论文标题/作者/摘要作为独立 JSON message 放在最后\n → OpenAI chat.completions.create()\n → 解析 JSON 响应:{tags, summary_cn, value_comment}\n → insert_analysis() 写入 analysis 表(含重复检查)\n\nanalyzer.analyze_paper_full(paper_data)\n → get_ai_task_config(\"deep_reading\") 获取深度阅读模型与参数\n → get_paper_full_text(max_chars=None) 下载 PDF 并提取全文(不截断)\n → system + 动态论文全文 JSON message + 稳定 instruction(长输入任务指令后置,见 7.4)\n → OpenAI chat.completions.create()\n → 解析 JSON 响应:{qa_analysis}\n → 按当前 Prompt 中的 Q 编号校验完整性;截断或缺题时最多自动续写一次\n → 补全后仍不完整则返回 warning,保留数据库中已有 qa_analysis\n → update_analysis() 仅写入 qa_analysis,不覆盖基础分析字段\n```\n\n### 4.3 论文学习流程\n```\nGET /paper/<arxiv_id>/chat\n → 展示自由讨论、3/6题练习、苏格拉底追问\n\nPOST /api/paper/<arxiv_id>/chat/messages\n → get_paper_chat_messages(limit=12) 读取最近历史\n → chat_about_paper()\n → get_learning_paper_text()\n → 优先检查 data/pdf_cache/<arxiv_id>.pdf\n → 未命中才调用 download_pdf()\n → PDF 下载/提取失败回退 abstract\n → build_paper_learning_messages()\n → system → 稳定任务说明 → 稳定论文上下文 → 历史/当前问题\n → paper_chat 任务模型\n → 保存 user/assistant 消息\n\nPOST /api/paper/<arxiv_id>/quiz/sessions\n → paper_quiz 任务模型生成 quick3/standard6 题目\n → 保存 session/questions\n\nPOST /api/paper/<arxiv_id>/quiz/questions/<question_id>/answer\n → paper_quiz 任务模型评分并返回 correct/missing/misconceptions/improved_answer\n → 保存 answer/feedback\n\nPOST /api/paper/<arxiv_id>/socratic/sessions\nPOST /api/paper/<arxiv_id>/socratic/sessions/<session_id>/reply\n → paper_quiz 任务模型根据历史连续追问\n```\n\n### 4.4 定时任务流程\n```\nAPScheduler cron(day_of_week, hour, minute)\n → daily_pipeline()\n → 初始化 task_logs + 六条 task_log_steps\n → fetch_latest_papers(days=schedule.fetch_days)\n # 抓取阶段异常时按 schedule.fetch_retry_interval_minutes 等待重试,\n # 最多 schedule.fetch_max_retries 次;仅作用于定时日报\n → analyze_pending_papers(limit=schedule.analyze_limit)\n → 按研究兴趣补齐推荐评分(未设置时 skipped)\n → generate_report_content(latest_date) + save_report()\n # Web 日报包含截至报告日最近 7 个有论文日期的标签走势、新标签和推荐分分布\n → send_report_email() # 未启用/已发送时 skipped;失败记 warning\n → run_webdav_backup() # 未启用时 skipped;失败记 warning\n → finish_task_log(success/warning/error)\n```\n\n应用启动时会将上一次进程遗留的 `running` 日志改为 `interrupted`。定时日报和 `/api/run` 共享非阻塞互斥锁;自动冲突记为 `skipped`,手动冲突返回 HTTP 409。\n\n---\n\n## 5. 配置系统\n\n### 5.1 config.py(硬编码,需改代码)\n- `ARXIV_CATEGORIES` — 监控的 arXiv 分类\n- `TAG_CANDIDATES` — AI 标签候选列表\n- `RATING_CRITERIA` — AI 基础分析评级标准(0-5 星校准锚点)\n- `ANALYSIS_CONCURRENCY` — 默认并发数\n- `SCHEDULE_HOUR/MINUTE` — 定时任务首次默认时间;运行后以 `settings.json` 的 `schedule` 为准\n- `WEB_HOST/PORT` — Web 服务地址\n\n### 5.2 data/settings.json(运行时,Web界面可改)\n```json\n{\n \"settings_schema_version\": 3,\n \"concurrency\": 5,\n \"admin_password\": \"sha256...\",\n \"session_secret\": \"随机生成的 Flask session 签名密钥\",\n \"personalization\": {\"research_interests\": \"用户研究兴趣\"},\n \"webdav_backup\": {\n \"enabled\": false,\n \"url\": \"https://example.com/remote.php/dav/files/user\",\n \"username\": \"webdav用户名\",\n \"password\": \"webdav密码或应用密码\",\n \"remote_dir\": \"arxiv-backups\",\n \"history_days\": 3\n },\n \"email_report\": {\n \"enabled\": false,\n \"smtp_host\": \"smtp.example.com\",\n \"smtp_port\": 587,\n \"security\": \"starttls\",\n \"username\": \"user@example.com\",\n \"password\": \"SMTP密码或授权码\",\n \"sender\": \"user@example.com\",\n \"recipients\": [\"reader@example.com\"],\n \"subject_template\": \"AI 论文日报 {date} - {paper_count} 篇论文\",\n \"site_url\": \"https://your-domain.example\",\n \"important_score_threshold\": 80,\n \"overview_limit\": 20\n },\n \"schedule\": {\n \"enabled\": true,\n \"days_of_week\": [\"mon\", \"tue\", \"wed\", \"thu\", \"fri\", \"sat\", \"sun\"],\n \"hour\": 10,\n \"minute\": 0,\n \"fetch_days\": 3,\n \"analyze_limit\": 1000,\n \"fetch_retry_interval_minutes\": 10,\n \"fetch_max_retries\": 20\n },\n \"providers\": {\n \"deepseek\": {\n \"name\": \"DeepSeek\",\n \"api_key\": \"sk-xxx\",\n \"base_url\": \"https://api.deepseek.com\",\n \"available_models\": [\"deepseek-chat\", \"deepseek-reasoner\"]\n }\n },\n \"prompts\": {\n \"system_prompt\": \"...\",\n \"user_prompt\": \"...{title}...{authors}...{abstract}...{tag_candidates}...\"\n },\n \"prompt_profiles\": {\n \"paper_import\": {\"system\": \"...\", \"instruction\": \"...从 PDF 提取可编辑元数据...\"},\n \"basic_analysis\": {\"system\": \"...\", \"instruction\": \"...{tag_candidates}...\"},\n \"deep_reading\": {\"system\": \"...\", \"instruction\": \"...\"},\n \"report_summary\": {\"system\": \"...\", \"instruction\": \"...\"},\n \"recommendation\": {\"system\": \"...\", \"instruction\": \"...返回 recommendation_score/recommendation_reason...\"},\n \"paper_chat\": {\"system\": \"...\", \"instruction\": \"...多轮讨论...\"},\n \"paper_quiz\": {\"system\": \"...\", \"instruction\": \"...主动回忆/评分/追问...\"}\n },\n \"ai_tasks\": {\n \"basic_analysis\": {\"provider_key\": \"deepseek\", \"model\": \"deepseek-chat\", \"temperature_enabled\": true, \"temperature\": 0.2, \"is_thinking\": false, \"max_tokens_enabled\": true, \"max_tokens\": 1200},\n \"paper_import\": {\"provider_key\": \"deepseek\", \"model\": \"deepseek-chat\", \"temperature_enabled\": true, \"temperature\": 0.1, \"is_thinking\": false, \"max_tokens_enabled\": true, \"max_tokens\": 1000},\n \"deep_reading\": {\"provider_key\": \"deepseek\", \"model\": \"deepseek-reasoner\", \"temperature_enabled\": false, \"temperature\": 0.2, \"is_thinking\": true, \"thinking_effort\": \"high\", \"max_tokens_enabled\": true, \"max_tokens\": 6000},\n \"report_summary\": {\"provider_key\": \"deepseek\", \"model\": \"deepseek-chat\", \"temperature_enabled\": true, \"temperature\": 0.3, \"is_thinking\": false, \"max_tokens_enabled\": true, \"max_tokens\": 1000},\n \"recommendation\": {\"provider_key\": \"deepseek\", \"model\": \"deepseek-chat\", \"temperature_enabled\": true, \"temperature\": 0.2, \"is_thinking\": false, \"max_tokens_enabled\": true, \"max_tokens\": 500},\n \"paper_chat\": {\"provider_key\": \"deepseek\", \"model\": \"deepseek-reasoner\", \"temperature_enabled\": false, \"temperature\": 0.4, \"is_thinking\": true, \"thinking_effort\": \"high\", \"max_tokens_enabled\": true, \"max_tokens\": 4000},\n \"paper_quiz\": {\"provider_key\": \"deepseek\", \"model\": \"deepseek-reasoner\", \"temperature_enabled\": false, \"temperature\": 0.3, \"is_thinking\": true, \"thinking_effort\": \"high\", \"max_tokens_enabled\": true, \"max_tokens\": 3000}\n }\n}\n```\n\n**source/settings 公共函数(根 settings.py 兼容导出):**\n- `load_settings()` / `save_settings()` — 读写JSON(含自动迁移)\n- `get_ai_config()` — 兼容接口,返回 `basic_analysis` 功能路由的实际调用配置\n- `get_ai_task_config(task_key)` — 获取某个 AI 功能的实际供应商、模型和参数配置\n- `resolve_ai_task_config(task_key, task_config)` — 将已保存或未保存的功能路由草稿与供应商连接凭据合并并校验\n- `get_ai_tasks()` / `save_ai_tasks()` — 获取/保存 PDF 元数据提取、基础分析、深度阅读、报告导读、个性化推荐、论文对话、论文问答练习的模型路由\n- `build_chat_completion_kwargs()` — 统一构建 Chat Completions 参数;功能路由只支持可选 Temperature 和输出长度,未启用 Temperature 或使用思考模型时不发送 Temperature,其他采样参数不发送并交给模型采用默认行为\n- LLM 客户端必须通过 `analyzer.get_openai_client()` 创建,以复用全局代理配置并禁用环境变量代理\n- `normalize_provider_connection()` — 归一化供应商连接字段;`normalize_provider_config()` 仅用于合并后的实际调用配置\n- `get_prompt_profile()` / `get_prompt_profiles()` — 获取任务级 Prompt Profile;`get_prompts()` 保留旧接口兼容\n- `get_concurrency()` — 获取并发数\n- `get_per_page()` — 获取每页论文数\n- `get_schedule_config()` / `save_schedule_config()` — 获取/保存内置日报的星期、时间、回看天数、分析上限和抓取失败重试策略\n- `get_fetch_config()` / `save_fetch_config()` — 抓取配置(请求间隔、批次天数、批次间隔)\n- `get_proxy_config()` / `save_proxy_config()` — 代理配置\n- `get_personalization_config()` / `save_personalization_config()` — 个性化推荐研究兴趣\n- `get_webdav_backup_config()` / `save_webdav_backup_config()` — WebDAV 云备份配置;GET 给前端时必须脱敏密码\n- `get_email_report_config()` / `save_email_report_config()` / `update_email_report_status()` — 每日报告邮件配置,含 `important_score_threshold`(重点精读推荐分阈值,默认 80,0-100)与 `overview_limit`(速览上限,默认 20,0-50);GET 给前端时必须脱敏 SMTP 密码;`last_sent_report_date` 只记录自动任务成功发送的日报日期\n- `add/remove/update_provider()` — 供应商连接 CRUD;被功能路由引用时禁止删除\n- `get/set/verify/has_admin_password()` — 管理密码\n- `get_session_secret()` — 获取/生成持久 Flask session 签名密钥\n\n> **配置合并:** `source/settings/store.py` 使用递归 deep merge;新增普通顶层字段无需维护白名单。需要归一化、迁移或秘密保留语义的字段,仍应在 normalize/store 中显式处理并补回归测试。\n\n---\n\n## 6. API 端点清单\n\n### 页面路由\n| 路由 | 说明 |\n|------|------|\n| `GET /` | 首页(论文列表) |\n| `GET /browse` | 分类浏览(多条件筛选) |\n| `GET /search?q=` | 搜索 |\n| `GET /paper/<arxiv_id>` | 论文详情(含编辑) |\n| `GET /paper/<arxiv_id>/chat` | 论文学习页(对话/问答/苏格拉底追问) |\n| `GET /about` | 公开项目宣传页(首页提供入口) |\n| `GET /vision` | 实验室科研情报基础设施愿景页(仅直接访问) |\n| `GET /settings` | 设置页(含独立定时任务标签) |\n| `GET /tasks` | 论文处理页 |\n\n### 任务 API\n| 端点 | 方法 | 说明 |\n|------|------|------|\n| `/api/fetch` | POST | 抓取论文 |\n| `/api/analyze` | POST | AI分析(?limit=50) |\n| `/api/generate` | POST | 生成报告 |\n| `/api/run` | POST | 抓取、分析、推荐并生成报告;流水线冲突返回 409 |\n\n### 论文 API\n| 端点 | 方法 | 说明 |\n|------|------|------|\n| `/api/papers` | GET | 论文列表JSON |\n| `/api/tags` | GET | 标签列表 |\n| `/api/stats` | GET | 统计信息 |\n| `/api/paper/<id>/analysis` | PUT | 更新论文分析 |\n| `/api/paper/<id>/hide` | POST | 隐藏论文 |\n| `/api/paper/import/preview` | POST | 解析论文链接或上传 PDF,返回可编辑元数据,不入库 |\n| `/api/paper/import` | POST | 确认手动导入,可选基础分析和深度阅读 |\n| `/api/paper/<id>/pdf` | POST | 为已有论文上传或替换本地 PDF |\n| `/api/paper/<id>/unhide` | POST | 取消隐藏 |\n| `/api/paper/<id>` | DELETE | 删除论文 |\n| `/api/paper/<id>/reanalyze` | POST | 重新AI分析 |\n| `/api/paper/<id>/todo/status` | GET | 检查阅读清单状态 |\n\n### 论文学习 API\n| 端点 | 方法 | 说明 |\n|------|------|------|\n| `/api/paper/<id>/chat/messages` | GET | 读取自由讨论历史 |\n| `/api/paper/<id>/chat/messages` | POST | 发送讨论消息并保存模型回复 |\n| `/api/paper/<id>/quiz/sessions` | POST | 创建 quick3/standard6 练习并生成题目 |\n| `/api/paper/<id>/quiz/sessions/<session_id>` | GET | 读取练习题、答案和反馈 |\n| `/api/paper/<id>/quiz/questions/<question_id>/answer` | POST | 提交单题答案并返回评分反馈 |\n| `/api/paper/<id>/socratic/sessions` | POST | 创建独立苏格拉底追问会话 |\n| `/api/paper/<id>/socratic/sessions/<session_id>/reply` | POST | 提交回答并返回反馈和下一问 |\n\n### 设置 API\n| 端点 | 方法 | 说明 |\n|------|------|------|\n| `/login` | GET | 管理登录页 |\n| `/api/auth/status` | GET | 当前认证状态 |\n| `/api/auth/login` | POST | 管理密码登录 |\n| `/api/auth/logout` | POST | 退出登录 |\n| `/api/providers` | GET/POST | 供应商列表/添加 |\n| `/api/providers/<key>` | PUT/DELETE | 更新/删除供应商 |\n| `/api/providers/presets` | GET | 预设供应商列表 |\n| `/api/providers/models` | POST | 从供应商 API 自动获取模型列表 |\n| `/api/prompts` | GET/POST | 读取/保存Prompt |\n| `/api/settings/ai-tasks` | GET/POST | 读取/保存 AI 功能模型路由 |\n| `/api/settings/ai-tasks/<task_key>/test` | POST | 使用未保存草稿测试指定功能路由,并返回思考能力提示 |\n| `/api/settings/ai-usage` | GET | 查看近期 LLM token 用量 |\n| `/api/settings/personalization` | GET/POST | 读取/保存研究兴趣 |\n| `/api/recommendations/recalculate` | POST | 手动重算个性化推荐评分 |\n| `/api/settings/webdav-backup` | GET/POST | 读取/保存 WebDAV 云备份配置(GET 不返回明文密码) |\n| `/api/backup/webdav/run` | POST | 手动立即执行 WebDAV 备份 |\n| `/api/settings/email-report` | GET/POST | 读取/保存每日报告邮件配置(GET 不返回明文密码) |\n| `/api/email-report/test` | POST | 使用最近一份日报告测试发送邮件 |\n| `/api/settings/concurrency` | POST | 保存并发数 |\n| `/api/settings/schedule` | GET/POST | 读取/保存内置日报的星期、时间、抓取天数、分析上限和抓取失败重试策略 |\n| `/api/db/info` | GET | 数据库信息 |\n| `/api/admin/password` | POST/DELETE | 设置/清除密码 |\n\n### 任务日志 API\n| 端点 | 方法 | 说明 |\n|------|------|------|\n| `/api/tasks/stats` | GET | 任务统计 |\n| `/api/tasks/logs` | GET | 日志列表(?task=&page=),日报日志附带 `steps` |\n| `/api/tasks/scheduled` | GET | 内置日报配置、时区、下次执行与最近运行 |\n| `/api/tasks/clear` | POST | 清理旧日志(?keep_days=30) |\n\n---\n\n## 7. 开发规范\n\n### 7.1 添加新功能的步骤\n1. 如果涉及新数据库表/字段 → 在 `source/storage/migrations.py` 注册下一个连续版本的迁移函数\n2. 如果涉及新 API → 在对应的 `source/web/*_api.py` Blueprint 添加路由函数\n3. 如果涉及新页面 → 创建 `templates/xxx.html`,在 `source/web/pages.py` 添加页面路由\n4. 如果涉及新样式 → 通用业务页面在 `static/style.css` 添加;独立宣传页使用 `static/promo.css` 和 `.promo-*` 命名空间\n5. 更新 `AGENTS.md` 记录变更\n\n### 7.2 数据库迁移模式\n`init_db()` 只负责启动顺序迁移器。新增 schema 变更时,在\n`source/storage/migrations.py` 的 `MIGRATIONS` 末尾注册连续版本,并把实际\nDDL/数据整理放入独立迁移函数。每个版本由迁移器在单独事务中执行并写入\n`schema_migrations`;变更前会创建 SQLite 一致性快照,失败时回滚并中止启动。\n\n所有业务数据库访问都使用 `with get_connection() as conn:`。该兼容式托管连接\n退出时会提交或回滚并关闭,且统一启用 WAL、外键和 5000 ms `busy_timeout`。\n\n### 7.3 添加新供应商\n在 `source/settings/defaults.py` 的 `PROVIDER_PRESETS` 字典中添加:\n```python\n\"new_provider\": {\n \"name\": \"显示名称\",\n \"base_url\": \"https://api.example.com/v1\",\n \"models\": [\"model-1\", \"model-2\"],\n}\n```\n\n供应商运行时配置只保存 `name`、`api_key`、`base_url` 和 `available_models`。模型、输出长度、Temperature 采样控制以及思考模式全部保存在 `ai_tasks` 的具体功能路由中;未启用 Temperature 时不发送该参数,其他采样参数不发送并采用模型默认行为。旧版供应商推理字段会在读取时迁移到显式任务路由。\n\n调用模型时必须通过 `build_chat_completion_kwargs()` 构建参数,不要在业务代码中直接固定传 `temperature` 或 `max_tokens`;保持可选参数的省略语义,让模型在未配置采样控制时使用自身默认值。\n\n管理密码设置后,`/settings`、`/tasks`、写接口和敏感设置读取接口都需要登录;阅读清单加入/移除接口例外,公开可用。登录状态通过签名 cookie 持久保存 180 天,默认使用 `settings.json` 中的 `session_secret` 保证服务重启后仍有效;如果设置了 `FLASK_SECRET_KEY` 则优先使用环境变量。修改管理密码会使旧登录状态失效。`GET /api/providers` 只能返回 `api_key_masked`,不能返回完整 `api_key`。\n\n### 7.4 修改 Prompt\n- 新版 prompt 主要存储在 `data/settings.json` 的 `prompt_profiles` 字段,按 `paper_import`、`basic_analysis`、`deep_reading`、`report_summary`、`recommendation`、`paper_chat`、`paper_quiz` 拆分\n- 旧版 `prompts.system_prompt/user_prompt` 保留为兼容字段,并映射到 `deep_reading`\n- 基础分析 Prompt Profile 可使用 `{tag_candidates}`、`{rating_criteria}`,并返回 `tags`、`rating`、`summary_cn`、`value_comment`;深度阅读只描述 Q&A 输出;论文标题、作者、摘要、PDF 全文由后端作为独立 JSON message 传入\n- 修改 AI 调用逻辑时不要重新把动态论文内容拼回稳定 instruction,否则会降低 prompt cache 命中率\n- 长输入任务(`deep_reading`、`paper_import`,输入含数万 token PDF 全文)的消息顺序固定为 `system → 动态论文全文 JSON message → 稳定 instruction`:指令放在长文本之后(输入末尾)可避免被上下文“淹没”导致 flash 类模型漏答;短输入任务(基础分析、推荐、报告导读)保持 `system → instruction → 动态数据` 以保留更长的稳定前缀缓存。两种顺序都由 `_build_task_messages()` 统一构建,不要手工拼消息\n- 深度阅读按质量优先读取完整 PDF 全文;基础分析通常只使用摘要,无摘要的手动论文可回退到 PDF 前 50000 字符\n- 深度阅读必须校验当前 Prompt 声明的所有 `### Qn:`;自动补全最多调用一次,补全后仍不完整时禁止覆盖已有 `qa_analysis`\n- 论文详情页和学习页的模型富文本统一通过 `static/rich_text.js` 的 `RichText.render()` / `RichText.renderMath()` 渲染,不要在模板中复制 Markdown 或清洗逻辑\n- 论文学习功能必须通过 `build_paper_learning_messages()` 构造消息,保持 `system → 稳定任务说明 → 稳定论文上下文 → 动态历史/用户输入` 的顺序;不要把时间戳、session id、当前问题等易变内容放进稳定论文上下文\n- 论文学习 PDF 文本必须通过 `get_learning_paper_text()` 获取,优先复用 `data/pdf_cache/<arxiv_id>.pdf`,未命中才下载,失败时回退摘要\n\n### 7.5 添加新标签\n在 `config.py` 的 `TAG_CANDIDATES` 列表中添加。注意:\n- 避免过于宽泛的标签(如 \"Transformer\"、\"LLM\")\n- 优先使用具体的技术方法名称\n\n### 7.6 arXiv API 注意事项\n- **submittedDate 过滤器不工作**:arXiv API 的 `submittedDate:[... TO ...]` 查询语法实际不返回结果,已踩坑\n- **正确做法**:使用 `cat:xxx` 查询 + `sortBy=submittedDate&sortOrder=descending` 排序,然后在代码中按 `published` 日期过滤\n- **分类查询**:`cat:cs.RO` 匹配主分类为 cs.RO 的论文,比 `primary_category:cs.RO` 更可靠\n- **分批抓取**:大批量抓取时使用 `fetch_batch()` 自动分批,避免单次请求过大\n- **时区问题**:arXiv 返回的 `published` 是带 UTC 时区的 datetime,比较时必须使用 `datetime.now(timezone.utc)`,否则报 `can't compare offset-naive and offset-aware datetimes`\n\n### 7.7 CSS 样式约定\n- 组件样式使用 kebab-case:`.paper-card`、`.qa-item`\n- 状态样式使用前缀:`.log-success`、`.log-warning`、`.log-error`、`.log-skipped`、`.log-interrupted`、`.log-running`\n- 响应式断点:`@media (max-width: 768px)`\n\n---\n\n## 8. 常见运维操作\n\n```bash\n# 启动服务\npython app.py\n\n# CLI 执行抓取、分析和推荐评分\npython main.py\n\n# 仅抓取\npython main.py fetch\n\n# 仅分析\npython main.py analyze\n\n# 查看数据库状态\npython -c \"from database import *; init_db(); print(get_paper_count(), 'papers,', get_analyzed_count(), 'analyzed')\"\n\n# 备份数据库\ncp data/papers.db data/papers.db.bak\n\n# WebDAV 云备份\n# 设置页「数据库 → WebDAV 云同步备份」可启用每日自动同步。\n# 备份包包含 papers.db 一致性快照、data/settings.json 和 manifest;\n# Web 日报位于 reports 表中,会随数据库快照备份。\n# 会包含 API Key、管理密码哈希和 session secret 等敏感配置。\n# 远端文件:arxiv-backup-latest.zip + arxiv-backup-YYYYMMDD-HHMMSS.zip,\n# 历史备份默认保留 3 天,可在设置页修改。\n\n# 报告邮件发送\n# 设置页「定时任务 → 报告邮件」可配置 SMTP、收件人、主题模板和站点地址。\n# 启用后每日定时任务会在报告生成并保存后发送邮件专用摘要版 HTML:\n# report_summary 导读、推荐分 > important_score_threshold(默认 80)重点精读、最多 overview_limit(默认 20)篇快速速览。\n# 自动发送前会检查 last_sent_report_date,同一日报成功发送后不再重复发送;\n# 手动测试发送可重复执行,但不会更新自动任务的去重日期。\n# 自动流程中的发送失败写入日报步骤并使父任务标记 warning,不中断后续备份;\n# 手动测试发送仍记录独立 email_report 日志。\n# SMTP 连接复用现有网络代理配置,代理启用时通过 HTTP CONNECT 连接 SMTP 服务器,\n# 不新增邮件专用代理配置。\n```\n\n---\n\n## 9. 已知限制与改进方向\n\n### 当前限制\n- PDF 提取依赖 PyMuPDF,扫描版 PDF 无法提取文本\n- arXiv API 有速率限制,大量抓取时需增加 delay_seconds\n- SQLite 仍适合单机中低并发;WAL 与 5000 ms `busy_timeout` 可缓解短时写锁竞争,但不替代分布式数据库\n- 无多用户隔离系统,管理密码只提供本地单用户访问保护\n\n### 可扩展方向\n- 添加更多 arXiv 分类到 `config.py` 的 `ARXIV_CATEGORIES`\n- 实现论文版本更新检测(v2/v3)\n- 添加 Webhook 推送每日报告\n- 实现向量语义搜索(embedding + cosine similarity)\n- 添加论文收藏/标注功能\n- 添加多用户系统\n- 用 Celery 替代 APScheduler 实现分布式任务\n\n---\n\n## 10. 依赖列表\n\n```\narxiv>=2.1.0 # arXiv API 客户端\nopenai>=1.0.0 # OpenAI 兼容 API SDK\nflask>=3.0.0 # Web 框架\napscheduler>=3.10.0 # 定时任务调度\nrequests>=2.31.0 # HTTP 客户端(PDF下载)\nPyMuPDF>=1.24.0 # PDF 文本提取\n```\n"}}}}