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.
1 line
33 KiB
JSON
1 line
33 KiB
JSON
{"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"}}}} |