Files
item-scraper/sidepanel.js
Julian Freeman 5bae19f80a init
2026-03-11 12:11:38 -04:00

138 lines
5.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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}`;
}