diff --git a/content.js b/content.js index e1c1a43..833a27e 100644 --- a/content.js +++ b/content.js @@ -3,7 +3,7 @@ script.type = 'module'; script.src = chrome.runtime.getURL('main.js'); document.head.appendChild(script); -// 2. 监听来自网页(main.js)的请求 +// 监听来自网页(main.js)的请求 window.addEventListener("DO_AI_REQUEST", async (event) => { const { userInput, systemPrompt } = event.detail; diff --git a/main.js b/main.js index 5952e2f..d8a12f7 100644 --- a/main.js +++ b/main.js @@ -1,64 +1,93 @@ +// main.js import { createPanel } from './scripts/panel.js'; import { handleCommand, COMMANDS } from './scripts/commands.js'; import { initVoice } from './scripts/voice.js'; import { translateToCommand } from './scripts/ai.js'; -// 等待页面加载完成 const init = async () => { - console.log("🚀 插件主程序启动..."); const ui = createPanel(); + let spaceTimer = null; + let isRecording = false; async function startProcess(text) { - const rawText = text.trim(); - if (!rawText) return; - - console.log("📩 接收到输入:", rawText); - - // 1. 本地匹配逻辑 - const localMatch = COMMANDS.find(c => rawText.includes(c.key)); - if (localMatch) { - console.log("🎯 本地关键词匹配成功:", localMatch.key); - handleCommand(localMatch.key); - return; - } - - // 2. AI 翻译逻辑 + if (!text) return; ui.setLoading(true); try { - const aiResult = await translateToCommand(rawText); - if (aiResult) { - handleCommand(aiResult); + const localMatch = COMMANDS.find(c => text.includes(c.key)); + if (localMatch) { + handleCommand(localMatch.key); } else { - console.warn("❌ AI 无法理解该指令"); + const aiResult = await translateToCommand(text); + if (aiResult) handleCommand(aiResult); } - } catch (err) { - console.error("AI 流程出错:", err); } finally { ui.setLoading(false); } } - // 输入框回车事件监听 - ui.input.addEventListener("keydown", (e) => { + const voiceCtrl = initVoice(document.getElementById("automation-ai-panel"), (text) => { + ui.input.value = text; + startProcess(text); + }); + + // --- 逻辑 A: 保留原有按钮点击录音 --- + ui.btn.onclick = () => { + if (!isRecording) { + voiceCtrl.start(); + ui.setRecording(true); + isRecording = true; + // 按钮模式下 3秒后自动停止,或靠 recognition 自动结束 + setTimeout(() => { + if (isRecording) { + voiceCtrl.stop(); + ui.setRecording(false); + isRecording = false; + } + }, 3000); + } + }; + + // --- 逻辑 B: 空格长按 0.5s 触发 --- + window.addEventListener("keydown", (e) => { + if (e.code === "Space" && e.target.tagName !== "INPUT" && e.target.tagName !== "TEXTAREA") { + if (spaceTimer || isRecording) return; + + spaceTimer = setTimeout(() => { + if (voiceCtrl.supportSpeech) { + voiceCtrl.start(); + ui.setRecording(true); + isRecording = true; + } + }, 500); + } + }); + + window.addEventListener("keyup", (e) => { + if (e.code === "Space") { + if (spaceTimer) { + clearTimeout(spaceTimer); + spaceTimer = null; + } + if (isRecording) { + // 松开空格,延迟一小会儿停止,确保最后几个字能录进去 + setTimeout(() => { + voiceCtrl.stop(); + ui.setRecording(false); + isRecording = false; + }, 200); + e.preventDefault(); + } + } + }); + + ui.input.onkeydown = (e) => { if (e.key === "Enter") { - e.preventDefault(); // 防止某些页面表单提交刷新 const val = ui.input.value; ui.input.value = ""; startProcess(val); } - }); - - // 语音识别初始化 - // 修正:确保 voice.js 里的 handleCommand 回调指向我们的 startProcess - initVoice(document.getElementById("automation-ai-panel"), (spokenText) => { - ui.input.value = spokenText; - startProcess(spokenText); - }); + }; }; -// 确保在 DOM 完成后运行 -if (document.readyState === "complete" || document.readyState === "interactive") { - init(); -} else { - window.addEventListener("DOMContentLoaded", init); -} \ No newline at end of file +if (document.readyState === "complete") init(); +else window.addEventListener("load", init); \ No newline at end of file diff --git a/scripts/panel.js b/scripts/panel.js index 99af3d7..b924575 100644 --- a/scripts/panel.js +++ b/scripts/panel.js @@ -1,6 +1,6 @@ +// scripts/panel.js export function createPanel() { const panelId = "automation-ai-panel"; - // 防止重复创建 let panel = document.getElementById(panelId); if (panel) return getUIRefs(panel); @@ -10,21 +10,30 @@ export function createPanel() { position: fixed; bottom: 24px; right: 24px; z-index: 2147483647; background: white; border-radius: 10px; box-shadow: 0 4px 12px rgba(0,0,0,.2); padding: 12px; width: 260px; - font-size: 14px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + font-size: 14px; font-family: sans-serif; `; panel.innerHTML = ` +
- - +
-
+
+
准备就绪 - +
`; @@ -33,19 +42,25 @@ export function createPanel() { } function getUIRefs(panel) { - const input = panel.querySelector("#voiceTextInput"); - const btn = panel.querySelector("#voiceBtn"); - const loading = panel.querySelector("#aiLoading"); - const statusText = panel.querySelector("#statusText"); - return { - input, - btn, - setLoading: (isLoading) => { - loading.style.display = isLoading ? "inline" : "none"; - statusText.style.display = isLoading ? "none" : "inline"; - input.disabled = isLoading; - if (!isLoading) input.focus(); + btn: panel.querySelector("#voiceBtn"), + input: panel.querySelector("#voiceTextInput"), + setLoading: (loading) => { + panel.querySelector("#aiLoading").style.display = loading ? "inline" : "none"; + panel.querySelector("#statusText").style.visibility = loading ? "hidden" : "visible"; + }, + setRecording: (isRecording) => { + const dot = panel.querySelector("#recordingIndicator"); + const text = panel.querySelector("#statusText"); + if (isRecording) { + dot.style.display = "inline-block"; + text.innerText = "正在聆听..."; + text.style.color = "#ff4d4f"; + } else { + dot.style.display = "none"; + text.innerText = "准备就绪"; + text.style.color = "#999"; + } } }; } \ No newline at end of file diff --git a/scripts/voice.js b/scripts/voice.js index 48100e9..5e9de82 100644 --- a/scripts/voice.js +++ b/scripts/voice.js @@ -1,36 +1,28 @@ -// 语音识别 - -export function initVoice(panel, handleCommand) { +// scripts/voice.js +export function initVoice(panel, onResultCallback) { const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; - const supportSpeech = !!SpeechRecognition; - let recognition; + if (!SpeechRecognition) return { supportSpeech: false }; - if (supportSpeech) { - recognition = new SpeechRecognition(); - recognition.lang = "zh-CN"; - recognition.continuous = false; - recognition.interimResults = false; + const recognition = new SpeechRecognition(); + recognition.lang = "zh-CN"; + recognition.continuous = false; // 改为 false,确保每次停止都能立即结算 + recognition.interimResults = false; - recognition.onresult = (event) => { - const text = event.results[0][0].transcript.trim(); - handleCommand(text); - }; - - recognition.onerror = (event) => { - console.error("语音识别错误", event.error); - }; - - panel.querySelector("#voiceBtn").onclick = () => recognition.start(); - } else { - panel.querySelector("#voiceBtn").disabled = true; - panel.querySelector("#voiceBtn").innerText = "❌"; - } - - document.addEventListener("keydown", (e) => { - if (e.altKey && e.key.toLowerCase() === "v" && supportSpeech && recognition) { - recognition.start(); + recognition.onresult = (event) => { + const text = event.results[0][0].transcript.trim(); + if (text) { + console.log("🎤 识别结果:", text); + onResultCallback(text); } - }); + }; - return supportSpeech; + recognition.onerror = (event) => { + if (event.error !== 'aborted') console.error("语音错误:", event.error); + }; + + return { + supportSpeech: true, + start: () => { try { recognition.start(); } catch(e){} }, + stop: () => { try { recognition.stop(); } catch(e){} } + }; } \ No newline at end of file