let isPlayerActive = false; const ttsUrl = 'https://vs.aimanyi.top/generate'; const grammarApiBase = 'https://gram.aimanyi.top'; // ---------- Grammar API ---------- async function fetchHighlights(text) { const response = await fetch(`${grammarApiBase}/analyze`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text }) }); if (!response.ok) { const detail = await response.text(); throw new Error(detail || '后台服务调用失败。'); } const data = await response.json(); return data.highlighted_html; } async function fetchParagraphHighlights(paragraphs) { const results = []; for (const paragraph of paragraphs) { const highlighted = await fetchHighlights(paragraph); results.push(highlighted); } return results; } chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { if (message?.type !== 'GRAMMAR_API_REQUEST') { return; } const { text, paragraphs } = message; (Array.isArray(paragraphs) && paragraphs.length > 0 ? fetchParagraphHighlights(paragraphs).then((highlightedHtmls) => ({ highlightedHtmls })) : fetchHighlights(text).then((highlightedHtml) => ({ highlightedHtml }))) .then((payload) => sendResponse(payload)) .catch((error) => { sendResponse({ error: error.message || '后台服务调用失败。' }); }); return true; }); // ---------- TTS Stream ---------- chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { if (request.action === "fetchTTS") { handleTTSRequest(request, sendResponse); return true; } }); async function handleTTSRequest(request, sendResponse) { try { const response = await fetch(ttsUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: request.text, voice: request.voice, speed: request.speed }) }); if (!response.ok) { throw new Error(`Server error: ${response.status}`); } const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; const audioChunks = []; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop() || ''; for (const line of lines) { if (!line.trim()) continue; try { const data = JSON.parse(line); if (data.audio) { audioChunks.push(data); } } catch (e) { console.error('Parse error:', e); } } } if (buffer.trim()) { try { const data = JSON.parse(buffer.trim()); if (data.audio) { audioChunks.push(data); } } catch (e) { console.error('Parse error:', e); } } sendResponse({ audioChunks }); } catch (error) { console.error('TTS request failed:', error); sendResponse({ error: error.message }); } } chrome.runtime.onConnect.addListener((port) => { if (port.name !== "tts-stream") { return; } let isDisconnected = false; const closePort = () => { if (isDisconnected) return; isDisconnected = true; try { port.disconnect(); } catch (err) { console.warn('Port disconnect failed:', err); } }; const safePostMessage = (payload) => { if (isDisconnected) return; try { port.postMessage(payload); } catch (err) { console.warn('Port post failed:', err); closePort(); } }; port.onDisconnect.addListener(() => { isDisconnected = true; }); port.onMessage.addListener(async (request) => { if (request.action !== "startStreamTTS" || isDisconnected) { return; } try { const response = await fetch(ttsUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: request.text, voice: request.voice, speed: request.speed }) }); if (!response.ok) { safePostMessage({ error: `Server error: ${response.status}` }); closePort(); return; } const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; let index = 0; while (!isDisconnected) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop() || ''; for (const line of lines) { if (!line.trim() || isDisconnected) continue; try { const data = JSON.parse(line); if (data.audio) { safePostMessage({ audio: data.audio, index: index++ }); } } catch (e) { console.error('Parse error:', e); } } } if (!isDisconnected && buffer.trim()) { try { const data = JSON.parse(buffer.trim()); if (data.audio) { safePostMessage({ audio: data.audio, index: index++ }); } } catch (e) { console.error('Parse error:', e); } } safePostMessage({ completed: true }); closePort(); } catch (error) { safePostMessage({ error: error.message }); closePort(); } }); }); // ---------- Player Toggle ---------- chrome.action.onClicked.addListener(async (tab) => { const url = tab?.url ?? tab?.pendingUrl ?? ''; if (!url || /^(chrome|chrome-extension|edge|about):/i.test(url)) { console.warn('受限制的页面,跳过播放器注入:', url); return; } if (!tab?.id) { console.warn('无法获取标签页 ID,跳过播放器注入'); return; } try { if (!isPlayerActive) { await chrome.scripting.executeScript({ target: { tabId: tab.id }, files: ['content.js'] }); await chrome.scripting.insertCSS({ target: { tabId: tab.id }, files: ['styles.css'] }); } } catch (error) { const lastError = chrome.runtime.lastError?.message || error.message; console.warn('播放器注入失败:', lastError); return; } chrome.tabs.sendMessage( tab.id, { action: isPlayerActive ? "deactivatePlayer" : "activatePlayer" }, () => { const message = chrome.runtime.lastError?.message; if (message) { console.warn('发送播放器指令失败:', message); return; } isPlayerActive = !isPlayerActive; } ); });