This commit is contained in:
fzk
2025-12-26 16:59:03 +08:00
parent e0d16b91d2
commit d3fc550cc9
5 changed files with 545 additions and 0 deletions
+124
View File
@@ -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 的响应,比如显示通知
}
})
// 检查更新
+63
View File
@@ -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
+171
View File
@@ -0,0 +1,171 @@
import type { PlasmoCSConfig } from "plasmo"
export const config: PlasmoCSConfig = {
matches: ["<all_urls>"],
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 已加载")
+15
View File
@@ -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"
}
}
+172
View File
@@ -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<string> {
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)
}
})
}