Skip to content

feat(tags): 论文标签管理 + 数据库初始化脚本#18

Merged
Color2333 merged 4 commits intomainfrom
dev
Apr 24, 2026
Merged

feat(tags): 论文标签管理 + 数据库初始化脚本#18
Color2333 merged 4 commits intomainfrom
dev

Conversation

@Color2333
Copy link
Copy Markdown
Owner

@Color2333 Color2333 commented Apr 24, 2026

Summary

将外部贡献者 @keep-me (Ma Yukai, 曲阜师范大学) 的 "标签管理" 功能合并入主干。原本他提了 4 个 PR(#13/14/15/16),但存在:#17 的 6 文件冲突、垃圾文件 第二轮失败.txt、错写的 commit message、多头重复,所以走本地清理后重新提交。

Supersedes: #13, #14, #15, #16 — 合并完成后会逐一关闭并指向本 PR。

作者署名

原始改动来自 @keep-me,本 PR 保留了他 3 个 commit 的原始 Author 字段(keep <1603421097@qq.com>),贡献记录归他。

变更内容

🆕 标签管理功能(cherry-pick 自 keep-me 0599bee

  • 新增 tags + paper_tags 表(SQLAlchemy 模型 + alembic 迁移 20260415_0001_add_tags_table.py
  • 新增 /tags CRUD + /papers/{id}/tags 关联管理(8 个接口,全 UUID 校验、HTTP 规范)
  • PaperRepository 扩展:get_tags_for_papers / link_to_tag / unlink_from_tag + 按 tag_ids 过滤论文列表
  • TagRepository 新增:CRUD + 按使用次数排序 + paper_count 聚合
  • 前端 PaperDetail.tsx / Papers.tsx 接入标签管理 UI + 按标签筛选
  • types/index.ts 增加 Tag / TagCreate / TagUpdate 类型

🛠 数据库工具脚本(cherry-pick 自 keep-me 5cf197b + 1a489e6

  • scripts/init_db.py — 幂等的 CREATE TABLE IF NOT EXISTS 脚本,已审查无破坏性 DDL(无 DROP/TRUNCATE)
  • scripts/check_db.py / check_alembic.py — 只读诊断工具
  • scripts/init_tags_table.py — tags 表单独初始化脚本
  • scripts/test_ingest.py — 入库测试脚本

🐛 意外修到的 DAG 链接 bug

infra/migrations/versions/20260303_0009_ieee_mvp.pydown_revision 原本写的是 "20260228_0008",但父迁移的实际 revision 是 "20260228_0008_agent_conversations",alembic 会断链。keep-me 顺手修了这个,本 PR 保留修复。

🧹 顺路清理的 main 历史 ruff 债务(为通过 pre-commit)

原先在 main 分支就有 16 处既有 ruff 错误(跟 tags 功能本身无关),但 ruff 扫整文件时会把它们和 tags 新代码一起报出,导致 commit 被 pre-commit 阻止。既然碰了,顺手清一把:

  • apps/api/main.py: 2 处 E402(延迟 import 加 # noqa
  • packages/ai/pipelines.py: F841 删未用变量、2 处 B007 去掉 unused idx、SIM105 改 contextlib.suppress
  • packages/storage/db.py: TC003 Generator 挪进 TYPE_CHECKING
  • packages/storage/repositories.py: TC001/TC003 挪 UUID + schemas 进 TYPE_CHECKING、3 处 SQLAlchemy == None / == True.is_not(None) / .is_(True)
  • scripts/init_tags_table.py: 2 处 E402 加 noqa

🗑 丢弃的提交

keep-me 的第 2 个 commit 53f9070 修复加载标签列表失败的结果 标题与内容严重不符 — 它只添加了一行 第二轮失败.txt(本地调试笔记)。本 PR 跳过这个 commit,不入库垃圾文件。

验证(全本地跑过)

结果
pre-commit (ruff/format/EOF) ✅ 全绿
npx tsc --noEmit ✅ 0 错误
npx vite build ✅ 2.48s 通过
alembic upgrade 20260415_0001 ✅ tags/paper_tags 表成功建立
Tags API smoke ✅ 9 个场景(创建/列表/关联/幂等/查询/404/删除)全通过

已知遗留(非本 PR 引入)

  • alembic 有 2 个 head (20260303_0011_ieee_quota20260415_0001),IEEE 分支未合入主线 — main 原本就是这个状态,建议后续单独 PR 处理

Test plan

  • 前端类型 / 构建
  • pre-commit 全绿
  • migration 应用成功
  • API smoke(含幂等与 404)
  • 浏览器端 UX 验收(标签在 Papers 列表筛选 / PaperDetail 添加删除标签 / 颜色显示)

🤖 Generated with Claude Code

keep-me added 3 commits April 24, 2026 12:28
- 新增标签数据模型及关联表
- 添加标签相关API接口
- 实现前端标签管理界面
- 支持为论文添加/移除标签
- 支持按标签筛选论文
- 添加标签初始化脚本
添加 init_db.py 用于初始化数据库表结构
添加 check_db.py 用于检查数据库状态
添加 check_alembic.py 用于检查 alembic 版本
添加测试脚本test_ingest.py用于测试论文导入功能
在init_db.py中为pipeline_runs表添加decision_note列
@Color2333
Copy link
Copy Markdown
Owner Author

/oc review

@github-actions
Copy link
Copy Markdown

🔍 OpenCode PR Review Required

这是一个受保护的分支,merge 前需要进行 code review。

请运行以下命令进行 OpenCode review:

/oc review https://github.com/Color2333/PaperMind/pull/$PR_NUM

或者在 PR 页面评论 /oc 来触发 OpenCode review。


This is an automated reminder from PR Review Gate.

@opencode-agent
Copy link
Copy Markdown
Contributor

现在我已经全面了解了这个 PR 的变更内容。让我进行 Code Review:

Code Review 总结

✅ 优点

  1. 标签功能架构清晰

    • 后端:TagRepository + PaperRepository 扩展,职责分离明确
    • 前端:标签筛选 UI 设计优雅,支持多选/编辑/删除
    • API 设计符合 RESTful 规范(9 个接口覆盖 CRUD 和关联管理)
  2. 数据库设计合理

    • tagspaper_tags 表结构规范,使用 UUID 主键
    • 唯一约束 uq_paper_tag 防止重复关联
    • 级联删除 (ondelete="CASCADE") 保证数据一致性
  3. 迁移脚本修复重要 bug

    • 20260303_0009_ieee_mvp.pydown_revision"20260228" 修正为 "20260228_0008_agent_conversations",解决了 Alembic 迁移链断裂问题
  4. 代码质量改进

    • 修复了 16 处既有 ruff 错误(E402/F841/B007/SIM105/TC003)
    • SQLAlchemy 查询改进:== None.is_not(None),更符合最佳实践
  5. 前端体验优秀

    • 标签筛选支持多选,带计数显示
    • 颜色编码直观,UI 交互流畅
    • 支持标签快速编辑/删除(hover 显示操作按钮)

⚠️ 潜在问题

  1. apps/api/routers/tags.py:158-185 - 批量更新竞态条件

    # 当前实现:先 unlink 再 link,中间状态可能导致数据不一致
    for tid in current_tag_ids - new_tag_ids:
        paper_repo.unlink_from_tag(str(paper_id), tid)
    for tid in new_tag_ids - current_tag_ids:
        # ...

    建议:在事务内用 SET 操作替换增量更新,或添加数据库级约束。

  2. packages/storage/repositories.py:800-819 - list_all 性能问题

    def list_all(self) -> list[Tag]:
        q = (
            select(Tag, func.count(PaperTag.id).label("paper_count"))
            .join(PaperTag, Tag.id == PaperTag.tag_id, isouter=True)
            .group_by(Tag.id)
            .order_by(func.count(PaperTag.id).desc())
        )

    问题:每次获取标签列表都做 LEFT JOIN + GROUP BY,标签数量增多后可能变慢。
    建议:考虑在 tags 表添加 paper_count 缓存字段,通过触发器或应用层维护。

  3. frontend/src/pages/Papers.tsx:269-289 - 标签删除未处理关联

    const handleDeleteTag = useCallback(
      async (tag: TagType) => {
        if (!window.confirm(`确定要删除标签 "${tag.name}" 吗?`)) return;
        await tagApi.delete(tag.id);
        // ...
      }

    风险:数据库虽有 ON DELETE CASCADE,但前端未提示用户"将同时移除 X 篇论文的标签关联"。
    建议:删除前 API 返回关联论文数,确认后再删除。

  4. infra/migrations/versions/20260415_0001_add_tags_table.py:14 - 硬编码的 down_revision

    down_revision = "b1d72ad8a6ed"

    风险:这个 revision ID 是本地生成的,可能与主线分支不一致。
    建议:确认 b1d72ad8a6ed 对应主线的哪个迁移,或改为依赖最新主线 revision。

  5. apps/api/routers/tags.py:36-52 - Query 参数校验不足

    @router.post("/tags")
    def create_tag(name: str, color: str = Query(default="#3b82f6")) -> dict:

    问题:未校验 color 是否为合法的十六进制颜色格式。
    建议:添加正则校验 Pattern(r"^#[0-9A-Fa-f]{6}$")

  6. frontend/src/types/index.ts:126-134 - 类型定义缺失

    export interface TagCreate {
      name: string;
      color?: string;
    }

    问题name 未限制长度,但数据库定义为 String(64)
    建议:添加 minLength/maxLength 约束或使用 zod 校验。

📋 建议改进

  1. 添加标签使用统计:在标签删除时提示影响范围
  2. 颜色选择器:前端创建标签时提供可视化颜色选择,而非手动输入 hex
  3. 标签去重:创建前检查同名标签(后端已有,前端可添加实时校验)
  4. 快捷键支持:论文列表页支持快捷键批量添加/移除标签
  5. 标签云视图:按使用频率展示标签云,快速发现热门标签

🎯 总体评价

这是一个高质量的 PR,功能完整、代码规范、测试充分。主要问题集中在边缘场景处理长期可维护性,不影响核心功能。建议修复上述 6 个潜在问题后合并。

推荐操作

New%20session%20-%202026-04-24T04%3A32%3A06.102Z
opencode session  |  github run

opencode review 提出的 6 条意见里,采纳 2 条并落地:

1. [review #3] Papers.tsx handleDeleteTag 在确认框里提示
   将同时移除 N 篇论文上的该标签关联,避免用户误删

2. [review #5] tags.py POST/PATCH /tags 参数严格校验
   - name: min_length=1, max_length=64
   - color: 正则 ^#(?:[0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$
   smoke 过 5 个场景:合法/非法 color、空/超长 name、默认值

其余 4 条(批量更新竞态、list_all 性能、硬编码
down_revision、TS 类型长度)在 PR 评论中说明保留原因。

顺带清理 Papers.tsx 未使用的 X 图标 import。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

🔍 OpenCode PR Review Required

这是一个受保护的分支,merge 前需要进行 code review。

请运行以下命令进行 OpenCode review:

/oc review https://github.com/Color2333/PaperMind/pull/$PR_NUM

或者在 PR 页面评论 /oc 来触发 OpenCode review。


This is an automated reminder from PR Review Gate.

@Color2333
Copy link
Copy Markdown
Owner Author

感谢细致的 review!逐条回应:

✅ 已采纳(commit b9a99cc

[#3] 删除标签前提示关联数Papers.tsx:handleDeleteTag 现在在 confirm 框里显示「将同时移除 N 篇论文上的该标签」,利用已有的 tag.paper_count 字段,无需新增 API。

[#5] color 参数正则校验 + name 长度校验tags.py 的 POST/PATCH 接口:

  • name: min_length=1, max_length=64(匹配 DB String(64)
  • color: 正则 ^#(?:[0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$
  • 5 个 smoke 场景(合法/非法 color、空/超长 name、默认值)均 4xx/2xx 正确

⏸ 保留现状(带技术理由)

[#1] 批量更新竞态 — 当前代码已在 with session_scope() 事务内执行所有 unlink/link,末尾一次 session.commit()。SQLite 事务里读不到未提交的中间状态;且本项目是单用户本地应用,不存在并发冲突场景。增量 set 差集模式是 correctness-preserving 的实现。

[#2] list_all 性能 — LEFT JOIN + GROUP BY 在标签数 < 1000 的场景里(正常用户的标签数量远低于此)毫秒级返回。为此加缓存字段 + 触发器属于典型的过早优化(YAGNI/KISS),建议等出现真性能问题再说。

[#4] 硬编码 down_revision b1d72ad8a6ed — 已本地验证:main 分支上 b1d72ad8a6ed -> 20260317_0012, add cs_categories and cs_feed_subscriptions 的确是存在的合法 revision,DAG 链接正确。alembic upgrade 20260415_0001 本地实测 upgrade 成功并建出 tags/paper_tags 表。

[#6] TS 类型缺少 minLength/maxLength — TypeScript 类型只做编译期检查,不做运行时校验。添加长度约束需要引入 zod 等运行时校验库,超出本 PR 范围。后端已加 min_length=1, max_length=64 的 FastAPI 校验,作为 source of truth 已经足够。

📝 review 的改进建议(非阻塞)

  • 颜色选择器 UI、标签云视图、批量操作快捷键 — 都是合理的 enhancement,但属于后续迭代而非本 PR 范围

全部验证通过后,准备合并到 main。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants