| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- 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;
- }
- );
- });
|