Chrome ChatGPT插件开发实战:从零构建智能对话助手

1次阅读
没有评论

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

image.webp

背景与痛点分析

在日常使用网页版 ChatGPT 时,我们常遇到几个典型问题:

Chrome ChatGPT 插件开发实战:从零构建智能对话助手

  1. 上下文丢失 :页面刷新或跳转后,对话历史无法保留,需要反复粘贴之前的内容
  2. 操作效率低 :每次使用都需要打开新标签页,无法快速在当前页面调用
  3. 功能局限 :缺乏个性化设置(如常用提示词收藏)和本地数据存储能力

这些痛点正是浏览器插件可以完美解决的场景。通过开发 Chrome 插件,我们可以实现:

  • 常驻侧边栏快速访问
  • 本地存储对话历史
  • 跨页面保持会话上下文

技术选型:Manifest V3 的必然选择

Chrome 插件开发面临的首要决策是选择 Manifest 版本。当前存在 V2 和 V3 两个主要版本:

特性 Manifest V2 Manifest V3
后台页面 常驻 background page Service Worker
远程代码 允许 eval 动态执行 严格禁止
网络请求 webRequest API declarativeNetRequest
审核要求 逐步淘汰 商店强制要求

选择 V3 的主要原因:

  1. 未来兼容性 :2023 年后新插件必须使用 V3
  2. 性能优势 :Service Worker 按需运行节省资源
  3. 安全性提升 :禁止远程代码执行降低风险

核心架构实现

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+ 的侧边栏 API
  • content_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 错误

解决方案:

  1. 在 manifest 中声明权限:
    "host_permissions": ["https://api.openai.com/*"]
  2. 通过后台服务转发请求(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)集成:

  1. 通过 WebSocket 连接本地 LLM 服务
  2. 使用 WebUSB/WebSerial API 直接与本地设备通信
  3. 利用 WebAssembly 在浏览器中运行量化模型

这种混合架构既能保护隐私,又能降低 API 成本,是未来值得探索的方向。

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