From d3fc550cc9fbc78107ee0d4b9a52fe3ff02bd622 Mon Sep 17 00:00:00 2001 From: fzk <458813868@qq.com> Date: Fri, 26 Dec 2025 16:59:03 +0800 Subject: [PATCH] =?UTF-8?q?AI=E8=81=8A=E5=A4=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- background/index.ts | 124 +++++++++++++++++++++++++ background/messages/chat.ts | 63 +++++++++++++ contents/chat-integration.ts | 171 ++++++++++++++++++++++++++++++++++ locales/en/messages.json | 15 +++ utils/chat-integration.ts | 172 +++++++++++++++++++++++++++++++++++ 5 files changed, 545 insertions(+) create mode 100644 background/messages/chat.ts create mode 100644 contents/chat-integration.ts create mode 100644 utils/chat-integration.ts diff --git a/background/index.ts b/background/index.ts index 38be36e..ea08b6c 100644 --- a/background/index.ts +++ b/background/index.ts @@ -7,6 +7,130 @@ chrome.runtime.onInstalled.addListener(function (object) { if (object.reason === chrome.runtime.OnInstalledReason.INSTALL) { chrome.tabs.create({ url: chrome.runtime.getURL("options.html") }) } + + // 创建右键菜单 + createContextMenus() +}) + +// 创建右键菜单 +function createContextMenus() { + // 翻译选中文本 + chrome.contextMenus.create({ + id: "translateText", + title: chrome.i18n.getMessage("contextMenuTranslate") || "翻译选中文本", + contexts: ["selection"] + }) + + // 解释代码 + chrome.contextMenus.create({ + id: "explainCode", + title: chrome.i18n.getMessage("contextMenuExplainCode") || "解释代码", + contexts: ["selection"] + }) + + // 总结内容 + chrome.contextMenus.create({ + id: "summarizeText", + title: chrome.i18n.getMessage("contextMenuSummarize") || "总结内容", + contexts: ["selection"] + }) + + // 截图并分析 + chrome.contextMenus.create({ + id: "captureAndAnalyze", + title: chrome.i18n.getMessage("contextMenuCaptureAnalyze") || "截图并分析", + contexts: ["page"] + }) + + // 发送图片到聊天 + chrome.contextMenus.create({ + id: "sendImageToChat", + title: chrome.i18n.getMessage("contextMenuSendImage") || "发送图片到聊天", + contexts: ["image"] + }) +} + +// 监听右键菜单点击 +chrome.contextMenus.onClicked.addListener((info, tab) => { + if (!tab?.id) return + + switch (info.menuItemId) { + case "translateText": + chrome.tabs.sendMessage(tab.id, { + action: "sendToChat", + text: `请将以下文本翻译成中文:\n\n${info.selectionText}`, + autoSend: true + }) + break + + case "explainCode": + chrome.tabs.sendMessage(tab.id, { + action: "sendToChat", + text: `请解释以下代码:\n\n\`\`\`\n${info.selectionText}\n\`\`\``, + autoSend: true + }) + break + + case "summarizeText": + chrome.tabs.sendMessage(tab.id, { + action: "sendToChat", + text: `请帮我总结以下内容:\n\n${info.selectionText}`, + autoSend: true + }) + break + + case "captureAndAnalyze": + chrome.tabs.captureVisibleTab(null, { format: "png" }, (dataUrl) => { + chrome.tabs.sendMessage(tab.id, { + action: "sendImageToChat", + images: [dataUrl] + }) + setTimeout(() => { + chrome.tabs.sendMessage(tab.id, { + action: "sendToChat", + text: "请分析这张图片的内容", + autoSend: false + }) + }, 100) + }) + break + + case "sendImageToChat": + if (info.srcUrl) { + // 下载图片并转换为 base64 + fetch(info.srcUrl) + .then((response) => response.blob()) + .then((blob) => { + const reader = new FileReader() + reader.onloadend = () => { + chrome.tabs.sendMessage(tab.id, { + action: "sendImageToChat", + images: [reader.result as string] + }) + } + reader.readAsDataURL(blob) + }) + .catch((error) => { + console.error("Failed to download image:", error) + }) + } + break + } +}) + +// 监听来自 content script 的消息 +chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + if (request.action === "captureVisibleTab") { + chrome.tabs.captureVisibleTab(null, { format: "png" }, (dataUrl) => { + sendResponse(dataUrl) + }) + return true // 保持消息通道开放 + } + + if (request.action === "chatResponse") { + console.log("[Background] 收到聊天响应:", request.data) + // 可以在这里处理 AI 的响应,比如显示通知 + } }) // 检查更新 diff --git a/background/messages/chat.ts b/background/messages/chat.ts new file mode 100644 index 0000000..d84f69d --- /dev/null +++ b/background/messages/chat.ts @@ -0,0 +1,63 @@ +import type { PlasmoMessaging } from "@plasmohq/messaging" + +// 处理截图请求 +const handler: PlasmoMessaging.MessageHandler = async (req, res) => { + const { action, text, autoSend, images, prompt } = req.body + + try { + if (action === "captureVisibleTab") { + // 截取当前可见标签页 + const dataUrl = await chrome.tabs.captureVisibleTab(null, { + format: "png" + }) + res.send({ success: true, dataUrl }) + return + } + + if (action === "sendToActiveTab") { + // 发送消息到当前活动标签页 + const [tab] = await chrome.tabs.query({ + active: true, + currentWindow: true + }) + + if (tab.id) { + await chrome.tabs.sendMessage(tab.id, { + action: "sendToChat", + text, + autoSend + }) + res.send({ success: true }) + } else { + res.send({ success: false, error: "No active tab found" }) + } + return + } + + if (action === "sendImageToActiveTab") { + // 发送图片到当前活动标签页 + const [tab] = await chrome.tabs.query({ + active: true, + currentWindow: true + }) + + if (tab.id) { + await chrome.tabs.sendMessage(tab.id, { + action: "sendImageToChat", + images + }) + res.send({ success: true }) + } else { + res.send({ success: false, error: "No active tab found" }) + } + return + } + + res.send({ success: false, error: "Unknown action" }) + } catch (error) { + console.error("[Chat Handler] Error:", error) + res.send({ success: false, error: error.message }) + } +} + +export default handler diff --git a/contents/chat-integration.ts b/contents/chat-integration.ts new file mode 100644 index 0000000..a6b4dd1 --- /dev/null +++ b/contents/chat-integration.ts @@ -0,0 +1,171 @@ +import type { PlasmoCSConfig } from "plasmo" + +export const config: PlasmoCSConfig = { + matches: [""], + run_at: "document_end" +} + +// 消息类型定义 +interface ExtensionMessage { + type: "CHAT_INPUT" | "CHAT_SEND" | "CHAT_INSERT_IMAGE" + payload: { + text?: string + images?: string[] + autoSend?: boolean + } +} + +interface ChatResponse { + type: "CHAT_RESPONSE" + payload: { + content: string + timestamp: number + } +} + +// 监听来自 background 的消息 +chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + console.log("[Chat Integration] 收到消息:", request) + + if (request.action === "sendToChat") { + sendMessageToChat({ + type: request.autoSend ? "CHAT_SEND" : "CHAT_INPUT", + payload: { + text: request.text, + autoSend: request.autoSend || false + } + }) + sendResponse({ success: true }) + return true + } + + if (request.action === "sendImageToChat") { + sendMessageToChat({ + type: "CHAT_INSERT_IMAGE", + payload: { + images: request.images + } + }) + sendResponse({ success: true }) + return true + } + + if (request.action === "captureAndSend") { + // 截图并发送到聊天 + captureAndSendToChat(request.prompt) + sendResponse({ success: true }) + return true + } +}) + +// 发送消息到聊天应用 +function sendMessageToChat(message: ExtensionMessage) { + // 尝试查找聊天应用的 iframe 或窗口 + const chatWindow = findChatWindow() + + if (chatWindow) { + console.log("[Chat Integration] 发送消息到聊天应用:", message) + chatWindow.postMessage(message, "*") + } else { + console.warn("[Chat Integration] 未找到聊天应用窗口") + // 如果没有找到 iframe,直接在当前窗口发送 + window.postMessage(message, "*") + } +} + +// 查找聊天应用窗口 +function findChatWindow(): Window | null { + // 检查是否有特定的 iframe + const chatIframe = document.querySelector( + 'iframe[src*="chat"], iframe[name="chat"]' + ) as HTMLIFrameElement + + if (chatIframe && chatIframe.contentWindow) { + return chatIframe.contentWindow + } + + // 检查所有 iframe + const iframes = document.querySelectorAll("iframe") + for (const iframe of iframes) { + if (iframe.contentWindow) { + return iframe.contentWindow + } + } + + // 如果没有 iframe,返回当前窗口 + return window +} + +// 监听来自聊天应用的响应 +window.addEventListener("message", (event) => { + if (event.data.type === "CHAT_RESPONSE") { + console.log("[Chat Integration] 收到 AI 回复:", event.data.payload.content) + + // 将响应发送回 background + chrome.runtime.sendMessage({ + action: "chatResponse", + data: event.data.payload + }) + } + + if (event.data.type === "CHAT_READY") { + console.log("[Chat Integration] 聊天应用已就绪") + } +}) + +// 截图并发送到聊天 +async function captureAndSendToChat(prompt?: string) { + try { + // 使用 Chrome API 截图 + const dataUrl = await chrome.runtime.sendMessage({ + action: "captureVisibleTab" + }) + + if (dataUrl) { + // 发送图片到聊天 + sendMessageToChat({ + type: "CHAT_INSERT_IMAGE", + payload: { + images: [dataUrl] + } + }) + + // 如果有提示文本,也发送 + if (prompt) { + setTimeout(() => { + sendMessageToChat({ + type: "CHAT_INPUT", + payload: { + text: prompt, + autoSend: false + } + }) + }, 100) + } + } + } catch (error) { + console.error("[Chat Integration] 截图失败:", error) + } +} + +// 导出工具函数供其他 content scripts 使用 +export function sendTextToChat(text: string, autoSend = false) { + sendMessageToChat({ + type: autoSend ? "CHAT_SEND" : "CHAT_INPUT", + payload: { + text, + autoSend + } + }) +} + +export function sendImageToChat(images: string[]) { + sendMessageToChat({ + type: "CHAT_INSERT_IMAGE", + payload: { + images + } + }) +} + +console.log("[Chat Integration] Content script 已加载") diff --git a/locales/en/messages.json b/locales/en/messages.json index b4fb770..87f65dc 100644 --- a/locales/en/messages.json +++ b/locales/en/messages.json @@ -226,5 +226,20 @@ }, "makerQRPost": { "message": "maker Post Image" + }, + "contextMenuTranslate": { + "message": "Translate to Chinese" + }, + "contextMenuExplainCode": { + "message": "Explain Code" + }, + "contextMenuSummarize": { + "message": "Summarize Content" + }, + "contextMenuCaptureAnalyze": { + "message": "Capture & Analyze" + }, + "contextMenuSendImage": { + "message": "Send Image to Chat" } } \ No newline at end of file diff --git a/utils/chat-integration.ts b/utils/chat-integration.ts new file mode 100644 index 0000000..1999bc1 --- /dev/null +++ b/utils/chat-integration.ts @@ -0,0 +1,172 @@ +// 聊天集成工具函数 + +/** + * 发送文本到聊天应用 + * @param text 要发送的文本 + * @param autoSend 是否自动发送(true)或仅插入(false) + */ +export async function sendTextToChat(text: string, autoSend = false) { + try { + const [tab] = await chrome.tabs.query({ + active: true, + currentWindow: true + }) + + if (tab.id) { + await chrome.tabs.sendMessage(tab.id, { + action: "sendToChat", + text, + autoSend + }) + return { success: true } + } + return { success: false, error: "No active tab" } + } catch (error) { + console.error("[Chat Integration] Error:", error) + return { success: false, error: error.message } + } +} + +/** + * 发送图片到聊天应用 + * @param images Base64 格式的图片数组 + */ +export async function sendImageToChat(images: string[]) { + try { + const [tab] = await chrome.tabs.query({ + active: true, + currentWindow: true + }) + + if (tab.id) { + await chrome.tabs.sendMessage(tab.id, { + action: "sendImageToChat", + images + }) + return { success: true } + } + return { success: false, error: "No active tab" } + } catch (error) { + console.error("[Chat Integration] Error:", error) + return { success: false, error: error.message } + } +} + +/** + * 截图并发送到聊天 + * @param prompt 可选的提示文本 + */ +export async function captureAndSendToChat(prompt?: string) { + try { + const dataUrl = await chrome.tabs.captureVisibleTab(null, { + format: "png" + }) + + await sendImageToChat([dataUrl]) + + if (prompt) { + setTimeout(() => { + sendTextToChat(prompt, false) + }, 100) + } + + return { success: true } + } catch (error) { + console.error("[Chat Integration] Capture error:", error) + return { success: false, error: error.message } + } +} + +/** + * 翻译选中的文本 + * @param text 要翻译的文本 + * @param targetLang 目标语言(默认中文) + */ +export async function translateText(text: string, targetLang = "中文") { + return sendTextToChat(`请将以下文本翻译成${targetLang}:\n\n${text}`, true) +} + +/** + * 解释代码 + * @param code 代码内容 + * @param language 编程语言(可选) + */ +export async function explainCode(code: string, language?: string) { + const langPrefix = language ? ` ${language}` : "" + return sendTextToChat( + `请解释以下${langPrefix}代码:\n\n\`\`\`${language || ""}\n${code}\n\`\`\``, + true + ) +} + +/** + * 总结内容 + * @param content 要总结的内容 + */ +export async function summarizeContent(content: string) { + return sendTextToChat(`请帮我总结以下内容:\n\n${content}`, true) +} + +/** + * 从 URL 下载图片并转换为 Base64 + * @param imageUrl 图片 URL + */ +export async function downloadImageAsBase64(imageUrl: string): Promise { + try { + const response = await fetch(imageUrl) + const blob = await response.blob() + + return new Promise((resolve, reject) => { + const reader = new FileReader() + reader.onloadend = () => resolve(reader.result as string) + reader.onerror = reject + reader.readAsDataURL(blob) + }) + } catch (error) { + console.error("[Chat Integration] Download image error:", error) + throw error + } +} + +/** + * 检测页面中的代码块并添加"解释代码"按钮 + */ +export function addExplainButtonsToCodeBlocks() { + const codeBlocks = document.querySelectorAll("pre code") + + codeBlocks.forEach((block) => { + // 避免重复添加按钮 + if (block.parentElement?.querySelector(".codebox-explain-btn")) { + return + } + + const button = document.createElement("button") + button.textContent = "💬 解释代码" + button.className = "codebox-explain-btn" + button.style.cssText = ` + position: absolute; + top: 8px; + right: 8px; + padding: 4px 8px; + background: #4CAF50; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 12px; + z-index: 1000; + ` + + button.onclick = async () => { + const code = block.textContent || "" + const language = block.className.replace("language-", "") + await explainCode(code, language) + } + + // 确保 pre 元素有相对定位 + if (block.parentElement) { + block.parentElement.style.position = "relative" + block.parentElement.insertBefore(button, block) + } + }) +}