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回复的标识?(例如,查找特定的头像图片
- 计数与通信:一旦确认是新的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; } } }); - 从后台脚本主动获取:弹出窗打开时,向后台脚本发送一个“获取最新数据”的消息,确保拿到的是实时数据。
- 显式设置画布尺寸:在调用
WordCloud函数前,用JavaScript动态设置canvas.width和canvas.height,使其等于其父容器的客户区宽度和高度。 - 确保容器可见:词云绘制必须在弹出窗的DOM完全渲染且可见后进行。可以将绘制代码放在
window.onload或确保DOM就绪后执行。 - 响应式处理:如果弹出窗尺寸可变,需要监听窗口的
resize事件,并重新生成词云。不过对于固定尺寸的弹出窗,这一步通常不需要。 - 本地存储原则:本插件所有代码逻辑均在浏览器本地执行。收集的对话文本、计数数据均使用
chrome.storage.localAPI存储在用户本地电脑上,不会通过网络发送到任何远程服务器。 - 权限最小化:
manifest.json中只申请了必要的权限:storage(用于本地存储)和针对chat.openai.com的host_permissions(用于注入脚本)。没有申请访问所有网站或网络请求的权限。 - 开源透明:项目代码完全开源在GitHub,任何人都可以审查代码,确认没有数据外传行为。
- 提供清除选项:在插件选项页明确提供“清除所有数据”的按钮,让用户拥有完全控制权。
- 懒加载与按需注入:目前内容脚本在匹配的页面加载时就会注入。可以改为在用户与页面交互(如开始输入)或检测到页面是ChatGPT聊天界面时才注入,减少初始开销。
- 数据存储优化:随着时间推移,存储的历史数据会越来越多。可以定期(如每季度)自动归档或清理超过一定时间(如一年)的详细对话文本,只保留每日的计数统计,以控制存储空间。
- 词云生成异步化:将分词和词频统计这些耗时操作放入Web Worker中执行,避免阻塞弹出窗或选项页的主线程,保持UI响应流畅。
- 多模型支持:不仅统计GPT-4,也可以识别和统计GPT-3.5、Claude等其他AI模型的请求(如果它们在同一个网站上有不同界面)。这需要扩展
isGPT4Response函数为isAIResponse,并区分模型类型。 - 自定义统计周期:允许用户自定义“统计日”的起始时间(例如,从每天凌晨4点开始),以适应不同作息。
- 设置与自定义:增加选项页,让用户可以:
- 选择词云的颜色主题、字体。
- 设置停用词列表,过滤掉不想出现在词云中的词(如项目特定的术语)。
- 开启/关闭某些功能(如对话内容收集,以满足更严格的隐私需求)。
- 数据同步:通过用户授权,将加密的统计数据同步到用户自己的云存储(如Google Drive、Dropbox),实现跨设备的数据统一。注意:此功能涉及敏感数据,必须提供明确的开关和加密保障。
- 更丰富的通知:当每日请求数达到用户设定的阈值时,在浏览器角落显示一个温和的通知,提醒适度使用。
- 数据导出格式多样化:除了HTML/Markdown,支持导出为CSV(便于用Excel分析)或JSON(便于程序处理)。
- 更深入的分析报告:在“今日报告”中,加入更多指标,如:平均每次对话的轮次、最常使用的提示词开头、对话活跃时间段分布图等。
- 配置化选择器:将用于识别消息容器、AI回复标识的CSS选择器提取到插件的配置(如
options页面存储)或远程配置文件中。当网站改版导致选择器失效时,可以快速更新配置,而无需用户重新安装插件。 - 社区维护:开源项目可以鼓励用户提交Issue报告失效情况,共同维护一套选择器规则。
- 降级方案:如果主要选择器失效,可以尝试降级到更通用但可能不太精确的检测方法(如检测所有新出现的消息块,并通过文本模式辅助判断),至少保证核心计数功能不完全瘫痪,同时给出界面需要更新的提示。
5.3 问题:词云图在弹出窗中显示不全或错位
现象:词云图只显示一部分,或者画布大小不对。原因:弹出窗的尺寸是固定的(通常在manifest.json的action中定义default_popup的HTML页面大小)。WordCloud2.js需要知道画布的确切尺寸来布局词语。如果画布尺寸设置不正确,或者弹出窗在绘制完成后才调整大小,就会出问题。解决方案:
5.4 问题:隐私与数据安全疑虑
用户可能担心:这个插件会读取我的所有对话内容吗?数据会上传到服务器吗?设计与声明:
在插件的应用商店描述和隐私政策中,必须清晰、醒目地说明以上几点,以建立用户信任。
6. 进阶优化与扩展思路
一个基础可用的插件已经完成,但总有可以打磨和增强的地方。这里分享几个我思考过的进阶方向。
6.1 性能与资源占用优化
6.2 功能增强
6.3 用户体验提升
6.4 应对网站变更的策略
ChatGPT的网页界面更新是常态。如何让插件更健壮?
开发这个插件的过程,是一个典型的“发现问题-设计解决方案-实现-测试-优化”的循环。它让我更深入地理解了浏览器扩展的开发模式、DOM监控的细节以及数据可视化在前端的应用。最重要的是,它解决了我自己的真实需求。如果你也有类似的想法,不妨基于这个项目开始你的探索,添加你独有的功能。编程的乐趣,往往就在于用代码将脑海中的一个小工具变为现实,并让它真正产生价值。