共计 3771 个字符,预计需要花费 10 分钟才能阅读完成。
技术背景:iframe 方案的局限性
在传统的 Web 应用中集成 ChatGPT 时,开发者常采用 iframe 嵌入方案。这种方式虽然简单,但在多页面应用(MPA)中暴露了几个关键问题:

- 会话隔离问题 :每个 iframe 实例维护独立的对话上下文,用户在不同页面间跳转时,AI 无法记住之前的对话历史
- DOM 污染风险 :iframe 内的样式和脚本可能影响主页面布局,特别是当 ChatGPT 返回包含 HTML 的内容时
- 性能瓶颈 :多个 iframe 实例同时运行会导致内存占用飙升,在低端设备上尤为明显
架构设计选型
我们对比了两种主流实现方案:
Web Components 方案
- 优点 :
- 原生浏览器支持,无需额外依赖
- 天然的样式隔离机制
-
可通过
<script type="module">按需加载 -
缺点 :
- 旧版本浏览器需要 polyfill
- 通信机制相对复杂
Chrome Extension 方案
- 优点 :
- 可访问浏览器 API(如 tab 管理)
- 后台脚本保持持久化状态
-
支持跨域请求
-
缺点 :
- 需要用户安装扩展
- 上架商店需要审核
- 移动端支持有限
综合考虑后,我们选择 Web Components + Service Worker 的混合方案,既保证兼容性又能实现高级功能。
核心实现
跨 Tab 通信
使用 BroadcastChannel API 实现多 Tab 同步:
// 初始化频道
const channel = new BroadcastChannel('chatgpt_channel');
// 发送消息
function postMessage(type: string, payload: any) {channel.postMessage({ type, payload});
}
// 监听消息
channel.addEventListener('message', (event) => {switch (event.data.type) {
case 'UPDATE_CONTEXT':
// 处理上下文更新
break;
}
});
持久化存储
结合 IndexedDB 和加密存储:
/**
* 加密对话历史
* @param content - 原始对话内容
* @param secretKey - 32 字节密钥
*/
async function encryptHistory(content: string, secretKey: string) {const iv = crypto.getRandomValues(new Uint8Array(12));
const alg = {name: 'AES-GCM', iv};
const key = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(secretKey),
'AES-GCM',
false,
['encrypt']
);
const encrypted = await crypto.subtle.encrypt(
alg,
key,
new TextEncoder().encode(content)
);
return {iv, data: new Uint8Array(encrypted) };
}
Vue3 侧边栏实现
自定义指令处理拖拽逻辑:
// 边界检测指令
app.directive('drag-boundary', {mounted(el, binding) {
const rect = binding.value;
let startX = 0;
el.addEventListener('mousedown', (e) => {
startX = e.clientX;
document.addEventListener('mousemove', moveHandler);
});
const moveHandler = (e) => {
const deltaX = e.clientX - startX;
const newLeft = el.offsetLeft + deltaX;
// 检查是否超出右边界
if (newLeft + el.offsetWidth > rect.right) {el.style.left = `${rect.right - el.offsetWidth}px`;
return;
}
// 检查是否超出左边界
if (newLeft < rect.left) {el.style.left = `${rect.left}px`;
return;
}
el.style.left = `${newLeft}px`;
startX = e.clientX;
};
}
});
性能优化
LRU 缓存实现
class ChatCache {private map = new Map<string, ChatMessage>();
private maxSize: number;
constructor(maxSize = 100) {this.maxSize = maxSize;}
get(key: string): ChatMessage | undefined {const item = this.map.get(key);
if (item) {
// 刷新为最新使用
this.map.delete(key);
this.map.set(key, item);
}
return item;
}
set(key: string, value: ChatMessage) {if (this.map.size >= this.maxSize) {
// 删除最久未使用的
const oldestKey = this.map.keys().next().value;
this.map.delete(oldestKey);
}
this.map.set(key, value);
}
}
Web Worker 处理流式响应
// worker.js
self.addEventListener('message', async (e) => {const { prompt} = e.data;
const stream = await fetchChatStream(prompt);
const reader = stream.body.getReader();
while (true) {const { done, value} = await reader.read();
if (done) break;
self.postMessage({chunk: value});
}
});
// 主线程
const worker = new Worker('worker.js');
worker.onmessage = (e) => {// 处理流式数据};
安全实践
敏感词过滤
const SENSITIVE_WORDS = [
'信用卡', '密码', '账号',
/\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}/g // 银行卡号正则
];
function filterContent(text: string): string {
let result = text;
SENSITIVE_WORDS.forEach(pattern => {result = result.replace(pattern, '***');
});
return result;
}
CSP 配置示例
<meta http-equiv="Content-Security-Policy"
content="default-src'self';
script-src 'self' 'unsafe-inline';
connect-src 'self' https://api.openai.com;">
避坑指南
Safari 隐私模式
// 检测是否处于隐私模式
async function checkPrivateMode() {
try {await window.indexedDB.open('test');
return false;
} catch (e) {return e.name === 'SecurityError';}
}
// 备用方案:使用 sessionStorage + 内存缓存
移动端适配
/* 响应式布局 */
.chat-sidebar {
width: 320px;
@media (max-width: 768px) {
width: 100vw;
height: 40vh;
top: auto;
bottom: 0;
}
}
单元测试示例
// 测试加密功能
describe('encryptHistory', () => {it('should encrypt and decrypt correctly', async () => {
const content = 'test message';
const key = '32-byte-long-secret-key-1234567890';
const encrypted = await encryptHistory(content, key);
const decrypted = await decryptHistory(encrypted, key);
expect(decrypted).toEqual(content);
});
});
未来展望:离线对话能力
Service Worker 可以提供离线缓存能力:
- 预缓存常见问答对
- 离线时显示本地缓存的回答
- 网络恢复后同步未发送的消息
可以考虑结合本地 NLP 模型(如 TensorFlow.js)实现基础对话功能,待网络恢复后再与 ChatGPT 同步完整上下文。这种混合策略可以显著提升弱网环境下的用户体验。
实现这一功能需要解决的主要挑战包括:
- 本地模型的体积控制
- 对话状态的冲突合并
- 增量同步机制设计
期待看到社区在这方面涌现更多创新解决方案。
正文完
发表至: 未分类
近两天内
