mirror of
https://github.com/027xiguapi/code-box.git
synced 2026-09-30 11:28:24 +00:00
AI聊天
This commit is contained in:
@@ -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 的响应,比如显示通知
|
||||
}
|
||||
})
|
||||
|
||||
// 检查更新
|
||||
|
||||
@@ -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
|
||||
@@ -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 已加载")
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user