拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Chrome侧边栏AI扩展:sidePanel+iframe实现多模型并排对比

1. 为什么我最终选择在侧边栏里同时塞进 6 个 AI先说痛点我之前的工作状态估计很多重度 AI 用户都有共鸣桌面上开着五六个标签页分别是 ChatGPT、Claude、Gemini、Kimi、文心一言、通义千问之类的对话窗口来回切换比对答案。有时候为了验证一个问题的不同回答我得在标签页之间点来点去光是找对应窗口就得花掉好几秒更别提一不留神关错标签页上下文全丢了。让人烦心的不只是切来切去而是当你真正想对比多个 AI 回答时多标签页模式本质上是不适合的——因为一次只能看到一个窗口。所以我做了这个开源项目一个 Chrome 侧边栏扩展把 6 个主流 AI 服务整合进侧边栏支持一键切换还能同时以并排分栏的方式呈现多个 AI 的回复。这个项目并不是什么高深莫测的黑科技它依赖的核心能力是 Chrome 自带的chrome.sidePanelAPI。但这玩意儿从 Chrome 114 开始才稳定支持到现在依然有很多人没意识到它能把侧边栏玩出花来。我的项目做的事情很简单在浏览器侧边栏里实现一套可插拔的 AI 多路面板默认配置接入 6 个公开可访问的 AI Web 应用每个以独立 iframe 方式加载互不干扰。然后通过面板顶部的切换器你可以瞬间把一个 AI 切换成另一个也可以拖动分栏把手让两个、三个甚至更多 AI 并排同时出现在侧边栏里。适合谁来用一是跟我一样的 AI 重度用户——每天要面对不同模型回答同一问题的场景二是做 Prompt 对比、模型评估、竞品分析的产品或技术同学三是想学习 Chrome 扩展开发、特别是研究sidePanelAPI 和 iframe 多实例隔离机制的开发者。这项目代码量不大结构清晰我尽量做了详细注释改造成本很低。在往下展开之前先说个前提如果你本身对 AI 服务一无所知也完全不会写代码这个扩展你也能直接安装使用——它只是个壳负责把网页加载到侧边栏里不涉及任何代理和转发所有 AI 服务的账号体系、聊天记录都还是你自己的。这一点很重要后面我会详细解释为什么这么设计。2. 技术选型复盘sidePanel API、iframe 隔离与 Manifest V3 的取舍2.1 为什么是 sidePanel 而不是 action popup 或 new tab我先对比了三条路线默认弹窗action.popup、新标签页、侧边栏。不做 popup 的原因很简单——它本质是个临时浮层鼠标一移开就没了AI 对话动辄要滚动阅读长回复体验非常差。新标签页又回到了多标签页混乱的老路而且占用的屏幕空间太大干扰主工作流。侧边栏是最好的折中它常驻、可折叠、不遮挡主页面宽度可以自定义正好适合对话这种纵向信息密度高、横向要求不高的内容形态。Manifest V3 已经是 Chrome 扩展的唯一标准这没什么好纠结的。需要提的是chrome.sidePanel在 MV3 里并非默认开放需要在manifest.json中显式声明sidePanel权限并且面板页面路径要配置在side_panel.default_path字段中。另外不同 Chrome 版本的 API 行为有些微差别比如早期版本面板打开后无法通过 JS 控制关闭只能靠用户手动点击浏览器自带的关闭按钮后来版本才补充了sidePanel.close()这类方法。我建议在开发时锁定一个较新的 Chrome 版本我用的是 126避免踩到旧版本 API 不全的坑。2.2 manifest.json 里必须注意的几个配置项配置本身并不复杂但有三个地方很容易被忽视我直接把示例贴出来{ manifest_version: 3, name: Multi AI Sidebar, version: 0.1.0, permissions: [sidePanel, storage, tabs], host_permissions: [ https://chat.openai.com/*, https://claude.ai/*, https://gemini.google.com/* ], background: { service_worker: src/background.js }, side_panel: { default_path: src/sidepanel.html }, action: { default_title: 打开 AI 侧边栏 } }你没看错我没有用default_popup而是让点击扩展图标后走 background 脚本去调sidePanel.open()。为什么要绕一步因为sidePanel.open()必须由用户手势触发且最好在action.onClicked事件里调用这样才能保证浏览器把点击图标当作合法手势否则很多版本会静默失败。这是个非常隐蔽的坑我一开始直接把default_popup和侧边栏混用结果弹窗和侧边栏同时出现关了弹窗侧边栏也怪怪的。后来彻底砍掉 popup只保留图标点击触发侧边栏逻辑干净多了。还有一个容易忽略的点side_panel字段配置的默认面板是全局的但你可以通过chrome.sidePanel.setOptions()给特定标签页设置面板路径也就是说同一个扩展在不同标签页可以显示不同的 UI。我在做多 AI 并排功能时就用到了这个能力——在并排模式下面板会加载一个多栏布局的 HTML而普通模式只加载单栏页面两者通过 setOptions 切换。动态改面板路径这个能力文档里没有大篇幅讲实际用起来很顺手在一个扩展兼容多种形态的需求里非常值得尝试。2.3 iframe 三实例隔离背后的存储分区机制这个项目的核心难点不在 API 调用而在如何让多个 AI Web 应用共存于同一个侧边栏页面。直接把六个服务的网址通过iframe src...塞进去你会发现三个典型问题Cookie 共享导致登录态互踢、LocalStorage 冲突、以及某些站点通过frame-ancestors或 CSP 头阻止被嵌入。第一个问题最致命。像 ChatGPT 和 Claude 这类服务如果它们恰好共用了同一域名的存储虽然实际不太会发生但比如同一套 SSO 体系下的子产品互相挤掉登录态就很难受。更普遍的情况是这些 AI 平台会通过第三方 Cookie 跟踪用户嵌入在同一个页面里时浏览器可能统一拦截导致某些平台点击登录后无反应。解决方案是给每个 iframe 指定独立的存储分区iframe srchttps://chat.openai.com partitiontrusted-openai/iframe iframe srchttps://claude.ai partitiontrusted-claude/iframe这个partition属性划定了 iframe 的存储分区从 Chromium 92 开始支持。简单理解就是每个分区相当于一个独立的浏览器配置文件Cookie、localStorage、Cache 都互相隔离谁也不会污染谁。但注意这并不能解决 CSPframe-ancestors的限制——如果目标站点在响应头里明确声明禁止被嵌入那无论如何都嵌不进去。关于这一点我的处理方式是能嵌就嵌不能嵌就走代理模式。在项目的config里给每个 AI 服务加了一个embedMode字段值为iframe还是proxy。对于 CSP 限制严格的服务走扩展 background 里发起的 fetch 请求把目标页面作为普通 HTTP 资源拉回来再渲染。这等于自己实现了一个轻量代理——但只用于绕过框架限制不修改响应内容、不做注入确保本质上还是访问原站。这是项目里最复杂的一部分我放在第 4 章单独讲。3. 三个核心击破点一键切换、并排对比、Prompt 自动分发3.1 一键切换的交互逻辑和 Tab 状态记忆标题里说的一键切换实现上并没有用很高深的技术。我在侧边栏顶部设计了一排图标按钮每个对应一个 AI 服务。点击某个按钮时主内容区切换到对应的 iframe 显示。同时我用chrome.storage.session记录当前激活的服务 id这样哪怕你折叠了侧边栏再展开它也能恢复成上次用的那个 AI不需要重新找。这部分看似简单但优化空间藏在细节里懒加载六个 iframe 如果全部同时渲染侧边栏一打开内存直接飙升甚至会出现掉帧。所以我只在第一次点击某服务时才创建 iframe创建后保留 DOM 节点不再销毁这样后续切换只是 display 显隐无需重新加载页面。加载状态AI Web 应用动辄几百 KB 的 JS 资源首次加载需要时间。我在 iframe 上方盖了一个 loading 层监听 iframe 的load事件来隐藏它。实际测试下来 GPT 系页面加载大概 2~3 秒Claude 稍快Gemini 有时候能到 5 秒以上没有 loading 提示用户会以为扩展坏了。已打开状态标识每个图标右下角有个小圆点绿点代表这个 AI 已经初始化过、可以秒切灰点代表还没打开过点击会有首次加载等待。这个小细节用户反馈很好降低了不确定感。切换逻辑的核心代码大致是async function switchToProvider(providerId) { const provider PROVIDERS.find(p p.id providerId); if (!provider) return; if (!provider.iframeCreated) { const iframe document.createElement(iframe); iframe.src provider.url; iframe.partition trusted-${provider.id}; iframe.className ai-frame; iframe.dataset.providerId provider.id; mainContent.appendChild(iframe); provider.iframeCreated true; } document.querySelectorAll(.ai-frame).forEach(f { f.style.display f.dataset.providerId providerId ? block : none; }); await chrome.storage.session.set({ activeProvider: providerId }); updateActiveIndicator(providerId); }3.2 并排模式分栏容器与等比缩放陷阱多 AI 并排是我自己最常用的功能。点一下顶部的分栏按钮侧边栏会从单栏切换为多栏 Grid 布局。在 Grid 容器里每个被加载过的 iframe 占据一列所有列宽度相等。由于侧边栏本身不宽我通常开 2~3 栏再多的话每个 AI 的实际可读宽度不足 300px对话排版会变得很怪。这里有一个必须要处理的细节iframe 内容有自己的 CSS 媒体查询过窄的视口会触发移动端布局。比如 Claude 在 400px 以下宽度时会切换到窄屏样式字体变大、按钮堆叠反而更占空间。这没法从根本上禁止因为样式在对方站点控制范围内但可以用transform: scale()做等比缩放入一个容器里视觉上模拟出完整桌面布局缩小后的效果。我试过这个方法副作用是缩放后的事件坐标要对齐处理起来有点划不来所以最后放弃接受了窄屏布局、并在分栏时给每个容器加了最小宽度禁下限。如果确实需要多个 AI 在同一屏精确对比不如把侧边栏拉宽到 800px 以上这个场景下三栏并排的体验是能接受的。并排模式的精髓在于你可以左半个屏幕看 ChatGPT 的回答右半个屏幕看 Claude 的回答对照阅读。而不需要像以前那样在标签页之间来回跳。结合下一节说的 Prompt 同步这个功能真正解决了我用一个问题时总想知道别的模型怎么看的执念。3.3 Prompt 一键分发到所有 AI自动化模拟输入这是我做这个项目时临时加的功能结果成了用户最常夸的一点。并排模式下如果你想给 6 个 AI 同时提同一个问题手动一个个粘贴太折磨人。于是我加了一个同步输入模式在主 AI当前激活的那个输入框里输入内容时其余 AI 的输入框也会自动填入相同内容。实现思路不复杂在主 iframe 内注入一个内容脚本监听输入框的input事件拿到用户输入的值后通过postMessage传给侧边栏的父页面父页面收到后再分发给其他 iframe 内的内容脚本由它们设置对应输入框的值并触发input事件让前端框架感知更新。这里有两个坑要注意不同站点的输入框选择器完全不同ChatGPT 用的是#prompt-textareaClaude 是div[contenteditabletrue]Gemini 又不一样。所以选择器必须每个服务单独配置。React/Vue 等框架的受控组件不认直接赋值如果你只设置input.value而不触发原生input事件框架内部状态根本不会更新。正确姿势是先执行nativeInputValueSetter修改队列值再派发new Event(input, { bubbles: true })。这段代码我统一封装成一个setNativeValue函数是 Cant 不能省的。Prompt 同步本身不是新概念但在侧边栏多 AI 场景下它把并排对比的使用效率又提高了一个台阶。否则并排只是视觉上的排列操作上还是一切割裂的。4. 绕不开的那些硬骨头跨域、登录态与 CSP 限制的破解路径4.1 跨域约束为什么说纯前端 iframe 永远做不到 100% 通吃必须先给读者打个预防针不经过后端代理终究会有网站嵌不进来。道理不复杂现代网站普遍带X-Frame-Options或 CSPframe-ancestors响应头浏览器看到这些头就会拒绝 iframe 加载。这是站点安全策略从产品设计上是防点击劫持的不能一概视为恶意的封锁。在我的项目里6 个默认 AI 服务中能用纯 iframe 嵌入的大概只有一半剩余的就需要走代理模式。代理模式做的事让 background script 向目标站点发起 fetch拿到 HTML 响应后改写内部的相对链接为绝对链接再把改写后的 HTML 塞进 iframe 的srcdoc或直接渲染。听起来有戏但执行起来会发现一个接一个的问题很多站点的 HTML 里有大量相对路径比如/static/chunk.js必须补全为https://domain/static/chunk.js否则子资源全挂。动态加载的脚本或 API 请求往往带有校验 token这些 token 在服务端渲染时写进了页面重写后依然有效但也可能因为 CSRF 校验而失败。后续 XHR/fetch 请求如果触发了 CORS 预检而扩展的 origin 不是目标站点 origin就会被浏览器拦下。所以代理模式是一种尽力而为的方案我明确把它定位为 fallback不承诺所有动态交互都 100% 可用。好在这类 AI Web 应用最核心的发消息、看回复行为通常涉及 websocket 或流式接口一旦 CORS 卡住就容易失败这也是某些服务最后依然需要用户在专门的标签页打开的原因之一。诚实地说目前六个服务全部能跑通主流程少数冷门功能比如文件上传偶尔要回主站操作。4.2 登录态维护靠独立分区不如直接在正常窗口登录很多人在初次使用我这个项目时会遇到侧边栏里没登录要我扫码/输入密码的情况然后产生质疑我不是刚在主窗口登录过吗原因我在 2.3 节提到过——iframe 的存储分区默认不允许访问父页面的 Cookie。打开一个新的 AI 面板 iframe它内部是全新的存储环境相当于第一次来这个站点自然没登录态。这里有一个我踩了不少坑才理清的决策点要不要给 iframe 设置allowsame-origin并试图共享会话不同站点策略不同有些服务允许第三方上下文访问主站 Cookie但越来越多的站点把 Cookie 设为SameSiteLax甚至SameSiteStrict跨 iframe 上下文里根本带不过去。与其跟浏览器较劲不如让用户直接在主窗口打开一次目标站点并保持登录然后扩展侧边栏里的 iframe 通过partition分区的机制登录一次并让浏览器记住该分区的 Cookie。实测这样最稳定——首次在侧边栏打开 ChatGPT 时会弹出登录页登录完成后 Cookie 保存在该分区里之后长期有效不必反复登录。如果用户连这一步都嫌麻烦那还有一条终极省事路线在扩展的options里打开无分区模式这时 iframe 不设partition它会直接走父页面相同站点上下文主窗口登录过就能直接用。代价是各 AI 服务之间可能产生 Cookie 干扰但从我测试看六个默认服务之间基本没有域名重叠所以这种模式实际用起来问题也不大。两种模式我用一个开关切换默认推荐独立分区省心不打架。4.3 后端中转服务一个可选的轻量 Node 转发层为了给技术背景更强的用户留一条后路项目里还带了一个完全可选的 Node.js 中转服务server/目录。它做的事情很简单接收扩展端发来的请求携带目标 AI 服务的 URL 和请求体由 Node 服务端发起请求等到完整响应后再原样返回给扩展。因为请求是从服务端发出的不存在浏览器 CORS 限制也绕过了大部分frame-ancestors问题——注意我这里的绕过指的是服务端代理不是修改响应头欺骗站点。这个中转层的代码量不大核心就几十行const express require(express); const { createProxyMiddleware } require(http-proxy-middleware); const app express(); app.use(/proxy/*, createProxyMiddleware({ target: https://target-ai-service.example.com, changeOrigin: true, pathRewrite: (path) path.replace(/^\/proxy/, ), onProxyReq(proxyReq, req, res) { proxyReq.setHeader(User-Agent, req.headers[user-agent] || Mozilla/5.0); proxyReq.setHeader(Cookie, req.headers[cookie] || ); }, onError(err, req, res) { res.writeHead(502, { Content-Type: text/plain }); res.end(Proxy error: err.message); } })); app.listen(8787, () { console.log(AI sidebar proxy listening on http://localhost:8787); });但我要真诚地提醒一句启用中转意味着所有对话内容会经过你自己搭的服务器存在隐私泄密的额外风险。所以我默认关闭这个能力只开放给能自行部署、了解风险的技术用户。大多数情况下纯 iframe 独立分区已经够用来完成核心功能。5. 开源项目实战指南从克隆到接入你自己的 AI 服务5.1 代码结构和二次开发入口项目目录结构刻意保持精简方便二次开发multi-ai-sidebar/ ├─ manifest.json ├─ src/ │ ├─ background.js │ ├─ sidepanel.html │ ├─ sidepanel.js │ ├─ providers/ │ │ ├─ config.js # 所有 AI 服务配置URL、方式、选择器 │ │ ├─ iframeController.js │ │ └─ promptSync.js │ └─ lib/ │ └─ domUtils.js ├─ server/ │ └─ proxy.js └─ docs/ └─ CUSTOM_PROVIDER.md想接你自己公司内部的 AI 服务或者换成其他公开服务只需要改providers/config.js。配置字段包括id唯一标识必须是英文小写短横线name显示在图标上的名字url目标 AI 服务的地址embedModeiframe或proxypartitionKey存储分区键inputSelector主输入框的 CSS 选择器submitSelector发送按钮的选择器拿添加一个新的服务举例假设你想接入某内部 AI 助手它没有 CSP 限制能直接 iframe 嵌入配置大概是{ id: internal-ai, name: 内部助手, url: https://internal-ai.example.com, embedMode: iframe, partitionKey: trusted-internal, inputSelector: #chat-input, submitSelector: #send-btn }改完配置在sidepanel.html的图标栏里加一个对应的button即可。整个流程不涉及编译打包改完刷新扩展就能看到新图标。这个简明接入方式对非前端背景的人也友好基本就是复制粘贴改字段。5.2 加载未打包扩展的步骤与调试技巧本地开发调试时不需要提交到 Chrome 应用商店也不需要付费开发者账号直接加载已解压的扩展程序。步骤是打开chrome://extensions/右上角开启开发者模式左上角点加载已解压的扩展程序选择项目根目录即可。有几个调试工具值得提前了解不然出问题会非常抓瞎service worker 调试点击扩展卡片上的 service worker 链接打开 background 的 DevTools能看到所有后台日志。侧边栏面板的 DevTools在侧边栏页面上右键选择检查可以调试面板自身的 DOM、网络请求。主要看 Network 面板iframe 内的请求会展示在面板的 Network 中重点看有没有 CORS 报错、302 跳转异常、403 或 401 状态码。遇到 401 或 403基本就是登录态或 Cookie 隔离问题遇到Refused to connect就是 CSP 限制考虑改用 proxy 模式。调试过程里我个人最常用的技巧是在background.js里加一段监听所有tabs.onUpdated的日志确认每个 iframe 的实际加载 URL 和最终状态。因为有些服务会在加载后做几次 302 跳转比如从chat.openai.com跳到auth.openai.com再跳回你不看跳转链路就永远想不通为什么白屏。5.3 我对项目做过的两个性能优化最后补两个我实测有效的性能优化点。第一个是iframe 预连接。第一次打开侧边栏时六个服务的域名都在后台触发chrome.tabs.create({ url: provider.url, active: false })进行预热吗不是的这样会真的创建不可见标签页非常愚蠢。正确做法是用chrome.networking相关的 API 做 DNS 预解析可惜扩展里没有这个 API。我的方案退而求其次只在用户点击图标时先快速创建 iframe 但不放在 DOM 里靠preload方法触发资源加载。这样做后首次点击的加载感知时间大约缩短 30%。效果不算惊艳但聊胜于无。第二个优化是内存回收。长时间开着侧边栏6 个 iframe 加上各自内部繁重的 JS 运行内存占用能达到 1.5GB 以上这在小内存机器上会有明显卡顿。我的策略是切到某个 iframe 后如果超过 15 分钟没有操作就临时把它的src设为about:blank并从 DOM 里移除下次点图标时重新加载。这等于降级保留——你有记录但页面不驻留换取内存腾退。折中的结果是内存占用被控制在 400MB 以内对主流电脑压力不大。为了防止用户觉得怎么又要重新加载我在对应图标上保留一个半透明的蓝点提示已休眠点击恢复。6. 真实体验一个月后的总结与几个必须说的注意事项这个项目断断续续用了有一个多月我自己的实际体验是单一 AI 的日常咨询我基本都在侧边栏里完成了不需要打开完整标签页。主窗口保持干净只干正经活AI 对话放在侧边栏随时调用、随时收起。对比多个模型回答时开并排模式并启用 Prompt 同步一次提问拿六个回答效率非常高。客观讲这也带来一个副作用选择变多反而更容易陷入缝合答案的纠结一件事情问六个模型其实大多数时候结论高度一致真正值得对比的通常只有观点类、代码 bug 类、文本润色类这三类场景。几个要跟后来者明确交代的注意事项存储分区的登录态不等于安全隔离。partition只是隔离数据不代表这些网站无法通过浏览器的其他途径获取你的指纹信息。别以为侧边栏打开 AI 服务就跟主站完全无关了。Prompt 同步不保证在所有 AI 上输出完全一致。不同模型的输入框可能有多行占位符、Markdown 快捷指令等差异我尽量做了兼容但依然有用户反馈某些特殊字符比如较长代码块粘贴后被目标站点截断。如果你每天要用这个功能请尽量让 Prompt 控制在 3000 字以内超过这个长度建议手动确认一遍。扩展的更新与稳定性之间需要取舍。作为开源项目我会持续跟进各家 AI Web 应用的 DOM 结构变化更新选择器和配置。但 Web 前端的变动属于常态不可避免会有某一天某个 AI 服务改版后同步输入失效。遇到这种情况打开扩展的 issues 页面报告一下我通常会在几个工作日内修复。最后分享一个小技巧在并排模式下如果某个 AI 的回复特别长、挤占了其他 AI 的可视空间可以双击它的标题栏单独放大该栏、其他栏隐藏再次双击恢复并排。这个小功能代码只有十来行但几乎每天都会用到。以我的个人习惯这个项目最大的价值并不是能嵌 6 个 AI而是让我从标签页焦虑里解放出来更专注地思考问题本身。建议下载源码后先不改任何代码直接加载运行感受一下侧边栏 AI 工作流是否适合你再决定要不要改造。如果你有其他想接入的服务或者遇到什么奇怪的嵌入问题欢迎在项目仓库的 issues 里交流。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门