This commit is contained in:
Julian Freeman
2026-03-11 12:11:38 -04:00
commit 5bae19f80a
8 changed files with 442 additions and 0 deletions

137
sidepanel.js Normal file
View File

@@ -0,0 +1,137 @@
// DOM Elements
const apiKeyInput = document.getElementById('apiKey');
const scriptUrlInput = document.getElementById('scriptUrl');
const saveConfigBtn = document.getElementById('saveConfig');
const extractLaptopBtn = document.getElementById('extractLaptop');
const extractPeripheralBtn = document.getElementById('extractPeripheral');
const customInput = document.getElementById('customInput');
const sendCustomBtn = document.getElementById('sendCustom');
const statusBadge = document.getElementById('statusBadge');
const resultsArea = document.getElementById('results');
// Load settings on startup
chrome.storage.local.get(['geminiApiKey', 'googleScriptUrl'], (data) => {
if (data.geminiApiKey) apiKeyInput.value = data.geminiApiKey;
if (data.googleScriptUrl) scriptUrlInput.value = data.googleScriptUrl;
});
// Save settings
saveConfigBtn.addEventListener('click', () => {
const apiKey = apiKeyInput.value.trim();
const scriptUrl = scriptUrlInput.value.trim();
chrome.storage.local.set({ geminiApiKey: apiKey, googleScriptUrl: scriptUrl }, () => {
updateStatus('已保存', 'bg-green-500 text-white');
setTimeout(() => updateStatus('待机', 'bg-gray-200 text-gray-600'), 2000);
});
});
// Action Handlers
extractLaptopBtn.addEventListener('click', () => handleExtraction('laptop'));
extractPeripheralBtn.addEventListener('click', () => handleExtraction('peripheral'));
sendCustomBtn.addEventListener('click', () => handleExtraction('custom', customInput.value));
async function handleExtraction(type, customText = '') {
const { geminiApiKey, googleScriptUrl } = await chrome.storage.local.get(['geminiApiKey', 'googleScriptUrl']);
if (!geminiApiKey) {
alert('请先输入 Gemini API 密钥。');
return;
}
updateStatus('提取中...', 'bg-blue-500 text-white');
resultsArea.textContent = '正在读取页面内容...';
try {
// 1. Get current tab content
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
const [{ result: pageData }] = await chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ['content.js']
});
resultsArea.textContent = '正在发送至 Gemini...';
// 2. Prepare Prompt
const systemPrompt = `你是一个专业的采购助手。
请从以下文本中提取产品数据。
将技术术语翻译成中文。
仅返回有效的 JSON 格式。
如果可能,将本地货币转换为美元,或者保留原始符号。
URL: ${pageData.url}
Title: ${pageData.title}`;
let userPrompt = "";
if (type === 'laptop') {
userPrompt = "提取笔记本详情品牌、CPU、内存、存储、价格、URL。仅返回 JSON。";
} else if (type === 'peripheral') {
userPrompt = "提取外设详情品牌、型号、连接方式、电池寿命、价格、URL。仅返回 JSON。";
} else {
userPrompt = `提取以下信息:${customText}。仅返回 JSON。`;
}
// 3. Call Gemini API
const geminiResult = await callGemini(geminiApiKey, systemPrompt, pageData.text, userPrompt);
const cleanedJson = parseGeminiJson(geminiResult);
resultsArea.textContent = JSON.stringify(cleanedJson, null, 2);
// 4. Send to Google Apps Script
if (googleScriptUrl) {
updateStatus('保存至表格...', 'bg-purple-500 text-white');
await sendToGoogleScript(googleScriptUrl, { ...cleanedJson, source_url: pageData.url, timestamp: new Date().toISOString() });
updateStatus('成功', 'bg-green-500 text-white');
} else {
updateStatus('完成 (未配置脚本链接)', 'bg-yellow-500 text-white');
}
} catch (error) {
console.error(error);
updateStatus('错误', 'bg-red-500 text-white');
resultsArea.textContent = `错误: ${error.message}`;
}
}
async function callGemini(apiKey, systemPrompt, contextText, userPrompt) {
const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${apiKey}`;
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
contents: [{
parts: [{
text: `${systemPrompt}\n\n上下文:\n${contextText}\n\n任务: ${userPrompt}`
}]
}]
})
});
const data = await response.json();
if (data.error) throw new Error(data.error.message);
return data.candidates[0].content.parts[0].text;
}
function parseGeminiJson(text) {
try {
// Remove markdown code blocks if any
const jsonStr = text.replace(/```json/g, '').replace(/```/g, '').trim();
return JSON.parse(jsonStr);
} catch (e) {
throw new Error('AI 返回了无效的 JSON: ' + text);
}
}
async function sendToGoogleScript(url, payload) {
const response = await fetch(url, {
method: 'POST',
mode: 'no-cors', // Apps Script often requires no-cors for simple POST
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
return response;
}
function updateStatus(text, classes) {
statusBadge.textContent = text;
statusBadge.className = `status-badge ${classes}`;
}