1. 浏览器 Tab 自动分组插件到底解决什么问题
浏览器 Tab 自动分组插件,说白了就是让浏览器自己把同一域名的标签页收进一个带颜色和名字的组里,省得你手动一个个拖。它适合每天开几十个标签页、又懒得整理的人,也适合想用 Cursor 练手浏览器扩展开发的同学。我平时查资料会同时开 GitHub、文档站、几个技术博客,标签栏挤成一条缝,找东西全靠肉眼扫,后来干脆用 Cursor 写了个插件,把域名和分组规则交给模型做聚类,再让扩展自动执行分组。
这个场景的核心链路其实分两段:一段是浏览器扩展本身的能力,用chrome.tabs.group、chrome.tabGroups这些 API 完成分组和上色;另一段是“怎么决定哪些域名该归到一组”,这一步如果纯靠手写规则,遇到新域名就得手动加,很烦。所以我把模型接口接进来,让模型根据域名和标题给出分组建议,再由扩展落地。模型调用需要一个稳定的 Base URL 和统一 Key,这里我用 TaoToken 做接入层,把模型请求统一走一个入口,Cursor 里写代码时也不用到处换配置。
先明确一下这个插件最终要做什么:
- 配置页:填写域名、分组名称、分组颜色,保存后对已打开的标签页立即分组。
- 编辑页:展示已有分组规则,支持修改和删除。
- 后台自动分组:打开已配置域名的链接时,自动并入对应分组。
- 模型聚类:对未配置的域名,调用模型接口给出分组建议,减少手动维护。
这四件事里,前三件是扩展 API 的活,第四件是模型接口的活。很多人卡在第四件,不是因为模型不会用,而是因为 Base URL、Key、Model ID 三件套没对齐,请求发出去报 401 或者local proxy failed。下面我按“先跑通扩展,再接模型”的顺序写,每一步都能复制。
2. TaoToken 前置准备:统一 Key 与 Base URL 怎么配
在 Cursor 里写调用模型的代码之前,先把接入层准备好。TaoToken 的作用是给你一个统一的 API 入口,Base URL 固定为https://taotoken.net/api,Key 在控制台生成。这样你在扩展里发请求时,不用关心后面具体是哪个模型厂商,换模型只改 Model ID 就行。
第一步,打开控制台创建 API Key。地址是https://taotoken.net/console,登录后在 API Keys 页面新建一个 Key,复制出来先存到本地临时文件里,后面要填进扩展的配置。注意 Key 只显示一次,丢了就重新建。
第二步,确认你要用的 Model ID。不同模型 ID 不一样,比如做文本聚类可以用通用的对话模型。你可以在模型对话页面先试一下请求能不能通,地址是https://taotoken.net/models,选一个模型发一句“你好”,能返回就说明 Key 和 Base URL 没问题。
第三步,把三件套记下来,后面 Cursor 生成的代码里要填:
| 配置项 | 值 | 说明 |
|---|---|---|
| Base URL | https://taotoken.net/api | 所有请求的前缀,不要加多余路径 |
| API Key | 控制台生成的sk-开头字符串 | 放在扩展的 storage 里,不要硬编码进源码 |
| Model ID | 例如gpt-4o-mini这类对话模型 | 按控制台可用列表填 |
这里有个容易踩的坑:Base URL 末尾不要自己加/v1或者/chat/completions,很多 SDK 会自动拼路径,你手动加了就变成双份,请求直接 404。我试过在 Cursor 生成的代码里看到它把 Base URL 写成https://taotoken.net/api/v1,结果一直报路径错误,改回https://taotoken.net/api就好了。
另外,Key 不要写进manifest.json或者提交到 Git。扩展里正确做法是存在chrome.storage.local,配置页让用户自己填,或者你在本地调试时用chrome.storage.local.set临时写进去。生产环境更推荐让用户自己填 Key,避免你的 Key 泄露。
如果你打算长期用这个插件做编码类任务,比如让模型帮你生成分组规则甚至改代码,可以了解下 Coding Plan,地址是https://taotoken.net/coding-plan,它更适合高频调用场景。不过我们这个插件只是偶尔调一次聚类,用普通 API Key 就够了。
准备好这三件套之后,回到 Cursor 项目里,把.cursorrules和 Composer 提示词写清楚,让生成的代码直接按这个 Base URL 来发请求,省得后面手动改。
3. 可复制配置:Cursor 工程初始化与模型调用片段
这一节给你能直接复制的配置。先在 Cursor 里新建项目目录,比如auto-tab-plugin,然后在根目录建.cursorrules文件,把项目约束写进去。.cursorrules的作用是让 Cursor 生成的代码符合你的技术栈和接口规范,尤其是 Base URL 和 Key 的读取方式。
.cursorrules内容如下:
你是浏览器扩展开发专家,使用 Manifest V3。 项目使用原生 JS + HTML,不引入框架。 所有模型请求统一走 Base URL: https://taotoken.net/api API Key 从 chrome.storage.local 读取,字段名 taotoken_key。 Model ID 从 chrome.storage.local 读取,字段名 taotoken_model。 请求路径使用 /chat/completions,不要重复拼接 /v1。 分组逻辑使用 chrome.tabs.group 和 chrome.tabGroups。接着用Cmd+I唤起 Composer,输入需求提示词。提示词里把模型调用部分写明确,避免 Cursor 自己编一个 Base URL:
帮我实现一个浏览器扩展,Manifest V3,功能是对标签页按域名分组。 要求: 1. 配置页:输入域名、分组名称、颜色,保存后对已打开标签页分组。 2. 编辑页:展示分组规则列表,支持编辑和删除。 3. 后台 service worker:打开已配置域名时自动并入分组。 4. 对未配置域名,调用模型接口获取分组建议。 模型请求配置: - Base URL: https://taotoken.net/api - 路径: /chat/completions - Key 从 chrome.storage.local 的 taotoken_key 读取 - Model 从 chrome.storage.local 的 taotoken_model 读取 - 请求体使用 OpenAI 兼容格式生成项目后,Cursor 可能会给一个生成 icon 的脚本,依赖 canvas。按它提示装依赖:
npm i canvas -g node convert_icons.js然后重点看模型调用那段代码。如果 Cursor 生成的callModel函数里 Base URL 不对,直接改成下面这个版本。这是一个可以复制进background.js的请求片段:
async function callModel(domains) { const { taotoken_key, taotoken_model } = await chrome.storage.local.get([ "taotoken_key", "taotoken_model", ]); const resp = await fetch("https://taotoken.net/api/chat/completions", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${taotoken_key}`, }, body: JSON.stringify({ model: taotoken_model || "gpt-4o-mini", messages: [ { role: "system", content: "你是标签分组助手。根据域名列表给出分组建议,返回 JSON 数组,每项包含 domain、group、color。", }, { role: "user", content: JSON.stringify(domains), }, ], temperature: 0.2, }), }); if (!resp.ok) { const errText = await resp.text(); throw new Error(`model request failed: ${resp.status} ${errText}`); } const data = await resp.json(); return data.choices[0].message.content; }注意Authorization是Bearer加空格再加 Key,少一个空格就是 401。model字段填你在控制台确认过的 Model ID,不要留空。请求体里messages是标准 OpenAI 兼容格式,TaoToken 的接口按这个格式收。
配置页里加两个输入框,让用户填 Key 和 Model,保存时写进 storage:
await chrome.storage.local.set({ taotoken_key: keyInput.value.trim(), taotoken_model: modelInput.value.trim(), });这样三件套就齐了:Base URL 写死在请求里,Key 和 Model ID 从 storage 读。后面验证请求时,如果报错,先检查这三个值。
4. 验证请求与分组准确率:本地加载与实测清单
代码写完后,先在浏览器里加载本地插件。打开chrome://extensions,右上角开启开发者模式,点“加载已解压的扩展程序”,选项目根目录。加载后看扩展卡片有没有红色报错,有的话点“错误”看详情,通常是manifest.json字段写错或者 service worker 语法错误。
加载成功后,点扩展图标打开配置页,填入 TaoToken 的 Key 和 Model ID,再新建一条分组规则,比如域名github.com、分组名Dev、颜色blue,点保存。这时候已打开的 GitHub 标签页应该被收进一个蓝色分组。如果分组成功但颜色没生效,是chrome.tabGroups.update没调用或者颜色值不在允许列表里,允许值包括grey、blue、red、yellow、green、pink、purple、cyan、orange。
验证模型请求是否通,可以在配置页加一个“测试模型”按钮,点击后调用callModel(["example.com"]),看返回内容。如果返回正常,说明 Base URL、Key、Model ID 三件套没问题。如果报 401,检查 Key 有没有多余空格;如果报local proxy failed,检查 Base URL 是不是被改成了本地地址;如果报reading choices相关错误,说明返回结构不是预期的choices[0].message.content,打印完整响应看看。
分组准确率的验证可以这样操作:
- 准备 10 个不同域名的标签页,其中 6 个属于你配置过的分组,4 个未配置。
- 触发自动分组,看已配置的 6 个是否全部进组,颜色是否正确。
- 对未配置的 4 个调用模型聚类,看建议分组是否合理。
- 手动改一条规则,看已分组标签页是否同步更新。
- 删除一条规则,看对应分组是否解散。
我实测下来,模型聚类对技术类域名的建议基本可用,但偶尔会把docs.xxx.com和xxx.com分成两组,因为模型只看域名字符串。解决办法是在提示词里加一句“子域名归入主域名分组”,或者在代码里先做一次域名归一化,把docs.前缀去掉再发给模型。
还有一个坑是已打开标签页多次分组。如果同域名标签页在分组后又被打开,后台脚本会再次调用分组,可能导致分组重复或者标签页跳动。处理方式是在分组前先查chrome.tabGroups.query,看该分组是否已存在,存在就只把新标签页加进去,不重建分组。
5. 常见报错排查:401、local proxy failed、reading choices
这一节把几个高频报错对照着说,都是我在调试时真实遇到的。
401 Unauthorized。最常见原因是 Key 不对或者格式不对。检查Authorization头是不是Bearer sk-xxx,中间一个空格。如果 Key 是从控制台复制的,注意有没有把前后空格带进去。还有一种情况是 Key 被禁用或者额度用完,去控制台看下状态。
local proxy failed。这个报错通常出现在你把 Base URL 改成了本地地址,比如http://localhost:xxxx,但本地服务没起来。我们这个插件里 Base URL 应该是https://taotoken.net/api,不要改成任何本地地址。如果你在 Cursor 生成的代码里看到它自动填了http://127.0.0.1,直接替换掉。
Cannot read properties of undefined (reading 'choices')。这说明返回的 JSON 里没有choices字段。可能原因有三个:一是请求路径不对,比如拼成了/api/v1/chat/completions,实际应该是/api/chat/completions;二是 Model ID 填错,接口返回了错误对象;三是响应被拦截或者返回了 HTML。排查方法是把resp.text()打印出来,看原始返回是什么。
OAuth 相关报错。如果你在 Cursor 里配置了某些需要 OAuth 的模型接入方式,可能会看到 token 过期或者回调失败。我们这个插件用的是 API Key 方式,不涉及 OAuth,所以如果你看到 OAuth 报错,说明代码里混入了别的接入逻辑,检查callModel函数是不是被 Cursor 改成了别的认证方式。
分组颜色不生效。前面提过,颜色值必须在允许列表里。另外chrome.tabGroups.update要在分组创建之后调用,顺序反了颜色不会应用。
规则列表加载不出来。编辑页读 storage 时字段名要对上,配置页写的是rules,编辑页就得读rules。如果 Cursor 生成的代码里一个用groupRules一个用rules,就会读不到。统一字段名即可。
自动分组不触发。Manifest V3 的 service worker 是事件驱动的,chrome.tabs.onUpdated监听要注册在顶层,不能放在异步回调里。检查background.js里监听器是不是直接写在最外层。
6. 继续用 Cursor 迭代这个插件的建议
插件跑通之后,你可以继续用 Cursor 加功能。比如把分组规则同步到云端,或者加一个“一键整理所有窗口”的按钮。每次改需求时,把.cursorrules里的 Base URL 和 Key 读取方式保留,Cursor 就不会乱改接口配置。
如果你后面要接更多模型能力,比如让模型直接读标签页标题做语义聚类,记得 Model ID 换成支持长文本的模型,请求体里把标题拼进messages。TaoToken 的 API Key 和 Base URL 不用变,只改 Model ID 就行。需要看接口细节可以去接入文档,地址是https://taotoken.net/doc。如果你更习惯在 Claude Code 这类工具里写代码,也可以参考https://taotoken.net/claude-code-anthropic的接入方式,思路是一样的:Base URL 加 Key 加 Model ID。
最后提醒一句,扩展里调用模型接口是异步的,分组操作也是异步的,注意用await串起来,不然会出现规则还没保存就去分组的情况。把这两段异步逻辑理清楚,这个插件的稳定性就差不多了。