From 38ed689d3e88bf2ad20b12aacdf2c377395b5010 Mon Sep 17 00:00:00 2001 From: Color2333 <1552429809@qq.com> Date: Thu, 19 Mar 2026 23:53:37 +0800 Subject: [PATCH 1/2] =?UTF-8?q?docs:=20=E6=9B=B4=E6=96=B0=20README=20-=20v?= =?UTF-8?q?3.2=20=E5=8A=9F=E8=83=BD=E6=B8=85=E5=8D=95=20+=20=E6=80=A7?= =?UTF-8?q?=E8=83=BD=E4=BC=98=E5=8C=96=20+=20=E7=8E=AF=E5=A2=83=E5=8F=98?= =?UTF-8?q?=E9=87=8F=20+=20API=20=E9=80=9F=E8=A7=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 264 +++++++++++++++++++++--------------------------------- 1 file changed, 103 insertions(+), 161 deletions(-) diff --git a/README.md b/README.md index 614d931..99498f3 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,9 @@ -
- -
- -PaperMind - -

+# PaperMind **AI 驱动的学术论文研究工作流平台** *从「搜索论文」进化为「理解领域」* -
- [![Python](https://img.shields.io/badge/Python-3.11+-3776AB?style=flat-square&logo=python&logoColor=white)](https://python.org) [![FastAPI](https://img.shields.io/badge/FastAPI-009688?style=flat-square&logo=fastapi&logoColor=white)](https://fastapi.tiangolo.com) [![React](https://img.shields.io/badge/React_18-61DAFB?style=flat-square&logo=react&logoColor=black)](https://react.dev) @@ -19,17 +11,9 @@ [![Tailwind](https://img.shields.io/badge/Tailwind_CSS_v4-06B6D4?style=flat-square&logo=tailwindcss&logoColor=white)](https://tailwindcss.com) [![SQLite](https://img.shields.io/badge/SQLite-003B57?style=flat-square&logo=sqlite&logoColor=white)](https://sqlite.org) [![License](https://img.shields.io/badge/License-MIT-green?style=flat-square)](LICENSE) -[![Version](https://img.shields.io/badge/Version-v3.1-667eea?style=flat-square)](CHANGELOG) - -
- -LLM Support +[![LLM](https://img.shields.io/badge/LLM-OpenAI_%7C_Anthropic_%7C_ZhipuAI-blueviolet?style=flat-square)]() -

- -> 🚀 **让 AI 成为你的研究助理** —— 自动追踪、智能分析、知识图谱、学术写作,一站式搞定! - -
+> 让 AI 成为你的研究助理 —— 自动追踪、智能分析、知识图谱、学术写作,一站式搞定! --- @@ -43,7 +27,7 @@ git clone https://github.com/Color2333/PaperMind.git && cd PaperMind # 2️⃣ 配置环境变量 cp .env.example .env -vim .env # 编辑配置,至少填写 LLM API Key +vim .env # 至少填写 LLM API Key # 3️⃣ 一键部署 docker compose up -d --build @@ -62,13 +46,12 @@ git clone https://github.com/Color2333/PaperMind.git && cd PaperMind # 2️⃣ 一键初始化(推荐) python scripts/dev_setup.py -# 脚本会自动:检查Python版本 → 创建虚拟环境 → 安装依赖 → 复制配置 → 初始化数据库 # 或手动初始化: python -m venv .venv && source .venv/bin/activate pip install -e ".[llm,pdf]" cp .env.example .env -vim .env # 编辑 .env 填入 LLM API Key +vim .env # 填入 LLM API Key python scripts/local_bootstrap.py # 3️⃣ 启动后端 @@ -82,7 +65,7 @@ cd frontend && npm install && npm run dev ### 站点认证(可选) ```bash -# 在 .env 中设置密码即可启用 +# 在 .env 中设置密码即可启用全站认证 AUTH_PASSWORD=your_password_here AUTH_SECRET_KEY=your_random_secret_key ``` @@ -91,174 +74,93 @@ AUTH_SECRET_KEY=your_random_secret_key ## 🎯 这是什么? -PaperMind 是一个**面向科研工作者的 AI 增强平台**,帮你: +PaperMind 是一个面向科研工作者的 AI 增强平台,帮你从「搜索论文」进化为「理解领域」。 | 😫 以前 | 😎 现在 | |:--------|:--------| | 每天手动刷 arXiv,怕错过重要论文 | 自动订阅主题,新论文推送到邮箱 | | 读论文从摘要开始,不知道值不值得精读 | AI 粗读打分,快速筛选高价值论文 | | 想了解领域发展,不知道从哪篇读起 | 知识图谱可视化,一眼看清引用脉络 | -| 写论文卡壳,不知道怎么表达 | 13 种写作工具,润色/翻译/去 AI 味 | +| 写论文卡壳,不知道怎么表达 | 学术写作助手,润色/翻译/去 AI 味 | | 文献综述耗时耗力,整理几百篇头大 | Wiki 自动生成,一键产出领域综述 | --- ## ✨ 核心能力 - - - - - - - - - - - - - - - - - -
- ### 🤖 AI Agent 对话 -你的智能研究助理,**自然语言交互**搞定一切: +你的智能研究助理,自然语言交互搞定一切: - 💬 **SSE 流式对话** —— Claude 风格,实时响应 -- 🔧 **22+ 工具链** —— 搜索/入库/分析/生成/写作自动调度 -- ✅ **用户确认机制** —— 重要操作等你点头 +- 🔧 **工具链** —— 搜索/入库/分析/生成/写作自动调度 +- ✅ **用户确认机制** —— 重要操作等你点头再执行 - 📜 **对话历史持久化** —— 切页面不丢上下文 - 🎯 **AI 关键词建议** —— 描述研究方向 → 自动生成搜索词 - - ### 📄 智能论文管理 -从收录到精读,**全流程自动化**: +从收录到精读,全流程自动化: -- 🔄 **ArXiv 增量抓取** —— 每个主题独立频率/时间 +- 🔄 **多源订阅** —— ArXiv 关键词 + CSFeeds 论文源双重抓取 - 🚫 **论文去重检测** —— 避免重复处理浪费 token - 📦 **递归抓取** —— 自动延伸更早期论文 - ⚡ **并行处理** —— 粗读/精读/嵌入三管齐下 - 💾 **按需下载 PDF** —— 入库不下载,精读才拉取 -
- -### 🔍 RAG 知识问答 - -向你的论文库**提问**,AI 跨论文综合分析: - -- 🎯 **双路召回** —— 向量检索 + 全文检索 -- 📚 **跨论文分析** —— 综合多篇论文回答问题 -- 📝 **Artifact 卡片** —— 答案自动生成可复用内容 -- 🔗 **引用追溯** —— 每句话都能找到出处 - - - ### 🕸️ 引用图谱 -**可视化**你的研究领域: +可视化你的研究领域: - 🌳 **引用树** —— 单篇论文引用网络 - 🌐 **主题图谱** —— 跨主题引用关系 - 🌉 **桥接论文** —— 发现跨领域的核心工作 - 🔬 **研究前沿** —— 高被引 + 高引用的热点 -- 📊 **研究空白** —— 发现 citation sparse region - -
+- 📊 **共引聚类** —— 相关研究自动分组 ### 📚 Wiki 自动生成 -**一键生成**领域综述: +一键生成领域综述: - 📖 **主题 Wiki** —— 输入关键词,输出完整综述 - 📄 **论文 Wiki** —— 单篇论文深度解读 - 📊 **实时进度条** —— 异步生成,自动刷新 -- 📜 **历史回溯** —— 所有生成内容可查看 +- 📜 **历史版本** —— 所有生成内容可追溯 - +### 🔍 论文订阅源(CSFeeds) + +发现你研究领域最重要的论文来源: + +- 🎯 **关键词订阅** —— arXiv 关键词自动追踪 +- 📡 **论文源订阅** —— 直接订阅 CSFeeds 热门论文 +- 📬 **邮件推送** —— 新论文自动发送到邮箱 +- ⏰ **按主题独立调度** —— 每个主题独立抓取频率 ### ✍️ 学术写作助手 -**13 种写作工具**,来自顶尖研究机构: +来自顶尖研究机构的写作工具: - 🌏 **中转英 / 英转中** —— 学术级翻译 - ✨ **润色(中/英)** —— 更地道的学术表达 - 🤖 **去 AI 味** —— 降低 AI 检测率 - 📊 **图表推荐 / 标题生成** —— 实验数据可视化建议 -- 🧪 **Reviewer 视角** —— 模拟审稿人批评 - -
### 📖 沉浸式 PDF 阅读器 -**专注阅读**,AI 随叫随到: +专注阅读,AI 随叫随到: - 📜 **连续滚动** —— IntersectionObserver 页码追踪 - 🔍 **缩放/全屏/跳转** —— 键盘快捷键支持 - 🌐 **arXiv 在线代理** —— 无本地 PDF 也能读 - ✨ **选中即问** —— AI 解释/翻译/总结 -- 📝 **侧边 AI 栏** —— Markdown + LaTeX 渲染 - - ### 🔐 站点安全认证 -**保护你的研究资产**: +保护你的研究资产: - 🔑 **站点密码** —— 简单可靠,适合个人/小团队 - 🎫 **JWT Token** —— 7 天有效期,自动续期 - 🛡️ **全站保护** —— 所有 API 都需要认证 -- 📄 **PDF Token** —— 文件访问也安全 - -
- ---- - -## 📸 界面预览 - - - - - - - - - - - - - - -
-🤖 AI Agent 对话主页

-Agent Home -
智能对话 · 论文推荐 · 热点追踪 -
-📄 论文库管理

-Papers List -
主题分类 · 日期分组 · 批量操作 -
-📖 沉浸式 PDF 阅读器

-PDF Reader -
连续滚动 · AI 问答 · arXiv 代理 -
-🕸️ 知识图谱

-Graph -
引用树 · 研究前沿 · 共引聚类 -
-📚 Wiki 自动生成

-Wiki -
主题综述 · 论文解读 · 历史回溯 -
-🌙 暗色主题

-Dark Theme -
全局暗色 · 护眼阅读 -
--- @@ -278,34 +180,44 @@ PaperMind 是一个**面向科研工作者的 AI 增强平台**,帮你: │ Service │ Engine │ Service │ Brief / Write │ ├─────────────┴─────────────┴─────────────┴───────────────────┤ │ Global TaskTracker (异步任务 + 实时进度) │ +│ 右下角悬浮面板 · 分类图标 · 完成历史 │ ├─────────────────────────────────────────────────────────────┤ │ Unified LLM Client (连接复用 + TTL 缓存) │ │ OpenAI │ Anthropic │ ZhipuAI │ ├─────────────────────────────────────────────────────────────┤ │ SQLite (WAL) │ ArXiv API │ Semantic Scholar API │ └─────────────────────────────────────────────────────────────┘ - │ + │ ┌────────────┴────────────┐ │ APScheduler Worker │ │ 按主题独立调度 │ │ 每日简报 / 每周图谱 │ └─────────────────────────┘ ``` + --- + ## ⚙️ 环境变量 | 变量 | 说明 | 默认值 | |:-----|:-----|:------:| | `LLM_PROVIDER` | LLM 提供商 (openai/anthropic/zhipu) | `zhipu` | | `ZHIPU_API_KEY` | 智谱 API Key | — | +| `OPENAI_API_KEY` | OpenAI API Key | — | +| `ANTHROPIC_API_KEY` | Anthropic API Key | — | | `LLM_MODEL_SKIM` | 粗读模型 | `glm-4.7` | | `LLM_MODEL_DEEP` | 精读模型 | `glm-4.7` | | `LLM_MODEL_VISION` | 视觉模型 | `glm-4.6v` | -| `SITE_URL` | 生产域名 | `http://localhost:5173` | +| `EMBEDDING_MODEL` | Embedding 模型 | `embedding-3` | +| `SITE_URL` | 生产域名 | `http://localhost:3002` | | `AUTH_PASSWORD` | 站点密码(留空禁用认证) | — | | `AUTH_SECRET_KEY` | JWT 密钥 | — | | `COST_GUARD_ENABLED` | 成本守卫 | `true` | | `DAILY_BUDGET_USD` | 每日预算 | `2.0` | +| `OPENALEX_EMAIL` | OpenAlex 邮箱(用于 API) | — | +| `IEEE_API_ENABLED` | 启用 IEEE 搜索 | `false` | +| `IEEE_API_KEY` | IEEE API Key | — | +| `SEMANTIC_SCHOLAR_API_KEY` | Semantic Scholar API Key | — | > 完整配置见 `.env.example` @@ -341,9 +253,9 @@ PaperMind 是一个**面向科研工作者的 AI 增强平台**,帮你: |:----:|:-----|:-----| | GET | `/papers/latest` | 论文列表(分页) | | GET | `/papers/{id}` | 论文详情 | -| GET | `/papers/{id}/pdf` | PDF 文件流 | | POST | `/pipelines/skim/{id}` | 粗读 | | POST | `/pipelines/deep/{id}` | 精读 | +| POST | `/pipelines/embed/{id}` | 生成嵌入向量 | @@ -356,6 +268,30 @@ PaperMind 是一个**面向科研工作者的 AI 增强平台**,帮你: | GET | `/graph/overview` | 全局概览 | | GET | `/graph/bridges` | 桥接论文 | | GET | `/graph/frontier` | 研究前沿 | +| GET | `/graph/cocitation` | 共引聚类 | + + + +
+📚 Wiki + +| 方法 | 路径 | 说明 | +|:----:|:-----|:-----| +| POST | `/wiki/topic` | 生成主题综述 | +| GET | `/wiki/topic/{id}` | 获取主题 Wiki | +| POST | `/wiki/paper/{id}` | 生成论文解读 | +| GET | `/wiki/history` | Wiki 生成历史 | + +
+ +
+📡 订阅源 + +| 方法 | 路径 | 说明 | +|:----:|:-----|:-----| +| GET | `/cs-feeds/` | 列表订阅源 | +| POST | `/cs-feeds/subscribe` | 订阅论文源 | +| POST | `/cs-feeds/fetch` | 手动触发抓取 |
@@ -365,45 +301,56 @@ PaperMind 是一个**面向科研工作者的 AI 增强平台**,帮你: | 类别 | 优化策略 | |------|----------| -| **前端** | 路由懒加载 · `useMemo`/`useCallback` · Vite chunk 分割 | -| **SSE** | RAF 批量 flush · 跨页面保活 | -| **LLM** | 连接复用 · 30s TTL 缓存 · 120s 超时 | -| **数据库** | SQLite WAL · 64MB cache · 关键索引 | -| **论文处理** | embed ∥ skim 并行 · 3 篇同时处理 | -| **成本** | 去重检测 · 全链路 token 追踪 | +| **首屏** | KaTeX 字体 CDN + PDF Worker CDN + 重型库懒加载(-2.7MB) | +| **前端** | 路由懒加载 · `useMemo`/`useCallback` · React.memo · RAF batching | +| **数据库** | SQLite WAL · 批量聚合查询 · Citation 索引 | +| **图谱** | list_lightweight 轻量加载 · 90% 内存削减 | +| **LLM** | 连接复用 · 30s TTL 缓存 · 指数退避重试 | +| **任务** | 统一进度回调 · 粒度化进度报告 · 分类图标 | --- ## 📋 更新日志 -### v3.1 (2026-03-01) — 安全认证 + 稳定性增强 - -**新功能** -- 🔐 **站点密码认证** —— JWT Token 保护所有 API,适合公开部署 -- 📄 **PDF Token 认证** —— 支持 query param token,文件访问也安全 -- 🔄 **SSE 认证** —— Agent 对话等 SSE 请求携带认证 +### v3.2 (2026-03-19) — 性能优化 + 全局任务系统重构 + +**性能优化** +- KaTeX 字体 + PDF Worker 改为 CDN,首屏 -2.7MB +- ForceGraph2D / react-pdf / react-markdown 懒加载 +- topic_stats N+1 查询改为批量聚合(401次→4次) +- Citation 字段加索引,图谱查询加速 +- graph_service 全量加载改为轻量模式,内存 -90% +- HTTP 客户端复用 + LLM 指数退避重试 +- 50+ 处 index-as-key 修复 + +**任务系统重构** +- 统一进度回调签名(message, current, total) +- TaskManager 合并到 global_tracker +- fetch / cs_feed / weekly / figure_analysis 进度粒度增强 +- GlobalTaskBar 改为右下角悬浮面板(分类图标/颜色/历史) +- ActiveTask 增加 category 字段 + +**其他** +- CSFeeds 论文订阅源功能完善 +- Agent 对话体验优化 +- 前端状态管理优化,减少无效重渲染 -**Bug 修复** -- 修复 `getApiBase()` 缺失闭合导致 TypeScript 编译失败 -- 恢复 `GZipMiddleware` 响应压缩 -- 恢复 `logging_setup` 统一日志格式 - -### v3.0 (2026-02-28) — 稳定性全面升级 +### v3.1 (2026-03-01) — 安全认证 + 稳定性增强 **新功能** -- Agent 对话历史完整持久化 -- PDF arXiv 在线代理 -- 论文去重检测 -- 全局任务追踪系统 +- 🔐 站点密码认证 —— JWT Token 保护所有 API +- 📄 PDF Token 认证 —— 文件访问也安全 +- 🔄 SSE 认证 —— Agent 对话等 SSE 请求携带认证 **Bug 修复** -- 修复 Wiki 生成失败、Agent 对话历史报错等 12+ 问题 -- 修复 nginx 配置导致的前端容器 crash -- 修复 Semantic Scholar API 限速重试 +- 修复 TypeScript 编译失败 +- 恢复 GZipMiddleware 响应压缩 +- 恢复 logging_setup 统一日志格式
-查看历史版本 +查看历史版本 +### v3.0 (2026-02-28) — 稳定性全面升级 ### v2.8 — 后端重构 + Agent 智能化 ### v2.7 — 多源引用 + 相似度地图 ### v2.5 — 知识图谱可视化 @@ -435,12 +382,7 @@ alembic upgrade head - **[awesome-ai-research-writing](https://github.com/Leey21/awesome-ai-research-writing)** — 写作助手 Prompt 模板来源 - **[ArXiv](https://arxiv.org)** — 开放论文平台 - **[Semantic Scholar](https://www.semanticscholar.org)** — 引用数据来源 - ---- - -## 📄 License - -[MIT](LICENSE) +- **[CSFeeds](https://csarxiv.org)** — 论文源订阅服务 --- From 5b5167660736479942d8e635ace2792dcfbc1847 Mon Sep 17 00:00:00 2001 From: Color2333 <1552429809@qq.com> Date: Fri, 20 Mar 2026 10:59:42 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=E9=87=8D=E5=86=99=20b1d72ad8a6ed=20?= =?UTF-8?q?migration=20-=20SQLite=20=E5=85=BC=E5=AE=B9=E7=89=88=E6=9C=AC?= =?UTF-8?q?=20(CREATE=20TABLE=20IF=20NOT=20EXISTS)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...2ad8a6ed_add_cs_categories_and_cs_feed_.py | 213 ++++-------------- 1 file changed, 43 insertions(+), 170 deletions(-) diff --git a/infra/migrations/versions/b1d72ad8a6ed_add_cs_categories_and_cs_feed_.py b/infra/migrations/versions/b1d72ad8a6ed_add_cs_categories_and_cs_feed_.py index be4e9b1..1b1ccc7 100644 --- a/infra/migrations/versions/b1d72ad8a6ed_add_cs_categories_and_cs_feed_.py +++ b/infra/migrations/versions/b1d72ad8a6ed_add_cs_categories_and_cs_feed_.py @@ -4,187 +4,60 @@ Revises: 20260317_0012 Create Date: 2026-03-19 15:48:01.869654 """ + from alembic import op import sqlalchemy as sa -# revision identifiers, used by Alembic. -revision = 'b1d72ad8a6ed' -down_revision = '20260317_0012' +revision = "b1d72ad8a6ed" +down_revision = "20260317_0012" branch_labels = None depends_on = None def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('cs_categories', - sa.Column('code', sa.String(length=32), nullable=False), - sa.Column('name', sa.String(length=128), nullable=False), - sa.Column('description', sa.String(length=512), nullable=False), - sa.Column('cached_at', sa.DateTime(), nullable=False), - sa.PrimaryKeyConstraint('code') + """Create cs_categories and cs_feed_subscriptions tables (idempotent). + + Note: The original migration attempted many ALTER TABLE operations + (SET NOT NULL, enum changes, column drops) that are not supported by SQLite. + SQLite schema changes require table recreation. The tables created here + are the only ones needed by the application; the ALTER operations were + either already applied on the server or not required for SQLite. + """ + op.execute( + sa.text(""" + CREATE TABLE IF NOT EXISTS cs_categories ( + code VARCHAR(32) PRIMARY KEY NOT NULL, + name VARCHAR(128) NOT NULL, + description VARCHAR(512) NOT NULL, + cached_at TIMESTAMP NOT NULL + ) + """) + ) + op.execute( + sa.text(""" + CREATE TABLE IF NOT EXISTS cs_feed_subscriptions ( + id VARCHAR(36) PRIMARY KEY NOT NULL, + category_code VARCHAR(32) NOT NULL, + daily_limit INTEGER NOT NULL, + enabled BOOLEAN NOT NULL, + status VARCHAR(32) NOT NULL, + cool_down_until TIMESTAMP, + last_run_at TIMESTAMP, + last_run_count INTEGER NOT NULL, + created_at TIMESTAMP NOT NULL + ) + """) ) - op.create_table('cs_feed_subscriptions', - sa.Column('id', sa.String(length=36), nullable=False), - sa.Column('category_code', sa.String(length=32), nullable=False), - sa.Column('daily_limit', sa.Integer(), nullable=False), - sa.Column('enabled', sa.Boolean(), nullable=False), - sa.Column('status', sa.String(length=32), nullable=False), - sa.Column('cool_down_until', sa.DateTime(), nullable=True), - sa.Column('last_run_at', sa.DateTime(), nullable=True), - sa.Column('last_run_count', sa.Integer(), nullable=False), - sa.Column('created_at', sa.DateTime(), nullable=False), - sa.PrimaryKeyConstraint('id') + op.execute( + sa.text( + "CREATE INDEX IF NOT EXISTS ix_cs_feed_subscriptions_category_code " + "ON cs_feed_subscriptions(category_code)" + ) ) - op.create_index(op.f('ix_cs_feed_subscriptions_category_code'), 'cs_feed_subscriptions', ['category_code'], unique=False) - op.alter_column('action_papers', 'id', - existing_type=sa.VARCHAR(length=36), - nullable=False) - op.create_unique_constraint('uq_action_paper', 'action_papers', ['action_id', 'paper_id']) - op.drop_constraint(None, 'action_papers', type_='foreignkey') - op.drop_constraint(None, 'action_papers', type_='foreignkey') - op.create_foreign_key(None, 'action_papers', 'papers', ['paper_id'], ['id'], ondelete='CASCADE') - op.create_foreign_key(None, 'action_papers', 'collection_actions', ['action_id'], ['id'], ondelete='CASCADE') - op.alter_column('agent_conversations', 'user_id', - existing_type=sa.VARCHAR(length=256), - type_=sa.String(length=36), - existing_nullable=True) - op.alter_column('agent_conversations', 'title', - existing_type=sa.VARCHAR(length=512), - type_=sa.String(length=256), - existing_nullable=True) - op.create_index(op.f('ix_agent_conversations_created_at'), 'agent_conversations', ['created_at'], unique=False) - op.alter_column('agent_messages', 'role', - existing_type=sa.VARCHAR(length=32), - type_=sa.String(length=20), - existing_nullable=False) - op.create_index(op.f('ix_agent_messages_created_at'), 'agent_messages', ['created_at'], unique=False) - op.drop_column('agent_messages', 'metadata_json') - op.drop_column('agent_messages', 'markdown') - op.drop_column('agent_messages', 'paper_id') - op.drop_index(op.f('ix_agent_pending_actions_created_at'), table_name='agent_pending_actions') - op.alter_column('collection_actions', 'id', - existing_type=sa.VARCHAR(length=36), - nullable=False) - op.alter_column('collection_actions', 'action_type', - existing_type=sa.VARCHAR(length=32), - type_=sa.Enum('initial_import', 'manual_collect', 'auto_collect', 'agent_collect', 'subscription_ingest', 'reference_import', name='action_type'), - existing_nullable=False) - op.drop_index(op.f('ix_collection_actions_created_at'), table_name='collection_actions') - op.drop_index(op.f('ix_collection_actions_type'), table_name='collection_actions') - op.create_index(op.f('ix_collection_actions_action_type'), 'collection_actions', ['action_type'], unique=False) - op.drop_constraint(None, 'collection_actions', type_='foreignkey') - op.create_foreign_key(None, 'collection_actions', 'topic_subscriptions', ['topic_id'], ['id'], ondelete='SET NULL') - op.alter_column('daily_report_configs', 'cron_expression', - existing_type=sa.VARCHAR(length=64), - nullable=False, - existing_server_default=sa.text('("0 4 * * *")')) - op.alter_column('generated_contents', 'metadata_json', - existing_type=sqlite.JSON(), - nullable=True) - op.alter_column('image_analyses', 'id', - existing_type=sa.VARCHAR(length=36), - nullable=False) - op.drop_constraint(None, 'image_analyses', type_='foreignkey') - op.create_foreign_key(None, 'image_analyses', 'papers', ['paper_id'], ['id'], ondelete='CASCADE') - op.alter_column('papers', 'read_status', - existing_type=sa.VARCHAR(length=8), - type_=sa.Enum('unread', 'skimmed', 'deep_read', name='read_status'), - existing_nullable=False, - existing_server_default=sa.text("'Unread'")) - op.drop_index(op.f('ix_papers_doi'), table_name='papers') - op.drop_index(op.f('ix_papers_source'), table_name='papers') - op.drop_index(op.f('ix_papers_source_id'), table_name='papers') - op.drop_column('papers', 'doi') - op.drop_column('papers', 'source_id') - op.drop_column('papers', 'source') - op.drop_index(op.f('ix_pipeline_runs_created_at'), table_name='pipeline_runs') - op.drop_index(op.f('ix_prompt_traces_created_at'), table_name='prompt_traces') - op.alter_column('topic_subscriptions', 'schedule_frequency', - existing_type=sa.VARCHAR(length=20), - type_=sa.String(length=32), - existing_nullable=False, - existing_server_default=sa.text("'daily'")) - op.drop_column('topic_subscriptions', 'ieee_api_key_override') - op.drop_column('topic_subscriptions', 'sources') - op.drop_column('topic_subscriptions', 'ieee_daily_quota') - # ### end Alembic commands ### def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('topic_subscriptions', sa.Column('ieee_daily_quota', sa.INTEGER(), server_default=sa.text("'10'"), nullable=False)) - op.add_column('topic_subscriptions', sa.Column('sources', sqlite.JSON(), server_default=sa.text('\'["arxiv"]\''), nullable=False)) - op.add_column('topic_subscriptions', sa.Column('ieee_api_key_override', sa.VARCHAR(length=512), nullable=True)) - op.alter_column('topic_subscriptions', 'schedule_frequency', - existing_type=sa.String(length=32), - type_=sa.VARCHAR(length=20), - existing_nullable=False, - existing_server_default=sa.text("'daily'")) - op.create_index(op.f('ix_prompt_traces_created_at'), 'prompt_traces', ['created_at'], unique=False) - op.create_index(op.f('ix_pipeline_runs_created_at'), 'pipeline_runs', ['created_at'], unique=False) - op.add_column('papers', sa.Column('source', sa.VARCHAR(length=32), server_default=sa.text("'arxiv'"), nullable=False)) - op.add_column('papers', sa.Column('source_id', sa.VARCHAR(length=128), nullable=True)) - op.add_column('papers', sa.Column('doi', sa.VARCHAR(length=128), nullable=True)) - op.create_index(op.f('ix_papers_source_id'), 'papers', ['source_id'], unique=False) - op.create_index(op.f('ix_papers_source'), 'papers', ['source'], unique=False) - op.create_index(op.f('ix_papers_doi'), 'papers', ['doi'], unique=False) - op.alter_column('papers', 'read_status', - existing_type=sa.Enum('unread', 'skimmed', 'deep_read', name='read_status'), - type_=sa.VARCHAR(length=8), - existing_nullable=False, - existing_server_default=sa.text("'Unread'")) - op.drop_constraint(None, 'image_analyses', type_='foreignkey') - op.create_foreign_key(None, 'image_analyses', 'papers', ['paper_id'], ['id']) - op.alter_column('image_analyses', 'id', - existing_type=sa.VARCHAR(length=36), - nullable=True) - op.alter_column('generated_contents', 'metadata_json', - existing_type=sqlite.JSON(), - nullable=False) - op.alter_column('daily_report_configs', 'cron_expression', - existing_type=sa.VARCHAR(length=64), - nullable=True, - existing_server_default=sa.text('("0 4 * * *")')) - op.drop_constraint(None, 'collection_actions', type_='foreignkey') - op.create_foreign_key(None, 'collection_actions', 'topic_subscriptions', ['topic_id'], ['id']) - op.drop_index(op.f('ix_collection_actions_action_type'), table_name='collection_actions') - op.create_index(op.f('ix_collection_actions_type'), 'collection_actions', ['action_type'], unique=False) - op.create_index(op.f('ix_collection_actions_created_at'), 'collection_actions', ['created_at'], unique=False) - op.alter_column('collection_actions', 'action_type', - existing_type=sa.Enum('initial_import', 'manual_collect', 'auto_collect', 'agent_collect', 'subscription_ingest', 'reference_import', name='action_type'), - type_=sa.VARCHAR(length=32), - existing_nullable=False) - op.alter_column('collection_actions', 'id', - existing_type=sa.VARCHAR(length=36), - nullable=True) - op.create_index(op.f('ix_agent_pending_actions_created_at'), 'agent_pending_actions', ['created_at'], unique=False) - op.add_column('agent_messages', sa.Column('paper_id', sa.VARCHAR(length=36), nullable=True)) - op.add_column('agent_messages', sa.Column('markdown', sa.TEXT(), server_default=sa.text("('')"), nullable=False)) - op.add_column('agent_messages', sa.Column('metadata_json', sqlite.JSON(), server_default=sa.text("'{}'"), nullable=False)) - op.drop_index(op.f('ix_agent_messages_created_at'), table_name='agent_messages') - op.alter_column('agent_messages', 'role', - existing_type=sa.String(length=20), - type_=sa.VARCHAR(length=32), - existing_nullable=False) - op.drop_index(op.f('ix_agent_conversations_created_at'), table_name='agent_conversations') - op.alter_column('agent_conversations', 'title', - existing_type=sa.String(length=256), - type_=sa.VARCHAR(length=512), - existing_nullable=True) - op.alter_column('agent_conversations', 'user_id', - existing_type=sa.String(length=36), - type_=sa.VARCHAR(length=256), - existing_nullable=True) - op.drop_constraint(None, 'action_papers', type_='foreignkey') - op.drop_constraint(None, 'action_papers', type_='foreignkey') - op.create_foreign_key(None, 'action_papers', 'papers', ['paper_id'], ['id']) - op.create_foreign_key(None, 'action_papers', 'collection_actions', ['action_id'], ['id']) - op.drop_constraint('uq_action_paper', 'action_papers', type_='unique') - op.alter_column('action_papers', 'id', - existing_type=sa.VARCHAR(length=36), - nullable=True) - op.drop_index(op.f('ix_cs_feed_subscriptions_category_code'), table_name='cs_feed_subscriptions') - op.drop_table('cs_feed_subscriptions') - op.drop_table('cs_categories') - # ### end Alembic commands ### + op.drop_index("ix_cs_feed_subscriptions_category_code", table_name="cs_feed_subscriptions") + op.drop_table("cs_feed_subscriptions") + op.drop_table("cs_categories")