ChatGPT Sidebar 集成实战:解决多页面上下文保持难题

1次阅读
没有评论

共计 3771 个字符,预计需要花费 10 分钟才能阅读完成。

image.webp

技术背景:iframe 方案的局限性

在传统的 Web 应用中集成 ChatGPT 时,开发者常采用 iframe 嵌入方案。这种方式虽然简单,但在多页面应用(MPA)中暴露了几个关键问题:

ChatGPT Sidebar 集成实战:解决多页面上下文保持难题

  • 会话隔离问题 :每个 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 可以提供离线缓存能力:

  1. 预缓存常见问答对
  2. 离线时显示本地缓存的回答
  3. 网络恢复后同步未发送的消息

可以考虑结合本地 NLP 模型(如 TensorFlow.js)实现基础对话功能,待网络恢复后再与 ChatGPT 同步完整上下文。这种混合策略可以显著提升弱网环境下的用户体验。

实现这一功能需要解决的主要挑战包括:

  • 本地模型的体积控制
  • 对话状态的冲突合并
  • 增量同步机制设计

期待看到社区在这方面涌现更多创新解决方案。

正文完
 0
评论(没有评论)