Chrome扩展Summarizer:OpenAI API文本摘要集成问题与代码修复求助
Summarizer Chrome扩展修复方案
我开发了一款名为Summarizer的Chrome扩展,功能是让用户高亮段落生成摘要,基于OpenAI API实现。目前遇到三个核心问题:API集成存在安全风险、摘要逻辑有故障、生成的摘要无法保存到侧边栏历史,同时希望优化API调用成本。
扩展预期运行流程:
- 用户高亮一段文本
- 高亮区域附近弹出「Summarize It?」按钮
- 点击按钮后,高亮文本发送至OpenAI API生成摘要
- 摘要通过弹窗展示,同时保存到侧边栏历史记录
原始代码存在的问题
- API密钥暴露:content.js中硬编码API密钥,任何用户都能查看,存在安全风险;直接在content script发起API请求,易引发安全隐患
- 按钮事件逻辑错误:点击其他区域隐藏按钮的代码中,使用了未定义的
summarizeBtn变量,导致报错,按钮无法正常隐藏 - 摘要未存入历史:生成摘要后未调用Chrome存储API保存,侧边栏无法展示历史记录
- 成本控制缺失:未限制输入文本长度,API调用的
max_tokens固定,易造成不必要的token消耗
修复后的文件代码
manifest.json
添加必要权限,确保API请求合法:
{ "manifest_version": 3, "name": "Summarizer", "version": "1.0", "description": "Quickly summarize and save important text.", "permissions": ["storage", "sidePanel", "activeTab"], "action": { "default_icon": { "48": "icons/icon48.png", "128": "icons/icon128.png" } }, "side_panel": { "default_path": "sidebar.html" }, "background": { "service_worker": "background.js" }, "content_scripts": [ { "matches": ["<all_urls>"], "js": ["content.js"], "run_at": "document_end" } ], "host_permissions": ["https://api.openai.com/*"] }
background.js
将API请求逻辑移至后台,避免密钥暴露,同时处理摘要存储:
chrome.runtime.onInstalled.addListener(() => { chrome.sidePanel .setPanelBehavior({ openPanelOnActionClick: true }) .catch((error) => console.error("Side Panel Error:", error)); }); // 处理content script的摘要请求 chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => { if (request.action === "summarizeText") { const apiKey = "YOUR_OPENAI_API_KEY"; // 建议通过chrome.storage.local安全存储 const url = "https://api.openai.com/v1/chat/completions"; try { const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${apiKey}` }, body: JSON.stringify({ model: "gpt-3.5-turbo", messages: [{ role: "system", content: "用简洁准确的语言总结文本,控制在100词以内" }, { role: "user", content: request.text }], max_tokens: 100, temperature: 0.3 }) }); if (!response.ok) { const errorMsg = await response.text(); console.error("API Error:", errorMsg); sendResponse({ success: false, message: "API请求失败:" + errorMsg }); return; } const data = await response.json(); const summary = data.choices?.[0]?.message?.content.trim() || "生成摘要失败"; // 保存摘要到本地存储 chrome.storage.local.get(["summaries"], (result) => { const summaries = result.summaries || []; summaries.unshift(summary); chrome.storage.local.set({ summaries }, () => { sendResponse({ success: true, summary }); }); }); return true; } catch (error) { console.error("Summarization error:", error); sendResponse({ success: false, message: "生成摘要时出错:" + error.message }); } } });
content.js
修复按钮事件错误,改为向后台发送请求:
document.addEventListener("mouseup", function (event) { let selectedText = window.getSelection().toString().trim(); let existingButton = document.getElementById("summarize-btn"); // 取消选中文本时移除按钮 if (!selectedText) { if (existingButton) existingButton.remove(); return; } // 创建按钮(仅一次) if (!existingButton) { existingButton = document.createElement("button"); existingButton.id = "summarize-btn"; existingButton.innerText = "Summarize It?"; existingButton.style.position = "absolute"; existingButton.style.zIndex = "1000"; existingButton.style.background = "#ff5722"; existingButton.style.color = "white"; existingButton.style.border = "none"; existingButton.style.padding = "8px"; existingButton.style.cursor = "pointer"; existingButton.style.borderRadius = "5px"; existingButton.style.boxShadow = "2px 2px 5px rgba(0,0,0,0.2)"; document.body.appendChild(existingButton); // 绑定点击事件 existingButton.onclick = async function () { existingButton.innerText = "Summarizing..."; existingButton.disabled = true; // 向后台发送请求 chrome.runtime.sendMessage( { action: "summarizeText", text: selectedText }, (response) => { alert(response.success ? `摘要:\n${response.summary}` : `错误:\n${response.message}`); existingButton.remove(); } ); }; // 点击其他区域隐藏按钮 document.addEventListener("click", function removeBtn(event) { if (!existingButton.contains(event.target)) { existingButton.remove(); document.removeEventListener("click", removeBtn); } }, { once: true }); } // 定位按钮到高亮文本下方 let rect = window.getSelection().getRangeAt(0).getBoundingClientRect(); existingButton.style.top = `${rect.bottom + window.scrollY + 5}px`; existingButton.style.left = `${rect.left + window.scrollX}px`; });
sidebar.js
优化历史记录展示,添加实时更新监听:
document.addEventListener("DOMContentLoaded", function () { const summaryList = document.getElementById("summary-list"); function loadSummaries() { chrome.storage.local.get(["summaries"], function (result) { summaryList.innerHTML = ""; const summaries = result.summaries || []; summaries.forEach((summary, index) => { displaySummary(summary, index); }); }); } function displaySummary(summary, index) { const summaryItem = document.createElement("div"); summaryItem.classList.add("summary-item"); summaryItem.innerHTML = ` <p><strong>摘要 ${index + 1}:</strong><br>${summary}</p> <button data-index="${index}">删除</button> `; summaryItem.querySelector("button").addEventListener("click", function () { deleteSummary(index); }); summaryList.appendChild(summaryItem); } function deleteSummary(index) { chrome.storage.local.get(["summaries"], function (result) { let summaries = result.summaries || []; summaries.splice(index, 1); chrome.storage.local.set({ summaries: summaries }, function () { loadSummaries(); }); }); } // 实时更新历史记录 chrome.storage.onChanged.addListener((changes) => { if (changes.summaries) loadSummaries(); }); loadSummaries(); });
成本优化建议
- 限制输入文本长度:在content.js中添加判断,超过1000词时提示用户缩短文本
- 模型选择:提供选项让用户切换到更经济的模型(如
gpt-3.5-turbo-16k适合长文本) - 精准Prompt:明确要求摘要长度,减少不必要的token生成
- 本地缓存:对相同文本的摘要进行缓存,避免重复调用API
内容的提问来源于stack exchange,提问作者Jeric
相关产品推荐
相关产品推荐

