项目 Github 地址:https://github.com/LIKEGAKKI/Tidymark

书签这件事,问题从来不是存不下来,而是存完以后基本找不回来了。

我自己的使用习惯很典型。看到一篇文章、一个工具、一个仓库,先收藏再说。收藏夹慢慢变成仓库,最后变成垃圾场。真要找东西的时候,浏览器原生书签搜索又不太够用,你记得的是“那个讲 React Compiler 的文章”,不是标题里第 7 个单词。

Tidymark 就是冲着这个问题做的。它不是另一个“全能 AI 助手”,也不是把浏览器书签重新包一层皮。这个项目只盯一件事:让已经收藏过的内容还能被重新找到、重新整理、重新清掉。

从实现上看,它是一个 Chrome Extension Manifest V3 项目,技术栈不复杂:React 19、TypeScript、Vite,加上 @crxjs/vite-plugin。真正麻烦的地方不在框架,而在边界感。哪些事交给 AI,哪些事必须自己做;哪些操作可以快一点,哪些操作必须保守。这些决定比“模型选什么”更影响产品体验。

先把产品边界钉死

我一开始就不想把 Tidymark 做成聊天机器人。书签管理最怕目标失焦。你明明想解决“怎么找回以前收藏的东西”,最后却做出一个什么都能聊两句、但书签还是乱的工具。

所以现在的产品边界很硬,只有三条主线:

  • AI 智能分类
  • AI 语义搜索
  • 冷门书签识别和清理

这里面只有前两项真正依赖模型。冷门书签识别完全是规则逻辑,直接根据 dateAddeddateLastUsed 计算。这个取舍很朴素,但我觉得是对的。能不用模型的地方,就别把问题复杂化。否则每个功能都想“AI 一下”,最后只会把成本、延迟和不确定性一块抬上去。

为什么界面拆成这么多块

Chrome 扩展天然不是单页应用。Tidymark 里我把界面拆成了 popupside panel、完整页面和 options 页面,这不是为了“结构清晰”这种空话,而是因为不同任务真的适合不同容器。

popup 很轻,只负责入口和状态摘要。它不适合承载复杂编辑,因为一失焦就关掉,用户体验会很差。AI 搜索我放进了 side panel,原因也很直接:搜索是一个需要边看当前页面边找书签的动作,侧边栏比弹窗顺手得多。分类编辑和冷门书签清理则放到完整页面里,因为这里会出现更重的交互,比如拖拽、合并分类、批量选择。设置、导入导出、快照管理单独放 options,避免和主流程搅在一起。

这套拆法最后会把代码结构也逼得更清楚。前端负责界面,background service worker 负责消息路由和业务编排,真正碰 Chrome 书签 API 的逻辑再往下沉一层。你回头看 src/background/index.ts,它做的事情其实很克制:收消息,分发给分类、搜索、冷门书签、快照、导入导出这些 handler。这个层不聪明,但很重要,因为 MV3 下你最好别把状态和副作用到处乱放。

AI 在这个项目里,不负责“理解一切”

这类项目最容易犯的错,就是把所有原始数据直接塞给模型,然后期待它替你做完整决策。Tidymark 刻意没这么干。

先说分类。

分类流程不是一步到位,而是拆成两段。第一步先从书签样本里生成分类骨架,限制在最多两级:一级分类加可选子分类。第二步再把书签按批次归类,每批默认 100 条。这样做有几个现实好处:

  • token 成本能控住
  • 结果结构更稳定
  • AI 出错时更容易校验和兜底

代码里还做了额外的防守。模型返回分类结果后,不是直接照单全收,而是先校验一级分类名和子分类名是不是在骨架里存在,不合法的直接丢进 uncategorized。说白了,我不相信模型每次都听话,所以先把“错了怎么办”写进流程里。

搜索链路也是同一套思路。先本地粗筛,再让 AI 精排。

具体做法不花哨:先把全部书签拍平,基于标题、URL、文件夹路径做关键词匹配,筛出候选集;再把候选集交给模型做语义排序,顺便生成命中理由。这样有个很实际的好处,AI 不需要面对整个书签库,只需要面对已经缩小过的一小批候选。成本低,响应也更稳。更重要的是,搜索结果还能保留“为什么给我这个结果”的解释,不至于像黑箱。

我挺在意这一点。很多 AI 功能的问题不是“不准”,而是“不知道它为什么这么判断”。一旦解释完全缺席,用户很难建立信任。

真正高风险的部分,其实不是 AI

AI 调错了,最多分类怪一点、排序歪一点。真正危险的是写操作,因为它会直接改书签树。

Tidymark 现在有一个很明确的底线:高风险写操作前必须先做快照。AI 分类应用结果之前会先抓当前书签树,批量删除冷门书签之前也一样。快照存在 chrome.storage.local,默认保留最近 10 份,超过就淘汰最旧的一份。这个机制不复杂,但非常值钱,因为它把“试试看”变成了“试错也能回来”。

这里还有一个我觉得必须做的动作:数据出站确认。

Tidymark 不内置固定 AI 服务,而是让用户自己配置 API EndpointAPI Key 和模型名。在真正调用前,还要额外确认一次“数据会被发送到你配置的 AI 服务”。这一步看起来有点烦,但省不得。书签数据往往很私人,至少得把责任边界说清楚。

导入导出这块也做得偏保守。导出文件里带了 schemaVersion,导入时先校验版本和基本结构,再决定是否恢复。这个设计现在看着普通,可一旦项目进入迭代期,它会比任何炫技代码都实用。没有 schema,后面基本等着踩坑。

一些刻意保守的决定

这个项目里有几处“明明还能再做一点”,但我故意停住了。

第一,分类层级只支持两级。不是因为做不出无限层,而是因为书签整理一旦层级太深,最后只是把“难找”换成“难点开”。两级对大多数个人收藏已经够用了。

第二,搜索没有上向量库,也没有维护额外索引。当前方案就是本地粗筛加 AI 重排。它不算前沿,但对于浏览器扩展这种场景,部署简单,状态一致性也更好。至少现在我不用为了“找回一条书签”再引入一套更重的数据系统。

第三,写入分类结果时走的是比较直接的书签树重建流程:先清空目标范围,再按分类结果写回去。这个方案不算优雅,性能上也不是最激进,但配合快照和二次确认,它的行为是可预期的。书签管理工具最怕“自作聪明地微调”,因为用户通常很难一眼看出哪里被改坏了。

这个项目让我更确定的一件事

做 Tidymark 之后,我反而更不迷信 AI 了。

模型当然有用,尤其是在“把模糊意图映射到已有数据”这类问题上,确实比纯关键词好不少。但一个能用的产品,靠的从来不只是模型能力。真正把体验拉开的,往往是那些看起来没那么性感的东西:范围控制、候选压缩、结果校验、快照恢复、明确的界面分工。

如果一定要给这个项目下个判断,我会说它像一个很克制的实验:让 AI 去做它擅长的语义理解,让规则系统守住边界,让危险操作永远有后路。

这比“让 AI 接管一切”麻烦一点,但至少更像一个能长期维护的工程。