import { type AudioRecorder, createAudioRecorder } from "./audio.js"; import { getConfig, isFirstRun, registerMenuCommands, setConfig, validateApiKey, } from "./config.js"; import { type InsertTarget, insertText, isOpencodePage, submitPrompt } from "./insert.js"; import { setupKeyboardShortcut } from "./keyboard.js"; import { transcribe } from "./transcribe.js"; import type { DictationState } from "./types.js"; import { setupUI } from "./ui.js"; let recorder: AudioRecorder | null = null; let currentState: DictationState = "idle"; let timerInterval: ReturnType | null = null; let elapsedSeconds = 0; let ui: ReturnType | null = null; let currentTarget: InsertTarget = "composer"; function startTimer(): void { elapsedSeconds = 0; timerInterval = setInterval(() => { elapsedSeconds++; ui?.updateState(currentState, elapsedSeconds); }, 1000); } function stopTimer(): void { if (timerInterval) { clearInterval(timerInterval); timerInterval = null; } } async function toggleDictation(target: InsertTarget): Promise { currentTarget = target; if (currentState === "idle") { await startRecording(); } else if (currentState === "recording") { await stopAndTranscribe(); } } async function startRecording(): Promise { try { recorder = createAudioRecorder(); await recorder.start(); currentState = "recording"; startTimer(); ui?.updateState(currentState, elapsedSeconds); } catch (err) { const message = err instanceof Error ? err.message : "Failed to access microphone"; ui?.toast(message, true); recorder = null; } } async function stopAndTranscribe(): Promise { if (!recorder) { return; } stopTimer(); currentState = "processing"; ui?.updateState(currentState); try { const audioBlob = await recorder.stop(); const config = getConfig(); const result = await transcribe(audioBlob, config); if (result.text) { const inserted = insertText(result.text, currentTarget); if (!inserted) { ui?.toast("Could not find input field", true); } else if (config.autoSubmit && currentTarget === "composer") { submitPrompt(); } } } catch (err) { const message = err instanceof Error ? err.message : "Transcription failed"; ui?.toast(message, true); } finally { currentState = "idle"; elapsedSeconds = 0; recorder = null; ui?.updateState(currentState); } } async function cancelRecording(): Promise { if (currentState !== "recording" || !recorder) { return; } stopTimer(); try { await recorder.stop(); } catch { // discard audio } currentState = "idle"; elapsedSeconds = 0; recorder = null; ui?.updateState(currentState); ui?.toast("Recording cancelled"); } function promptForApiKey(): void { const key = prompt("Enter your Groq API key (get one free at console.groq.com/keys):", ""); if (key && validateApiKey(key)) { setConfig({ groqApiKey: key.trim() }); ui?.toast("API key saved!"); } else if (key) { ui?.toast("Invalid key format. Must start with 'gsk_'", true); } } function promptForModel(): void { const model = prompt( "Whisper model (whisper-large-v3 or whisper-large-v3-turbo):", getConfig().model, ); if (model && (model === "whisper-large-v3" || model === "whisper-large-v3-turbo")) { setConfig({ model }); ui?.toast(`Model set to ${model}`); } else if (model) { ui?.toast("Invalid model name", true); } } function promptForLanguage(): void { const lang = prompt( "Language code (empty for auto-detect, e.g. 'ru', 'en'):", getConfig().language, ); if (lang !== null) { setConfig({ language: lang.trim() }); ui?.toast(lang.trim() ? `Language set to ${lang}` : "Auto-detect enabled"); } } function promptForWhisperPrompt(): void { const text = prompt("Whisper prompt (context for transcription):", getConfig().whisperPrompt); if (text !== null) { setConfig({ whisperPrompt: text }); ui?.toast("Whisper prompt updated"); } } function toggleAutoSubmit(): void { const config = getConfig(); setConfig({ autoSubmit: !config.autoSubmit }); ui?.toast(`Auto-submit ${!config.autoSubmit ? "enabled" : "disabled"}`); } function checkFirstRun(): void { if (isFirstRun()) { setTimeout(() => { ui?.toast("First run: Set your Groq API key via the Violentmonkey/Tampermonkey menu"); }, 2000); } } function init(): void { if (!isOpencodePage()) { setTimeout(init, 1500); return; } ui = setupUI({ onToggle: (target: InsertTarget) => { void toggleDictation(target); }, onCancel: () => { void cancelRecording(); }, }); setupKeyboardShortcut(() => { void toggleDictation("composer"); }); registerMenuCommands({ onSetKey: promptForApiKey, onToggleAutoSubmit: toggleAutoSubmit, onSetModel: promptForModel, onSetLanguage: promptForLanguage, onSetPrompt: promptForWhisperPrompt, }); checkFirstRun(); } init();