共计 5512 个字符,预计需要花费 14 分钟才能阅读完成。
背景与痛点分析
在日常使用网页版 ChatGPT 时,我们常遇到几个典型问题:

- 上下文丢失 :页面刷新或跳转后,对话历史无法保留,需要反复粘贴之前的内容
- 操作效率低 :每次使用都需要打开新标签页,无法快速在当前页面调用
- 功能局限 :缺乏个性化设置(如常用提示词收藏)和本地数据存储能力
这些痛点正是浏览器插件可以完美解决的场景。通过开发 Chrome 插件,我们可以实现:
- 常驻侧边栏快速访问
- 本地存储对话历史
- 跨页面保持会话上下文
技术选型:Manifest V3 的必然选择
Chrome 插件开发面临的首要决策是选择 Manifest 版本。当前存在 V2 和 V3 两个主要版本:
| 特性 | Manifest V2 | Manifest V3 |
|---|---|---|
| 后台页面 | 常驻 background page | Service Worker |
| 远程代码 | 允许 eval 动态执行 | 严格禁止 |
| 网络请求 | webRequest API | declarativeNetRequest |
| 审核要求 | 逐步淘汰 | 商店强制要求 |
选择 V3 的主要原因:
- 未来兼容性 :2023 年后新插件必须使用 V3
- 性能优势 :Service Worker 按需运行节省资源
- 安全性提升 :禁止远程代码执行降低风险
核心架构实现
1. 项目结构设计
标准插件目录结构如下(关键文件标注⭐):
chatgpt-extension/
├── icons/ # 插件图标
├── src/
│ ├── background/ # ⭐Service Worker
│ ├── content/ # ⭐Content Scripts
│ └── popup/ # 弹出窗口 UI
├── manifest.json # ⭐核心配置文件
└── styles/
2. Manifest 配置详解
以下是支持侧边栏和内容注入的 manifest.json 示例:
{
"manifest_version": 3,
"name": "ChatGPT Assistant",
"version": "1.0",
"permissions": [
"storage",
"sidePanel",
"activeTab"
],
"background": {"service_worker": "src/background/main.js"},
"content_scripts": [{"matches": ["<all_urls>"],
"css": ["styles/inject.css"],
"js": ["src/content/main.js"]
}],
"side_panel": {"default_path": "sidepanel.html"}
}
关键配置说明:
sidePanel:启用 Chrome 114+ 的侧边栏 APIcontent_scripts:匹配所有页面注入我们的脚本service_worker:替代原来的 background pages
3. 通信机制实现
插件各模块间的通信架构:
flowchart LR
A[Content Script] -->|chrome.runtime.sendMessage| B[Service Worker]
B -->|chrome.tabs.sendMessage| A
B -->|fetch| C[ChatGPT API]
A --> D[Page DOM]
具体实现代码示例:
后台服务(Service Worker):
// 消息路由处理
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {switch (request.type) {
case 'API_CALL':
handleChatGPTRequest(request.data)
.then(sendResponse)
.catch(console.error);
return true; // 保持消息通道开放
case 'SAVE_HISTORY':
return idb.saveConversation(request.data)
.then(sendResponse);
}
});
// 封装 API 请求
async function handleChatGPTRequest(prompt) {const API_KEY = await chrome.storage.sync.get('apiKey');
try {
const resp = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`
},
body: JSON.stringify({
model: "gpt-3.5-turbo",
messages: [{role: "user", content: prompt}]
})
});
if (!resp.ok) throw new Error(`HTTP error! status: ${resp.status}`);
return await resp.json();} catch (error) {console.error('API 请求失败:', error);
return {error: error.message};
}
}
内容脚本(Content Script):
// 注入浮动按钮
const floatBtn = document.createElement('div');
floatBtn.id = 'chatgpt-float-btn';
document.body.appendChild(floatBtn);
// 点击事件处理
floatBtn.addEventListener('click', async () => {const selection = window.getSelection().toString().trim();
chrome.runtime.sendMessage({ type: 'API_CALL', data: selection || 'Hello!'},
(response) => {showResponseInUI(response);
}
);
});
性能优化实践
1. 按需注入策略
默认情况下,content scripts 会在所有匹配页面注入。通过动态注入可以显著提升性能:
// manifest.json 移除 content_scripts 声明
// 改为在后台动态注入
chrome.action.onClicked.addListener((tab) => {
chrome.scripting.executeScript({target: { tabId: tab.id},
files: ['src/content/main.js']
});
});
2. 对话历史分页加载
使用 IndexedDB 存储大量历史记录时的优化方案:
// 分页查询实现
async function getHistory(page = 1, pageSize = 10) {const db = await getDB();
const tx = db.transaction('conversations', 'readonly');
const store = tx.objectStore('conversations');
return new Promise((resolve) => {const countRequest = store.count();
countRequest.onsuccess = () => {
const total = countRequest.result;
const cursorRequest = store.openCursor(IDBKeyRange.lowerBound(0),
'prev'
);
let results = [];
let advanced = false;
cursorRequest.onsuccess = (e) => {
const cursor = e.target.result;
if (!cursor) return resolve({results, total});
if (!advanced && page > 1) {cursor.advance((page - 1) * pageSize);
advanced = true;
return;
}
results.push(cursor.value);
if (results.length >= pageSize) {return resolve({ results, total});
}
cursor.continue();};
};
});
}
3. 请求节流控制
防止用户频繁点击导致 API 过载:
let lastRequestTime = 0;
const REQUEST_INTERVAL = 3000; // 3 秒冷却
async function throttledRequest(prompt) {const now = Date.now();
if (now - lastRequestTime < REQUEST_INTERVAL) {throw new Error(` 请等待 ${(REQUEST_INTERVAL - (now - lastRequestTime))/1000} 秒再试 `);
}
lastRequestTime = now;
return await handleChatGPTRequest(prompt);
}
安全防护措施
1. 敏感信息加密存储
对 API 密钥等敏感数据采用加密存储:
// 使用 Web Crypto API 加密
async function encryptData(data, password) {const encoder = new TextEncoder();
const keyMaterial = await crypto.subtle.importKey(
'raw',
encoder.encode(password),
'PBKDF2',
false,
['deriveKey']
);
const key = await crypto.subtle.deriveKey({ name: 'PBKDF2', salt: encoder.encode('fixed-salt'), iterations: 100000, hash: 'SHA-256' },
keyMaterial,
{name: 'AES-GCM', length: 256},
false,
['encrypt', 'decrypt']
);
const iv = crypto.getRandomValues(new Uint8Array(12));
const encrypted = await crypto.subtle.encrypt({ name: 'AES-GCM', iv},
key,
encoder.encode(data)
);
return {iv: Array.from(iv), data: Array.from(new Uint8Array(encrypted)) };
}
2. 严格的 CSP 策略
在 manifest 中设置内容安全策略:
{
"content_security_policy": {"extension_pages": "script-src'self'; object-src'none'"}
}
常见问题解决方案
1. 跨域请求被拦截
现象:调用 API 时出现 CORS 错误
解决方案:
- 在 manifest 中声明权限:
"host_permissions": ["https://api.openai.com/*"] - 通过后台服务转发请求(Service Worker 不受 CORS 限制)
2. 大响应导致 UI 卡顿
现象:长文本响应时页面冻结
优化方案:
// 分块流式处理响应
async function* streamResponse(response) {const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {const { done, value} = await reader.read();
if (done) break;
yield decoder.decode(value, { stream: true});
}
}
// 在 UI 中逐步显示
for await (const chunk of streamResponse(resp)) {
outputEl.textContent += chunk;
await new Promise(r => requestAnimationFrame(r)); // 让出 UI 线程
}
3. 插件更新数据迁移
问题:插件更新后 localStorage 数据丢失
解决方案:
// 版本升级时触发迁移
chrome.runtime.onInstalled.addListener((details) => {if (details.reason === 'update') {migrateV1ToV2();
}
});
async function migrateV1ToV2() {const oldData = await chrome.storage.local.get(null);
if (oldData.conversations) {await idb.bulkSave(oldData.conversations);
chrome.storage.local.remove('conversations');
}
}
扩展思考
本文实现的插件是基于云端 ChatGPT API,但也可以探索与本地运行的 LLM(如 LLaMA、Alpaca)集成:
- 通过 WebSocket 连接本地 LLM 服务
- 使用 WebUSB/WebSerial API 直接与本地设备通信
- 利用 WebAssembly 在浏览器中运行量化模型
这种混合架构既能保护隐私,又能降低 API 成本,是未来值得探索的方向。
正文完
发表至: 技术开发
近三天内
