// DOM Elements const apiKeyInput = document.getElementById('apiKey'); const scriptUrlInput = document.getElementById('scriptUrl'); const modelInput = document.getElementById('modelInput'); 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'); // Collapsible logic const toggleConfig = document.getElementById('toggleConfig'); const configContent = document.getElementById('configContent'); const configChevron = document.getElementById('configChevron'); toggleConfig.addEventListener('click', () => { const isHidden = configContent.classList.toggle('hidden'); configChevron.classList.toggle('rotate-180', !isHidden); }); // Load settings on startup chrome.storage.local.get(['geminiApiKey', 'googleScriptUrl', 'geminiModel'], (data) => { if (data.geminiApiKey) { apiKeyInput.value = data.geminiApiKey; // Fold by default if already configured configContent.classList.add('hidden'); configChevron.classList.remove('rotate-180'); } else { // Expand if no API key configContent.classList.remove('hidden'); configChevron.classList.add('rotate-180'); } if (data.googleScriptUrl) scriptUrlInput.value = data.googleScriptUrl; if (data.geminiModel) modelInput.value = data.geminiModel; }); // Save settings saveConfigBtn.addEventListener('click', () => { const apiKey = apiKeyInput.value.trim(); const scriptUrl = scriptUrlInput.value.trim(); const model = modelInput.value.trim(); chrome.storage.local.set({ geminiApiKey: apiKey, googleScriptUrl: scriptUrl, geminiModel: model }, () => { 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, geminiModel } = await chrome.storage.local.get(['geminiApiKey', 'googleScriptUrl', 'geminiModel']); if (!geminiApiKey) { alert('请先输入 Gemini API 密钥。'); return; } const selectedModel = geminiModel || 'gemini-1.5-flash'; updateStatus('提取中...', 'bg-blue-500 text-white'); resultsArea.textContent = `正在使用 ${selectedModel} 读取内容...`; 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, selectedModel, 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, model, systemPrompt, contextText, userPrompt) { const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}: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}`; }