const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; if (!SpeechRecognition) { document.getElementById("statusMessage").innerText = "❌ This browser does NOT support speech recognition. Use Chrome Desktop."; throw new Error("SpeechRecognition unsupported"); } let recognition = null; let isRecording = false; let lastFinalTimestamp = null; let pauseTimer = null; const languageSelect = document.getElementById("languageSelect"); const toggleButton = document.getElementById("toggleButton"); const transcriptionOutput = document.getElementById("transcriptionOutput"); const statusMessage = document.getElementById("statusMessage"); const clearButton = document.getElementById("clearButton"); const downloadButton = document.getElementById("downloadButton"); function highlightDisfluencies(text) { return text.replace( /\b(um+|uh+|er+|ah+|eh+|oh+|hmm+|mmm+|like)\b/gi, (m) => `${m}` ); } function addPauseMarker() { transcriptionOutput.innerHTML += " "; transcriptionOutput.scrollTop = transcriptionOutput.scrollHeight; } function initRecognition(lang) { recognition = new SpeechRecognition(); recognition.lang = lang; recognition.continuous = true; recognition.interimResults = true; recognition.onstart = () => { statusMessage.innerText = `🎙️ Listening… (${lang})`; }; recognition.onerror = (e) => { statusMessage.innerText = `⚠️ Error: ${e.error}`; if (e.error === "not-allowed") { statusMessage.innerText = "❌ Microphone blocked. Click the 🔒 icon → Allow microphone."; } }; recognition.onend = () => { if (isRecording) recognition.start(); }; recognition.onresult = (event) => { let interimText = ""; let finalText = ""; for (let i = event.resultIndex; i < event.results.length; i++) { const text = event.results[i][0].transcript.trim(); if (event.results[i].isFinal) { finalText += text + " "; } else { interimText += text + " "; } } // ---- INTERIM (live) ---- if (interimText) { const base = transcriptionOutput.innerHTML.replace(/.*?<\/span>/, ""); transcriptionOutput.innerHTML = base + `${highlightDisfluencies(interimText)}`; transcriptionOutput.scrollTop = transcriptionOutput.scrollHeight; clearTimeout(pauseTimer); pauseTimer = setTimeout(() => addPauseMarker(), 2000); } // ---- FINAL ---- if (finalText) { let cleaned = transcriptionOutput.innerHTML.replace(/.*?<\/span>/, ""); const now = Date.now(); if (lastFinalTimestamp && now - lastFinalTimestamp > 3000) { cleaned += "
"; } transcriptionOutput.innerHTML = cleaned + highlightDisfluencies(finalText); transcriptionOutput.scrollTop = transcriptionOutput.scrollHeight; lastFinalTimestamp = now; } }; } toggleButton.addEventListener("click", () => { if (isRecording) { isRecording = false; recognition.stop(); toggleButton.textContent = "🎤 Start Listening"; statusMessage.innerText = "Stopped."; } else { initRecognition(languageSelect.value); recognition.start(); isRecording = true; toggleButton.textContent = "⏹ Stop Listening"; } }); clearButton.addEventListener("click", () => { transcriptionOutput.innerHTML = ""; statusMessage.innerText = "Cleared."; }); downloadButton.addEventListener("click", () => { const text = transcriptionOutput.innerText; const blob = new Blob([text], { type: "text/plain" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "transcription.txt"; a.click(); });