
「开发者日记」用 Cursor 和 AI 开发浏览器插件 Mindflow
这是一个我用 Cursor 和 AI 从零打造智能标签管理插件 Mindflow 的完整实录,分享从技术架构、可视化方案到用户调研的全过程,以及在开发与定位中学到的产品教训与反思。
开源地址:WeepsDanky/tab-management-extension
下载/安装地址:Mindflow - Chrome Web Store
0. 一切的起点:为什么要做 Mindflow?
这个项目的起点,其实源自一个很「日常」的痛点:
我浏览网页的方式一直很混乱。开了一堆标签页,不舍得关,但又没时间看完。打开浏览器时像在扫墓:「啊,这个页面我还记得」,「这篇我当时是要查什么来着?」。最终这些标签页堆成一座信息坟场,既影响效率,也压得脑袋很沉。
而当我试图解决这个问题时,发现市面上的插件都不太对味。像 OneTab 只能收纳标签,效率是提升了,但我也再没打开过那些被“一键收纳”的页面;而像 Pocket、Notion 等知识管理工具又与标签页完全割裂,保存流程繁琐、上下文断裂。我想要一个更自然、更智能、更贴合我浏览习惯的工具。
所以我就决定自己动手做一个 Chrome 插件,帮我解决标签页爆炸、信息沉没这些问题。
Mindflow 是一个专为 Chrome 浏览器设计的智能侧边栏扩展插件,核心目标是:
解决现代用户在浏览过程中遇到的 信息过载、标签爆炸和知识碎片化 问题。
通过一个侧边栏,它把标签管理 + 内容摘录 + 知识整理整合在一个界面中,让浏览器变成你大脑的延伸,而不是信息的垃圾桶。
这些问题,Mindflow 试图一一解决。
这个项目过程中,我踩了不少坑,也在 Cursor 和 AI 的帮助下做出很多技术决策。例如:
- 一开始用 D3.js 做图形可视化,性能太差,后来在 AI 的建议下换成了 Cytoscape.js。
- 为了同步多个标签页的状态,研究了 Chrome 扩展的消息传递机制,做了一个稳定的后台服务 worker。
- 搜索系统从模糊匹配做起,逐渐发展成可以用语义相似度查找相关标签的“向量搜索”。
0.5 作为 Web 开发者,第一次做浏览器插件是怎样的体验?
坦白说,起初我完全不会做浏览器插件。我熟的是 Next.js Web 应用和一些基于 Flask 的后端开发,平时用 Docker 管理开发环境,对浏览器扩展的机制一无所知。
于是我开始疯狂查资料,研究各种插件开发框架。最初考虑过直接用原生 JavaScript 写 manifest v3 的插件,也试过用一些看起来「黑科技」的框架,比如 webextension-polyfill、Plasmo、WXT,但要么太重、要么文档不齐全,最后还是选择了一个相对轻量又现代的组合:
👉 React + TypeScript + Vite + CRXJS
- React + TS:保证开发体验和组件复用
- Vite:冷启动超快,HMR 很舒服
- CRXJS:一个极简但很清爽的构建工具,能把 React 项目直接打包成 Chrome 插件,开发体验接近 Web 应用
而这里,Cursor AI 发挥了巨大的作用。不夸张地说,我的很多架构选型和代码结构,都是在和 Cursor 聊出来的。它给了我思路,也帮我快速落地原型。
1. 打造标签宇宙视图:从 D3 到 Cytoscape.js 的演进
当我决定把标签页可视化成“宇宙视图”时,我的第一反应就是:
“要不直接用 D3.js 吧?”
D3 是前端可视化的大杀器,我之前在做数据仪表盘的时候也用过它。它灵活、强大、而且有无数社区例子。于是,我花了一个周末,用 force-directed layout 把当前打开的标签页渲染成一个“星系图”:
每个标签是一个节点,它们之间根据来源网站、访问时间、标题相似度等因素连线。看起来还挺酷,但问题也很快来了:
❌ D3.js 的痛点:酷炫但性能吃紧
当你只有 10 来个标签页时,一切都很顺滑。但当你打开了 40+ 个页面,图就卡得像 PPT。节点之间不停地摆动、碰撞,浏览器风扇开始呼啸,我的 M1 都顶不住了。
D3 本质是基于 DOM 操作的,每个节点和连线都是 SVG 元素。而 SVG 渲染复杂图形的时候,性能消耗非常高,加上物理引擎模拟布局,浏览器负载直接爆表。
我尝试做了很多优化:
- 降低 tick 频率
- 缩短连线距离
- 把 simulation 设置为冷启动后静态图
- ...都治标不治本
有点失望,但也意识到:也许 D3 不是最佳方案。
1.1. 转向 Cytoscape.js:更适合图谱视图的选择
后来我和 Cursor 聊天时随口提了这个问题,它推荐我看看 Cytoscape.js。
这其实是一个更偏向“图数据库可视化”的库,之前我只在生物信息领域见过它(比如基因图谱和 connectedpapers)。但它的结构非常适合 Mindflow 这种场景:
- 节点/边是纯数据,不直接绑 DOM
- 支持几十种 layout 算法(我们最后用了 Cola layout)
- 可以动态切换视图、更新节点状态
- canvas 渲染,比 SVG 更高效
我用 Cursor 让它帮我把原来的 D3 实现迁移成 Cytoscape,居然一个晚上就搞定了大部分逻辑。换完 Cytoscape 之后,性能明显提升,而且图谱还能实现交互,比如:
- 点击标签节点高亮当前页面
- 拖动节点自动重新布局
- 鼠标悬浮时显示网页摘要
- 支持缩放、聚焦某一组主题等操作
整个体验,从「好看但没法用」跃迁到了「能用还挺顺畅」的阶段。
然后我会讲讲另一个大坑:**怎么在多个标签页之间保持实时同步?**毕竟每个标签页都是独立上下文,如何在它们之间同步状态、避免冲突,是我一开始完全没考虑过的问题。
在 Mindflow 的开发过程中,有一个一开始完全没想到,但后来变成“卡脖子”级别的问题:
多个标签页之间的数据同步到底怎么搞?
你可能没注意到,Chrome 插件中的每个标签页,其实运行的是完全独立的「上下文」。比如你在 A 标签页里改了个数据,B 标签页是不会自动知道的。它们互相不认识,也互相不监听。
初期的做法:LocalStorage + 轮询
最开始我很天真,想着「既然都是 React,直接把数据放 localStorage 不就完事了?」但事实证明这招根本不行:
- localStorage 更新不会自动通知其他标签页
- 除非在所有页面里加个
setInterval
轮询变化(这也太土了) - 而且多个标签页同时写入时,还会互相覆盖
这时候我开始查 Chrome 插件的官方机制,发现关键在于两个词:
Service Worker 和 Message Passing
2. 进入正轨:理解插件架构的核心机制
Chrome 插件的结构其实很像微服务系统,它分几个部分:
- Content Script:注入到网页里的 JS,能操作页面 DOM
- Background Service Worker:像一个“总控中心”,生命周期独立,可以监听事件、存储全局数据
- Popup / Sidebar 页面:你看到的插件 UI,在用户点击图标或打开侧边栏时加载
- Storage API:比 localStorage 更可靠的 chrome.storage.local
最关键的是——这些部分之间不能直接访问彼此的状态,它们之间要靠「消息传递」沟通。
我是怎么实现跨标签通信的?
我给每个标签页都加上了 content script,它会监听用户的操作,比如点击按钮、切换标签等,然后通过 chrome.runtime.sendMessage
把消息发给后台的 Service Worker。
Service Worker 接收到消息后,更新全局状态,然后通过 chrome.tabs.sendMessage
把变更广播给其他页面。它有点像“中枢神经”:
// 后台监听器
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === "tabUpdated") {
// 更新全局状态
updateTabState(message.tabId, message.payload);
// 广播给所有其他标签页
broadcastToOtherTabs(message.tabId, message.payload);
}
});
这个机制一旦打通,整个插件就“活了”:你在任何一个页面更新信息,别的地方都能即时反映出来。
有了消息传递后,另一个问题出现了:
如果两个标签页同时更新了同一个标签组,谁说了算?
我参考了一些分布式系统的思路,最后选择了 **“后台为唯一可信源”**的方案:
- 每次更新请求都会发给后台
- 后台接收后广播新的状态(时间戳更新)
- 所有前端页面都以“最新时间戳”为准更新 UI
这种方式牺牲了一点实时性,但大大降低了冲突带来的 UI 抖动和状态不一致问题。
Mindflow 不仅要同步状态,还得能在插件关闭后保留数据。
Chrome 插件提供了 chrome.storage.local
这个 API,它有点像浏览器内建的 NoSQL 存储。你可以像操作字典一样读写它,而且:
- 跨标签页共享
- 插件关闭再打开数据还在
- 不用担心浏览器清缓存(除非用户卸载插件)
我把所有核心状态(标签结构、分组信息、知识库记录等)都存进了这里,并通过一个简单的状态管理器封装起来,方便维护。
3. 搜索系统的演进:从关键词到语义相似度
搜索系统是 Mindflow 最核心的能力之一。毕竟再强大的标签管理功能,如果找不回“那个曾经看过但忘了名字”的网页,也难免让人懊恼。
而搜索这个功能,我可以说是改了不下三次,每一次都像升级一个层级,体验上完全不同。
第一版:朴素标题匹配
最开始的时候,我只做了一个最简单的版本:
- 每次打开一个新标签,就把它的
title
和url
存进状态里 - 在搜索框里输入关键字时,对所有标题进行
includes()
匹配,展示匹配的结果
这个版本的好处是实现快、响应也很快,但问题也非常明显:
- 只能搜标题:很多时候,网页内容才是你记住的关键词,而不是网页标题
- 不支持模糊匹配:比如你搜索“数据可视化”,有些文章标题里写的是“信息图表”,就搜不到
很快我就意识到,这种搜索方式只能应付“我知道我在找啥”的场景,但对于「模糊记忆」「回溯查找」的用途来说远远不够。
第二版:高亮正文关键词,支持内容全文搜索
我开始研究 Chrome 插件有没有办法“读懂”网页的正文内容。
好消息是:可以!
Chrome 允许插件通过 content script 注入代码到网页中,读取页面中的文本内容。这意味着:
Mindflow 完全可以在用户打开网页时,把网页的正文部分抓取下来,并存进本地数据库中。
于是我做了第二版改进:
- 插件后台会提取页面正文(类似 Readability 那种方式,自动去除广告、菜单、评论等无关内容)
- 把正文作为搜索索引,支持内容层级的关键词匹配
- 搜索结果中可以高亮关键词在网页中的位置,并跳转定位
这个版本终于解决了「标题搜不到」的问题,大大提升了找回信息的成功率。而且在 UI 上,我加了高亮 + 预览片段的功能,效果很接近 Notion 那种体验。
不过问题也来了:
- 有些页面结构复杂,正文提取不准(比如动态渲染的站点)
- 搜索内容变多后,关键词匹配效率下降(尤其是在几十上百个标签页时)
- 搜索结果相关性差:虽然关键词能匹配,但很多时候用户记住的是概念或主题,而不是具体词语
4. 下一步打算
现在 Mindflow 的搜索系统已经能满足我大部分“模糊找回”的需求了,但我还想尝试:
- 增加 多轮搜索上下文:比如你上次搜了「记忆术」,这次再搜「学习效率」,能把两次结合起来推荐内容
- 支持 图片/代码等非文本信息 的搜索(需要用 embedding 处理更多格式)
- 加入 用户行为反馈:比如用户点了某个搜索结果,就认为它是“相关”的,反哺 embedding 权重排序
5. 这次 AI 开发的感想
这一章我想聊聊一个对我自己帮助很大的反思:
AI 开发 ≠ 让 AI 写代码
AI 开发 = 你得先把方向搞清楚,AI 才能帮你快马加鞭
这次在开发 Mindflow 的过程中,我其实也走过一些“误区”——尤其是在没有设计文档、缺乏架构规划的情况下就开写,最后反而多走了很多弯路。
5.1 Cursor 是个很棒的副驾驶,但你得先有方向盘
我这次最直观的一个教训就是:
如果架构没设计好,再聪明的 AI 也救不了你。
比如那个 MindUniverse.tsx
文件——也就是标签宇宙图谱的主组件。一开始我想着“功能先跑通”,于是各种逻辑都往里堆,结果到后来这个文件膨胀到了 1500 多行,几乎什么都干:
- 初始化 Cytoscape 图谱
- 加载标签数据
- 布局计算
- 鼠标交互响应
- 搜索结果高亮
- 节点聚类逻辑
- 边样式更新
- 主题切换适配
- ……等等等等
每次打开这个文件,我都要先深呼吸两下。
更离谱的是,修改一个边的颜色竟然会引发整个组件的重绘,还会打乱原来的布局,搞得我满头问号。
这个时候我才意识到:
是我自己没有先画清楚这张图,而不是 AI 没给出正确答案。
5.2 如果有 Design Doc 和 Checklist,AI 会变得特别强大
我后悔的一件事是:在项目一开始的时候,我并没有写清楚组件职责、状态流向、接口规范。这次开发的时候为了尽快出一个 MVP 跳过了这个步骤。
所以每次问 AI 时,我给它的上下文就非常模糊,只能靠它猜。猜对了我就点头,猜错了就继续试。效率其实很低。
最好的还是提前写好模块设计(比如:哪些模块负责什么?谁管理状态?)和一个设计文档,设计好明确的 API 输入/输出结构。
5.3 好的架构比好的代码更值钱
我其实是踩着痛点才真正理解“架构先行”的重要性。
比如,如果一开始就把图谱逻辑拆成多个小模块:
GraphInitializer.ts
: 只负责加载图数据LayoutManager.ts
: 管理布局算法和动态更新InteractionHandler.ts
: 响应点击、悬浮等用户操作ThemeAdapter.ts
: 控制颜色、样式适配
那后来要加新功能,比如“搜索结果高亮”或“节点拖动吸附”,只需要修改对应模块,逻辑清晰、风险也低。
可惜我一开始就写了一坨,等要拆时已经牵一发而动全身。
6. 回头看 Mindflow:我们真正学到了什么?
跟以前的项目其实很想,只不过这次又文字的反思。这次在写完插件、做完系统、优化完体验后,我最深刻的反思不是:功能是不是还可以更强?而是:
“我们是不是从一开始,就没想清楚,到底在解决谁的什么问题?”
6.1 一开始的问题就模糊了:不是所有人都需要「整理 tab」
在最初构思 Mindflow 时,我以为“浏览器标签混乱”是个普遍问题,解决了这个问题,大家自然就会觉得有用。
但后来我发现,我们默认的用户画像——“需要更好管理标签页和网页信息的用户”,其实根本是个「想象中的用户」。
有些用户,比如我自己,会经常开几十个甚至上百个 tab,但实际上这种行为本身就是很主观的。有些人——尤其是专注型用户,比如写 code 的、科研的、资料阅读型的——标签本身就不多,因为他们知道自己为什么开每个 tab。
这些人自然也就不需要什么搜索、不需要自动整理,甚至完全不需要插件。
6.2 真正的问题是,我们没理解用户「为什么开那么多 tab」
后期我们在做用户调研时,试图找出“标签过多的群体”。找来找去,发现这类人群其实非常分散:
- 有的人是因为 FOMO(怕错过)
- 有的人是 ADHD 风格工作(跳来跳去)
- 有的人其实是把 tab 当 todo list 在用
- 有的人只是临时收藏,不舍得关
你会发现——这些人行为看似一致,背后动机却千差万别。
而更致命的是:这些人中的很多,并不会主动寻找工具来解决这个问题。他们要么习惯了混乱、要么懒得管理、要么压根不觉得这是个问题。
这就解释了一个很扎心但真实的现象:
我们做了一个“功能强大”的插件,但并没有人真正愿意用它。
6.3 用户不是“找”来的,是“在对的地方被发现”的
另一个大教训是用户获取。
我一开始以为:用户就是用户,只要有标签混乱的痛点,谁都可以是我们的用户。
但后来我们发现,即使找到了目标用户(比如某些博士、知识工作者),如果他们本身并没有创新意识或尝试新工具的习惯,那他们也不会是好的种子用户。
比如我们找的一些 PhD、WebLab 方向的工程师,他们真的就是:
- 习惯用最传统的方式(书签、笔记本、手动记录)
- 更相信“训练你的大脑”,而不是依赖工具
- 更倾向于稳、熟悉、不出错,而不是尝试未知的新插件
这些用户,不管我们怎么优化 Mindflow,对他们来说都“不是必需品”。
相反,如果我们去 Hacker News、Product Hunt、AI/工具极客社群、独立开发者圈子 找用户,或许反馈会完全不同。
这让我意识到:
找对人 + 找对场景,比功能本身还重要。
6.4 用户访谈 ≠ 反馈,而是隐性的销售
还有一个非常深的认知升级,是我以前把“用户访谈”和“推广增长”看成两件事。
现在我才明白:
真正有价值的用户调研,其实就是销售的一部分。
你在访谈中说得越清楚、越能激发对方的想象,他就越愿意深入给出反馈、参与测试,甚至成为你的第一批用户。
单向的“你说我听”式访谈,是很难挖出真正需求的。只有当访谈变成互利的交流和共创,你才能真正理解用户背后的动机。
更重要的是:这些深度访谈,往往就是最精准的增长动作,因为你是在一对一地构建高质量关系。
7. 结语:不是继续做功能,而是更精准地找对人
现在回头看 Mindflow,其实功能层面已经做得很完整了。我们有:
- 自动聚类的标签图谱
- 语义向量搜索
- 多标签实时状态管理
但是下一步我不会再去想“怎么加新功能”,而是:
- 去找对的用户(创新社区、工具极客、数字工作者)
- 为他们量身打造传播内容和上手流程
- 打造「内容 + 工具」结合的使用场景(比如学习资料整理、论文阅读辅助、项目研究管理等)
- 设计极简版本的 Mindflow(哪怕砍掉 80% 功能)
如果你也在开发某个产品,希望这段经历对你有启发。
我们下一个项目见 :)
附录:AI 开发 checklist
这次做完 Mindflow,我找了一个简单的 AI 项目准备 checklist,分享给你: