基于MutationObserver的GPT-4使用统计插件开发实战
1. 项目概述与核心价值作为一个深度依赖GPT-4进行内容创作、代码审查或日常咨询的用户你是否曾有过这样的困惑今天到底向GPT-4提了多少个问题这个月的使用频率是上升了还是下降了每天聊得最多的主题是什么这些看似简单的问题如果没有一个直观的记录工具往往只能凭模糊的感觉去猜测。今天要分享的就是我自己为了解决这个痛点而折腾出来的一个Chrome浏览器插件——GPT-4请求计数器。它不是一个复杂的AI模型而是一个轻巧、实用的“仪表盘”专门用来监控和分析你在浏览器端与GPT-4的每一次互动。这个插件的核心功能非常直接自动计数、图表化历史、可视化分析。它像一个沉默的助手在你每次与ChatGPT网页版特指GPT-4模型对话交互时默默地在后台计数。然后通过一个简洁的弹出窗口你可以实时看到当天的请求数通过历史图表回顾过去一段时间的使用趋势甚至它还能分析你当天所有的对话内容生成一张关键词词云图让你一眼看清今天思考的焦点。对于需要管控使用成本、分析工作流效率或者单纯想了解自己与AI互动习惯的朋友来说这个小工具能提供非常直观的数据支持。它的实现原理并不涉及高深的AI算法而是巧妙地利用了浏览器扩展的能力通过内容脚本Content Script监听页面DOM的变化来识别一次“请求-响应”的完成。整个项目代码开源基于MIT协议这意味着你可以自由地使用、修改甚至二次开发。接下来我将从设计思路、具体实现、避坑经验到扩展可能为你完整拆解这个项目无论你是想直接使用这个插件还是对如何开发一个类似的浏览器监控工具有兴趣相信都能从中获得启发。2. 插件核心功能与设计思路拆解在动手写代码之前明确“要做什么”以及“为什么这么做”至关重要。这个插件的目标是在非侵入式的前提下精准统计GPT-4的请求次数并提供衍生分析。围绕这个目标我拆解出了几个核心的设计决策。2.1 为什么选择浏览器插件而非独立应用首先统计对象是“在浏览器中向GPT-4发送的请求”。最直接的场景就是用户访问chat.openai.com并使用GPT-4模型进行对话。因此插件的形态是最佳选择无感集成插件与浏览器环境天然融合用户无需打开额外软件统计过程在后台自动完成体验无缝。精准捕获只有浏览器插件能直接访问和监听目标网页的DOM文档对象模型与网络活动这是识别用户交互的关键。低性能开销相比于启动一个独立的桌面应用插件通常只激活在特定页面资源占用更小。注意这意味着插件仅对浏览器环境生效。如果你通过API、第三方客户端或手机App使用GPT-4本插件是无法统计的。这是由其设计边界决定的。2.2 核心功能模块设计基于用户需求我规划了四个核心功能模块它们共同构成了插件的价值闭环请求计数器这是基石功能。目标是在用户每次成功获得GPT-4回复后计数1。这里的关键在于如何准确界定一次“有效请求”。不能简单地计算点击“发送”按钮的次数因为网络可能失败、用户可能中途刷新。我的设计是监听对话界面中代表GPT-4回复的新消息块的出现。只有当包含特定标识如GPT-4头像或模型标记的新div元素被添加到DOM中才认为一次请求完成并计数。历史数据图表仅有当日计数是不够的。为了分析趋势需要持久化存储历史数据。我选择使用浏览器的chrome.storage.localAPI来存储每日的计数。图表功能则负责读取这些数据并使用一个轻量级的图表库如Chart.js渲染成折线图或柱状图让用户能直观看到过去7天、30天甚至更久的使用情况。当日对话词云图这是提升插件趣味性和洞察力的功能。思路是收集当天所有对话的文本内容包括用户提问和AI回复进行分词、过滤停用词如“的”、“了”、“我”然后统计词频。最后利用词云生成库如WordCloud2.js将高频词以视觉化的方式呈现。词云图能快速揭示当天讨论的核心话题。数据导出与报告为了满足深度分析或备份需求提供了导出功能。可以将当天的完整对话内容导出为结构清晰的HTML或Markdown文件。更进一步“今日报告”可以整合计数、词云、高频词列表、平均对话长度等数据形成一个简单的数据简报。2.3 技术栈选型考量前端框架插件弹出窗Popup和选项页Options界面相对简单为了保持轻量我直接使用原生JavaScript配合DOM操作完成没有引入React或Vue。这减少了打包体积和复杂度。数据存储chrome.storage.local是官方推荐的方式它提供异步API存储空间相对较大通常5MB以上且数据与浏览器配置文件绑定。相比于localStorage它更适合插件环境并且能更方便地在插件的不同部分如后台脚本、内容脚本、弹出窗之间共享数据。图表与词云库选择Chart.js和WordCloud2.js是因为它们都是纯前端库无需后端支持图表美观且文档完善只需通过CDN引入或本地封装即可。构建工具使用简单的模块化JavaScript通过manifest.json的content_scripts和background字段来组织代码结构。对于更复杂的项目可以考虑使用webpack或Parcel进行构建。这个设计思路确保了插件核心目标明确、架构清晰且每个功能都有可行的实现路径。接下来我们深入每个功能的具体实现细节。3. 关键实现细节与核心技术解析将设计转化为代码会遇到许多具体的技术挑战。这里我重点解析几个最关键的实现环节并分享其中的决策逻辑和注意事项。3.1 请求计数的精准捕获MutationObserver的应用如何知道用户收到了GPT-4的新回复ChatGPT的网页是动态更新的不能依赖定时器去轮询。这里的主角是MutationObserverAPI它可以监听DOM树的变化。实现原理注入内容脚本在manifest.json中声明content_scripts将其匹配到https://chat.openai.com/*。这样当用户打开ChatGPT网站时我们的脚本会自动注入到页面中。定位对话容器首先需要找到页面上承载所有聊天消息的容器元素。通过浏览器开发者工具分析发现其通常是一个具有特定类名如group或w-full的div。创建观察者针对这个容器创建一个MutationObserver实例。配置它监听“子节点添加”childList: true这一变化。定义回调函数每当容器内有新的子节点即新的消息块被添加时回调函数就会被触发。在这个函数里我们需要检查新添加的节点是否包含代表GPT-4回复的标识例如查找特定的头像图片alt属性为“ChatGPT”或者找到包含“GPT-4”文本的模型标签。该消息块是否是新生成的避免页面加载历史消息时误触发。计数与通信一旦确认是新的GPT-4回复就将计数增加。这个计数需要从内容脚本传递到插件的其他部分如弹出窗显示。这里使用chrome.runtime.sendMessageAPI 发送一个计数增加的消息给后台脚本background script由后台脚本统一更新存储。// 内容脚本 (content.js) 示例代码片段 const targetNode document.querySelector(‘[data-testid^conversation-turn-]‘); // 假设的容器选择器 const observerConfig { childList: true, subtree: true }; const observer new MutationObserver((mutationsList) { for (let mutation of mutationsList) { if (mutation.type ‘childList‘) { mutation.addedNodes.forEach((node) { if (node.nodeType 1 isGPT4Response(node)) { // 检查是否为GPT-4回复元素 chrome.runtime.sendMessage({ type: ‘INCREMENT_COUNT‘ }); } }); } } }); observer.observe(targetNode, observerConfig); function isGPT4Response(element) { // 实现逻辑检查元素内是否包含GPT-4的特定标识 // 例如查找模型选择器显示为“GPT-4”或者特定的头像/图标 return element.querySelector(‘div.model-selector:contains(“GPT-4”)‘) ! null; // 示例实际选择器需分析页面 }实操心得ChatGPT的网页结构可能会更新导致我们依赖的CSS选择器失效。因此isGPT4Response函数的健壮性很重要。一个更稳健的策略是组合多种特征判断例如同时检查模型标识和消息流的顺序用户消息后新增的才是AI回复。此外需要在manifest.json的permissions中声明storage和activeTab权限。3.2 数据持久化与跨上下文通信插件的不同部分运行在独立的“上下文”中内容脚本运行在网页的上下文中能直接访问DOM但权限受限。后台脚本在浏览器后台持续运行拥有完整的Chrome API权限适合做数据管理和逻辑协调。弹出窗用户点击插件图标时出现生命周期短主要用于展示和简单交互。数据流设计存储中心所有数据当日计数、历史记录、对话文本都通过后台脚本使用chrome.storage.local.set/get进行读写。这保证了数据的一致性和持久性。通信机制内容脚本 - 后台脚本使用chrome.runtime.sendMessage发送“计数增加”或“新对话文本”消息。后台脚本 - 弹出窗/选项页当弹出窗打开时它主动向后台脚本发送消息如getTodayCount请求数据。后台脚本收到后从存储中读取数据并返回。对于实时更新可以使用chrome.runtime.onMessage监听。弹出窗内部直接使用chrome.storage.local.onChanged监听器可以在存储数据变化时自动更新UI实现计数器的实时跳动效果。// 后台脚本 (background.js) 示例 chrome.runtime.onMessage.addListener((request, sender, sendResponse) { if (request.type ‘INCREMENT_COUNT‘) { // 1. 获取当前日期作为键 const today new Date().toISOString().split(‘T‘)[0]; // 2. 从存储中读取今日计数 chrome.storage.local.get([today], (result) { let todayCount result[today] || 0; todayCount; // 3. 更新存储 chrome.storage.local.set({ [today]: todayCount }, () { console.log(Count updated for ${today}: ${todayCount}); }); }); } // 可以处理其他类型的消息... }); // 弹出窗脚本 (popup.js) 示例 - 打开时获取数据 document.addEventListener(‘DOMContentLoaded‘, () { const today new Date().toISOString().split(‘T‘)[0]; chrome.storage.local.get([today], (result) { document.getElementById(‘countDisplay‘).textContent result[today] || 0; }); });3.3 词云图生成的优化处理生成词云图看似简单但要做好需要处理文本预处理和性能问题。处理流程文本收集在内容脚本中不仅计数也捕获每条消息的文本内容并随消息发送到后台存储。注意只收集当天的对话。文本预处理分词对于英文可以按空格和标点简单分割。对于中文需要使用分词库如jieba需引入其浏览器版或segment。本项目为了轻量最初可能采用简单的按字分割或使用一个小的禁用词表过滤但效果有限。更优解是集成一个轻量级的中文分词库。清洗移除URL、特殊符号、纯数字、以及常见的停用词如“的”、“了”、“是”、“在”。统计词频遍历所有分词结果统计每个词出现的次数。生成词云将词频数组传递给WordCloud2.js。可以自定义颜色、形状、字体大小范围等。一个关键点是词云画布通常放在弹出窗或选项页中需要确保画布尺寸适配容器并在数据更新时重新绘制。// 假设我们已经有了一个词频数组 wordFreqList [[‘编程‘, 15], [‘设计‘, 12], ...] function generateWordCloud(wordFreqList) { const canvas document.getElementById(‘wordcloudCanvas‘); const ctx canvas.getContext(‘2d‘); // 确保画布大小正确 canvas.width canvas.parentElement.clientWidth; canvas.height 300; // 调用 WordCloud2.js (假设已加载) WordCloud(canvas, { list: wordFreqList, gridSize: 10, weightFactor: 8, color: ‘random-dark‘, backgroundColor: ‘#f9f9f9‘, rotateRatio: 0.3, }); }注意事项词云生成是计算密集型操作如果当天对话内容极多数万字在弹出窗的短暂生命周期内处理可能导致卡顿。因此可以考虑将分词和词频统计放在后台脚本中进行或者对文本进行采样处理。另外WordCloud2.js在绘制大量词语时也可能有性能压力需要合理设置gridSize和weightFactor参数。4. 插件打包、发布与使用指南开发完成后我们需要将代码打包成.crx文件供安装并了解如何发布到商店。4.1 项目结构与Manifest配置一个标准的Chrome插件项目结构如下gpt4-request-counter/ ├── manifest.json # 核心配置文件 ├── icons/ # 插件图标多种尺寸 ├── popup.html # 弹出窗口页面 ├── popup.js ├── options.html # 选项页面可选 ├── options.js ├── background.js # 后台服务脚本 ├── content.js # 注入到页面的脚本 └── libs/ # 第三方库如chart.js, wordcloud2.jsmanifest.json是重中之重它定义了插件的基本信息、权限和资源。以下是关键部分{ manifest_version: 3, // 务必使用Manifest V3 name: GPT-4 Requests Counter, version: 1.0.0, description: 统计并可视化您每日使用GPT-4的请求次数和对话内容。, permissions: [ storage, // 用于存储数据 activeTab // 获取当前标签页信息可选 ], host_permissions: [ https://chat.openai.com/* // 允许向该网站注入内容脚本 ], content_scripts: [ { matches: [https://chat.openai.com/*], js: [content.js], run_at: document_idle // 页面加载完成后运行 } ], background: { service_worker: background.js // MV3使用service worker }, action: { default_popup: popup.html, default_icon: icons/icon48.png }, icons: { 48: icons/icon48.png, 128: icons/icon128.png } }4.2 本地加载与测试在发布前需要在本地进行充分测试。打开Chrome浏览器进入chrome://extensions/。开启右上角的“开发者模式”。点击“加载已解压的扩展程序”选择你的插件项目根目录。插件将被加载。此时打开chat.openai.com开始对话检查弹出窗中的计数是否正常增加图表和词云功能是否工作。4.3 打包与发布到Chrome网上应用店打包在chrome://extensions/页面找到你的插件点击“打包扩展程序”。选择项目根目录它会生成一个.crx插件包和一个.pem私钥文件务必保存好用于后续更新。发布准备你需要一个Google开发者账号需一次性支付5美元注册费。在 Chrome开发者信息中心 创建新项目。上传与填写信息上传打包好的.zip文件注意不是.crx通常需要将项目文件夹压缩成zip填写详细的商店列表信息标题、描述、宣传图、截图、分类等。描述中应清晰说明功能、使用方法和隐私声明例如声明本插件仅本地存储数据不会上传任何信息。提交审核提交后Google会进行审核通常需要几天时间。审核通过后插件即可公开下载。4.4 用户使用指南对于最终用户安装和使用非常简单安装访问Chrome网上应用店搜索“GPT-4 Requests Counter”或通过项目提供的直接链接点击“添加到Chrome”。使用正常使用chat.openai.com与GPT-4对话。插件图标上可能会显示一个徽章实时更新当日计数。点击浏览器工具栏上的插件图标弹出窗口会显示当前日期和请求总数。“History Chart”按钮点击跳转到选项页查看历史请求的折线图/柱状图。“Today‘s Word Cloud”按钮点击在弹出窗或新页面中生成并展示当天的对话词云图通常旁边会有“Download”按钮可以保存为PNG图片。“Export Today‘s Chat”按钮将当天所有对话以HTML或Markdown格式导出并下载。“Today‘s Report”按钮生成一个包含统计摘要、高频词列表和词云图的综合报告页面。数据管理在插件的选项页面通常通过右键点击插件图标选择“选项”进入用户可以查看更详细的历史数据有时也提供“清除所有数据”的按钮。5. 开发中遇到的典型问题与解决方案在实际开发过程中我踩过不少坑。这里记录下几个典型问题及其解决方法希望能帮你绕开这些弯路。5.1 问题计数不准确或重复计数现象有时一次对话回复计数器却增加了2次或更多。排查检查MutationObserver的回调函数。ChatGPT页面可能在一条消息完全渲染前会多次更新DOM例如先添加一个骨架屏再填充内容。我们的观察者可能监听到了多次childList变化。检查isGPT4Response函数。它可能将用户消息或系统消息误判为GPT-4回复。解决方案去抖处理为同一轮对话的DOM变化设置一个简单的去抖debounce。例如在收到变化事件后等待500毫秒再检查最终的消息状态。这可以避免中间状态被多次计数。更精确的标识深入分析DOM结构找到唯一标识GPT-4回复的元素。可能是一个包含特定>// popup.js chrome.storage.onChanged.addListener((changes, namespace) { if (namespace ‘local‘) { const today new Date().toISOString().split(‘T‘)[0]; if (changes[today]) { document.getElementById(‘countDisplay‘).textContent changes[today].newValue; } } });从后台脚本主动获取弹出窗打开时向后台脚本发送一个“获取最新数据”的消息确保拿到的是实时数据。5.3 问题词云图在弹出窗中显示不全或错位现象词云图只显示一部分或者画布大小不对。原因弹出窗的尺寸是固定的通常在manifest.json的action中定义default_popup的HTML页面大小。WordCloud2.js需要知道画布的确切尺寸来布局词语。如果画布尺寸设置不正确或者弹出窗在绘制完成后才调整大小就会出问题。解决方案显式设置画布尺寸在调用WordCloud函数前用JavaScript动态设置canvas.width和canvas.height使其等于其父容器的客户区宽度和高度。确保容器可见词云绘制必须在弹出窗的DOM完全渲染且可见后进行。可以将绘制代码放在window.onload或确保DOM就绪后执行。响应式处理如果弹出窗尺寸可变需要监听窗口的resize事件并重新生成词云。不过对于固定尺寸的弹出窗这一步通常不需要。5.4 问题隐私与数据安全疑虑用户可能担心这个插件会读取我的所有对话内容吗数据会上传到服务器吗设计与声明本地存储原则本插件所有代码逻辑均在浏览器本地执行。收集的对话文本、计数数据均使用chrome.storage.localAPI存储在用户本地电脑上不会通过网络发送到任何远程服务器。权限最小化manifest.json中只申请了必要的权限storage用于本地存储和针对chat.openai.com的host_permissions用于注入脚本。没有申请访问所有网站或网络请求的权限。开源透明项目代码完全开源在GitHub任何人都可以审查代码确认没有数据外传行为。提供清除选项在插件选项页明确提供“清除所有数据”的按钮让用户拥有完全控制权。在插件的应用商店描述和隐私政策中必须清晰、醒目地说明以上几点以建立用户信任。6. 进阶优化与扩展思路一个基础可用的插件已经完成但总有可以打磨和增强的地方。这里分享几个我思考过的进阶方向。6.1 性能与资源占用优化懒加载与按需注入目前内容脚本在匹配的页面加载时就会注入。可以改为在用户与页面交互如开始输入或检测到页面是ChatGPT聊天界面时才注入减少初始开销。数据存储优化随着时间推移存储的历史数据会越来越多。可以定期如每季度自动归档或清理超过一定时间如一年的详细对话文本只保留每日的计数统计以控制存储空间。词云生成异步化将分词和词频统计这些耗时操作放入Web Worker中执行避免阻塞弹出窗或选项页的主线程保持UI响应流畅。6.2 功能增强多模型支持不仅统计GPT-4也可以识别和统计GPT-3.5、Claude等其他AI模型的请求如果它们在同一个网站上有不同界面。这需要扩展isGPT4Response函数为isAIResponse并区分模型类型。自定义统计周期允许用户自定义“统计日”的起始时间例如从每天凌晨4点开始以适应不同作息。设置与自定义增加选项页让用户可以选择词云的颜色主题、字体。设置停用词列表过滤掉不想出现在词云中的词如项目特定的术语。开启/关闭某些功能如对话内容收集以满足更严格的隐私需求。数据同步通过用户授权将加密的统计数据同步到用户自己的云存储如Google Drive、Dropbox实现跨设备的数据统一。注意此功能涉及敏感数据必须提供明确的开关和加密保障。6.3 用户体验提升更丰富的通知当每日请求数达到用户设定的阈值时在浏览器角落显示一个温和的通知提醒适度使用。数据导出格式多样化除了HTML/Markdown支持导出为CSV便于用Excel分析或JSON便于程序处理。更深入的分析报告在“今日报告”中加入更多指标如平均每次对话的轮次、最常使用的提示词开头、对话活跃时间段分布图等。6.4 应对网站变更的策略ChatGPT的网页界面更新是常态。如何让插件更健壮配置化选择器将用于识别消息容器、AI回复标识的CSS选择器提取到插件的配置如options页面存储或远程配置文件中。当网站改版导致选择器失效时可以快速更新配置而无需用户重新安装插件。社区维护开源项目可以鼓励用户提交Issue报告失效情况共同维护一套选择器规则。降级方案如果主要选择器失效可以尝试降级到更通用但可能不太精确的检测方法如检测所有新出现的消息块并通过文本模式辅助判断至少保证核心计数功能不完全瘫痪同时给出界面需要更新的提示。开发这个插件的过程是一个典型的“发现问题-设计解决方案-实现-测试-优化”的循环。它让我更深入地理解了浏览器扩展的开发模式、DOM监控的细节以及数据可视化在前端的应用。最重要的是它解决了我自己的真实需求。如果你也有类似的想法不妨基于这个项目开始你的探索添加你独有的功能。编程的乐趣往往就在于用代码将脑海中的一个小工具变为现实并让它真正产生价值。