跨模型提示词管理工具:浏览器扩展实现与优化 1. 为什么我要自己动手做一个跨模型提示词工具事情的起因很朴素我每天要在 ChatGPT、Claude、Gemini 三个模型之间来回切换。写代码用 Claude查资料用 ChatGPT处理长文档和图片理解用 Gemini。问题不在于模型本身而在于我积累的那套提示词——它们散落在各个平台的对话框里、备忘录里、Notion 里甚至微信收藏里。每次换一个模型我都要重新复制粘贴一遍格式还经常乱掉。更麻烦的是同一个提示词在不同模型上的表现差异很大。ChatGPT 对结构化 Markdown 的响应很稳Claude 更吃角色设定 约束条件这一套Gemini 对超长上下文和图文混排更友好。我手里那几十条经过反复打磨的提示词如果只是简单复制等于把三个模型的优势全浪费了。市面上不是没有提示词管理工具但大多数要么绑定单一平台要么是云端 SaaS 需要登录、同步、付费要么就是纯文本库没有变量替换和版本管理。我想要的其实很简单一个浏览器扩展点开就能用提示词存在本地支持变量占位符能一键注入到当前打开的任意模型页面里并且针对不同模型自动做格式适配。这就是这个项目的由来。它不是一个商业产品是我给自己做的一个效率工具但做完之后发现它解决的是很多 AI 重度用户共同的痛点。下面我把整个设计思路、技术选型、踩过的坑和实际使用效果完整拆一遍如果你也在多模型之间反复横跳这套方案可以直接抄。2. 跨模型提示词管理的真实痛点拆解2.1 提示词不是存起来就完事了大多数人管理提示词的方式是建一个文档按类别分好用的时候搜索复制。这个方式在单模型时代够用但跨模型场景下会暴露三个问题。第一是格式漂移。你在 ChatGPT 里调好的提示词里面用了###标题和-列表复制到 Claude 的输入框里Claude 对 Markdown 的解析逻辑和 ChatGPT 不完全一样有时候它会把###当成正文内容而不是结构标记。Gemini 更特殊它对超长提示词的截断策略和前两者不同如果你把最重要的指令放在末尾可能直接被截掉。第二是变量缺失。我有很多提示词是模板化的比如你是一个资深 {语言} 工程师请 review 以下代码{代码}。每次用都要手动替换{语言}和{代码}替换完还要检查有没有漏。如果提示词里有五六个变量手动替换的出错率非常高。第三是版本混乱。同一条提示词我可能在 ChatGPT 上改了一版在 Claude 上又改了一版最后自己都分不清哪个是最新的。没有版本记录改坏了想回退都回不去。2.2 三个模型的脾气差异到底在哪要做一个跨模型工具必须先搞清楚三个模型在提示词处理上的差异。我花了大概两周时间用同一批提示词在三个模型上做了对照测试结论如下。维度ChatGPTClaudeGemini角色设定敏感度中等system 和 user 分离清晰高角色描述越具体效果越好中等偏好自然语言描述结构化格式对 Markdown 响应最稳定对 XML 标签和缩进敏感对纯文本段落响应更好长提示词处理中段内容容易遗忘长上下文保持较好超长上下文优势明显指令位置偏好开头和结尾都有效开头设定 结尾强调效果最佳开头集中说明效果最好变量占位符不识别需手动替换不识别需手动替换不识别需手动替换这张表是我实测出来的不是官方文档抄的。比如 Claude 对 XML 标签的敏感度是我在测试用context包裹背景信息时发现的它对这个结构的遵循度明显高于纯 Markdown。而 Gemini 在超过一定长度后对末尾指令的遵循度会下降所以重要约束要前置。这些差异直接决定了工具的设计方向不能只做一个提示词仓库必须做格式适配层。同一条提示词注入到不同模型时要自动做格式转换。2.3 为什么选择浏览器扩展而不是桌面应用技术形态的选择上我考虑过三种方案桌面应用、云端 Web 应用、浏览器扩展。桌面应用的问题是需要单独安装、单独更新而且和浏览器的交互有隔阂复制粘贴还是要走剪贴板。云端 Web 应用的问题更明显提示词是我的核心资产放在别人的服务器上我不放心而且每次用还要切窗口、登录、复制操作链路太长。浏览器扩展的优势在于零切换成本。我本来就在浏览器里用这三个模型扩展直接注入到当前页面点一下就能把提示词填进输入框不需要离开当前上下文。数据存在chrome.storage.local里完全本地不经过任何服务器。更新也简单自己加载解压后的扩展就行。唯一的代价是扩展的权限模型和页面注入需要处理一些兼容性问题这个后面会详细讲。3. 工具的核心架构与数据模型设计3.1 整体架构三层分离整个扩展的架构我拆成了三层这样每一层的职责清晰后续维护和扩展都方便。第一层是存储层负责提示词的增删改查和版本管理。数据存在chrome.storage.local结构是一个数组每条记录包含 id、标题、内容、变量定义、标签、适用模型、版本历史、创建和更新时间。用 local 而不是 sync是因为 sync 有容量限制约 100KB而且同步冲突处理很麻烦。local 的容量是 5MB 起步对纯文本提示词来说完全够用。第二层是适配层这是整个工具的核心。它接收一条原始提示词和目标模型类型输出适配后的文本。适配逻辑包括Markdown 转 XML针对 Claude、长提示词分段重排针对 Gemini、变量替换、以及模型特定的前缀后缀注入。第三层是注入层负责把适配后的文本填进目标页面的输入框。这一层要处理三个模型页面不同的 DOM 结构以及 React 受控组件的事件触发问题。三层之间通过消息传递通信content script 负责注入层popup 和 background 负责存储层和适配层。这样即使某个模型页面改版导致注入失败也不会影响提示词的管理和编辑。3.2 提示词的数据结构长什么样一条提示词在存储里的完整结构是这样的{ id: prompt_1720000000000, title: 代码审查专家, content: 你是一个资深 {language} 工程师请从以下角度审查代码\n1. 边界条件\n2. 异常处理\n3. 性能隐患\n\n代码\n{code}, variables: [ { name: language, label: 编程语言, default: Python }, { name: code, label: 待审查代码, default: } ], tags: [编程, 代码审查], targetModels: [chatgpt, claude, gemini], versions: [ { timestamp: 1720000000000, content: ... } ], createdAt: 1720000000000, updatedAt: 1720000000000 }这里有几个设计决策值得说明。变量用{name}而不是{{name}}或${name}。原因是三个模型的输入框里{{}}和${}有时会被模型当成模板语法或代码产生意外解析。单层花括号在自然语言提示词里出现频率低冲突概率小。变量定义单独存一个数组是为了在 UI 上生成对应的输入框用户填完值再替换而不是让用户直接在文本里改。版本历史只存内容快照不存完整对象。因为标题、标签这些元数据很少变真正需要回退的是内容本身。每次保存时如果内容有变化就往 versions 里 push 一条最多保留 20 个版本超出就删最旧的。这样既控制了存储体积又保证了可回退。targetModels 字段决定适配策略。如果一条提示词只勾选了 Claude那注入到 ChatGPT 页面时工具会提示此提示词未针对当前模型优化但仍然允许注入。这个设计是为了避免用户被限制死同时给出提醒。3.3 适配层的转换规则是怎么定的适配层的核心是一组转换函数输入是原始内容和目标模型输出是适配后的内容。我定义了三套规则。针对ChatGPT的规则最简单保持 Markdown 原样但在开头加一行[角色设定]结尾加一行[请严格遵循以上要求]。这是因为 ChatGPT 对首尾指令的遵循度最高中间内容容易被稀释。针对Claude的规则是把 Markdown 的###标题转成section title...标签把-列表转成item标签整体用instructions包裹。实测下来 Claude 对 XML 结构的遵循度比 Markdown 高不少尤其是当提示词里有多个并列要求时。针对Gemini的规则是如果内容超过 2000 字把核心指令提取出来放到最前面用【核心要求】标记其余内容作为背景放在后面。Gemini 对超长上下文的处理能力强但对末尾指令的遵循度会随长度下降所以前置关键约束。这三套规则不是拍脑袋定的是我用同一批提示词在三个模型上跑了对照实验看哪个版本的输出质量最稳定然后固化下来的。当然这些规则会随着模型更新而变化所以适配层做成了可配置的规则写在单独的配置文件里改起来不用动主逻辑。4. 浏览器扩展开发中的关键实现细节4.1 manifest v3 下的权限与注入策略现在 Chrome 扩展必须用 manifest v3这对注入逻辑影响很大。v2 时代可以用chrome.tabs.executeScript动态注入v3 改成了chrome.scripting.executeScript而且需要在 manifest 里声明host_permissions。我的 manifest 关键部分是这样的{ manifest_version: 3, permissions: [storage, activeTab, scripting], host_permissions: [ https://chatgpt.com/*, https://chat.openai.com/*, https://claude.ai/*, https://gemini.google.com/* ], content_scripts: [ { matches: [ https://chatgpt.com/*, https://claude.ai/*, https://gemini.google.com/* ], js: [content.js], run_at: document_idle } ] }这里有个坑host_permissions和content_scripts.matches要一致否则会出现扩展有权限但 content script 没注入的情况。另外run_at用document_idle而不是document_start是因为三个模型的页面都是 SPA输入框是异步渲染的太早注入找不到目标元素。还有一个细节ChatGPT 的域名从chat.openai.com迁移到了chatgpt.com两个都要写上否则老用户的书签会失效。Claude 用的是claude.aiGemini 是gemini.google.com这三个是目前稳定的入口。4.2 往 React 受控输入框里塞文本的正确姿势这是整个项目里最折腾的部分。三个模型的输入框都是 React 受控组件你直接改textarea.value或者div.innerTextReact 的 state 不会更新看起来文本进去了一发送就发现是空的。正确的做法是模拟原生输入事件。对于textarea和input元素要用nativeInputValueSetter来绕过 React 的 value 拦截function setNativeValue(element, value) { const valueSetter Object.getOwnPropertyDescriptor( element.__proto__, value ).set; const prototype Object.getPrototypeOf(element); const prototypeValueSetter Object.getOwnPropertyDescriptor( prototype, value ).set; if (valueSetter valueSetter ! prototypeValueSetter) { prototypeValueSetter.call(element, value); } else { valueSetter.call(element, value); } }然后手动派发input事件const event new Event(input, { bubbles: true }); element.dispatchEvent(event);对于contenteditable的 divClaude 和 Gemini 用的是这种逻辑又不一样。需要先 focus 元素然后用document.execCommand(insertText, false, text)来插入文本。这个 API 虽然被标记为废弃但在 contenteditable 场景下仍然是兼容性最好的方案。实测下来ChatGPT 用的是textareaClaude 用的是contenteditable的 divGemini 也是contenteditable。所以注入层要针对三种情况分别处理不能一套代码走天下。4.3 变量替换的时机与校验变量替换看起来简单其实有个时机问题。如果用户在 popup 里填了变量值点注入这时候替换是同步的没问题。但如果提示词里有变量没填直接注入就会留下{code}这样的占位符模型会把它当成字面内容。我的处理方式是注入前先扫描内容里的所有{xxx}模式和 variables 数组比对。如果有变量没定义或者定义了但值为空就在 popup 里高亮提示让用户确认是否继续。如果用户选择继续就把未填的变量替换成空字符串而不是保留占位符。还有一个边界情况提示词内容里本身就有花括号比如代码示例function foo() { return 1; }。这种情况会被误判为变量。解决办法是在变量定义时用更严格的命名规则只识别{字母数字下划线}这种模式纯符号或含空格的花括号不识别。同时提供一个转义选项用户可以用\{来表示字面花括号。5. 实测中的意外情况与排查过程5.1 注入成功但发送后内容为空这个问题困扰了我整整一个下午。现象是点注入输入框里能看到文本但一点发送消息发出去是空的或者只有第一个字符。排查过程是这样的。第一步我检查了注入后的 DOM发现textarea.value确实有完整内容。第二步我在发送按钮的点击事件上加了个断点发现 React 的 state 里value是空字符串。第三步我对比了手动输入和程序注入的差异发现手动输入时 React 的onChange被触发了而程序注入时没有。根因就是前面说的 React 受控组件问题。我一开始只改了value属性没有触发input事件React 的 state 没更新。修复方案就是加上nativeInputValueSetter和dispatchEvent。这个坑在 React 应用里非常普遍任何做浏览器自动化的项目都会遇到。5.2 Claude 页面改版导致选择器失效Claude 的页面结构在几个月内改过两次。第一次是把输入框从textarea换成了contenteditable的 div第二次是给输入框加了一层 shadow DOM。选择器失效的表现是注入函数找不到目标元素静默失败。因为我在代码里用了document.querySelector加可选链找不到就返回 null没有报错所以一开始没发现。修复方案有两部分。一是把选择器改成多重回退先试[contenteditabletrue]再试div.ProseMirror再试textarea任何一个命中就用。二是加了日志上报注入失败时在 popup 里显示未找到输入框请刷新页面重试而不是静默失败。这个经历让我意识到做跨平台工具选择器不能写死必须做容错。而且要有明确的失败反馈否则用户以为工具坏了其实是页面结构变了。5.3 Gemini 长提示词被截断Gemini 的输入框对超长文本有个隐性的截断行为。我有一条提示词大概 3000 字注入到 Gemini 后实际进入输入框的只有前 2000 字左右后面的被吞了。排查后发现不是 Gemini 的输入框有长度限制而是execCommand(insertText)在超长文本时会有性能问题插入过程被中断。解决办法是分段插入每段 500 字用setTimeout间隔 50ms 依次插入。这样虽然慢一点但能保证完整。另一个相关问题是Gemini 对超长提示词的末尾指令遵循度下降。所以我在适配层里加了逻辑如果内容超过 2000 字自动把最后一段请遵循以上要求之类的指令提取出来复制一份放到开头。这个改动之后Gemini 的输出稳定性明显提升。5.4 存储配额与版本历史的平衡chrome.storage.local默认配额是 5MB看起来很大但如果你存了很多长提示词加上版本历史很容易逼近上限。我一开始没注意存了大概 200 条提示词每条平均 500 字加上版本历史占了 3MB 多。优化方案是版本历史只保留最近 10 个版本且单个版本超过 2000 字的只存前 2000 字加省略标记。同时提供一个清理历史按钮让用户手动释放空间。另外导出功能很重要我加了一键导出全部提示词为 JSON 文件的功能这样即使本地存储出问题数据也不会丢。6. 这套工具实际用起来是什么体验6.1 日常使用流程现在的使用流程是这样的打开 ChatGPT 或 Claude 或 Gemini 的页面点扩展图标popup 里列出所有提示词按标签筛选或搜索选中一条如果有变量就填值点注入文本就进了输入框然后正常发送。整个过程大概 3 到 5 秒比手动复制粘贴快不了太多但胜在不会出错。变量替换是自动的格式适配是自动的版本管理是自动的。尤其是当你有几十条提示词、每天要用十几次的时候累积节省的时间和避免的错误很可观。我还加了一个最近使用列表把最常用的 5 条置顶进一步减少查找时间。以及一个快捷键CtrlShiftP直接唤起 popup不用去点扩展图标。6.2 哪些场景收益最大用了两个月下来我觉得收益最大的场景有三个。第一是代码审查。我有一条代码审查提示词里面有 6 个变量语言、框架、代码、关注点等。以前每次用都要手动填现在填完一键注入而且针对 Claude 会自动转成 XML 格式审查质量比手动粘贴的版本高不少。第二是长文档摘要。Gemini 处理长文档有优势但我的摘要提示词比较长以前经常被截断。现在适配层会自动分段插入并前置核心指令摘要的完整性和准确性都提升了。第三是跨模型对比。同一条提示词我可以快速注入到三个模型里对比输出差异。这个在调试提示词的时候特别有用能快速看出哪个模型对哪类指令更敏感。6.3 几个我踩过的使用坑第一个坑是不要在有未发送内容时注入。如果你输入框里已经打了一半的字再点注入新内容会追加到后面而不是替换。我一开始没注意导致发出去的消息前半段是旧内容。后来加了个检查注入前如果检测到输入框非空弹窗确认是替换还是追加。第二个坑是变量值里不要包含花括号。如果你填的变量值本身有{}替换后会被下一轮扫描误判。解决办法是替换时用一次性替换不递归扫描同时提示用户避免在变量值里用花括号。第三个坑是扩展更新后要刷新页面。因为 content script 是页面加载时注入的扩展更新后旧页面的 content script 还是旧版本会出现行为不一致。所以每次更新扩展我都会习惯性刷新一下模型页面。7. 后续可以继续打磨的方向这个工具目前对我来说已经够用了但还有几个方向值得继续做。一个是提示词效果追踪。现在我只能凭感觉判断哪条提示词好用如果能记录每条提示词的使用次数、在哪个模型上用的、输出质量的主观评分就能形成一个反馈闭环帮我优化提示词库。另一个是团队共享。目前数据是纯本地的如果能把提示词库导出成可分享的格式或者支持导入别人的提示词包价值会更大。但这里要处理好隐私问题不能默认上传任何数据。还有一个是适配规则的自动化更新。现在三套适配规则是我手动维护的如果模型更新导致规则失效需要我手动改。未来可以考虑做一个简单的规则测试集每次模型更新后跑一遍自动检测哪条规则需要调整。不过这些都是锦上添花。核心的存储、适配、注入三层已经跑通了日常使用很稳定。如果你也在多模型之间切换我建议先把最常用的那几条提示词整理出来做成模板然后按这个思路搭一个最小可用的版本。不用一开始就追求功能完整先解决复制粘贴出错这个最痛的点后面再慢慢加。