|
|
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) => `<span class="disfluency">${m}</span>` |
|
|
); |
|
|
} |
|
|
|
|
|
function addPauseMarker() { |
|
|
transcriptionOutput.innerHTML += " <span class='pause'>…</span> "; |
|
|
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 + " "; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (interimText) { |
|
|
const base = transcriptionOutput.innerHTML.replace(/<span class="interim">.*?<\/span>/, ""); |
|
|
transcriptionOutput.innerHTML = base + |
|
|
`<span class="interim">${highlightDisfluencies(interimText)}</span>`; |
|
|
transcriptionOutput.scrollTop = transcriptionOutput.scrollHeight; |
|
|
|
|
|
clearTimeout(pauseTimer); |
|
|
pauseTimer = setTimeout(() => addPauseMarker(), 2000); |
|
|
} |
|
|
|
|
|
|
|
|
if (finalText) { |
|
|
let cleaned = transcriptionOutput.innerHTML.replace(/<span class="interim">.*?<\/span>/, ""); |
|
|
|
|
|
const now = Date.now(); |
|
|
if (lastFinalTimestamp && now - lastFinalTimestamp > 3000) { |
|
|
cleaned += "<br><span class='pause'>…</span> "; |
|
|
} |
|
|
|
|
|
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(); |
|
|
}); |
|
|
|