Created
December 16, 2025 07:03
-
-
Save rickmed/826483105efbc792e05456d5ded68b74 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Chrome Speech-to-Text Quality Test</title> | |
| <style> | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; | |
| max-width: 800px; | |
| margin: 50px auto; | |
| padding: 20px; | |
| background: #f5f5f5; | |
| } | |
| .container { | |
| background: white; | |
| padding: 30px; | |
| border-radius: 10px; | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
| } | |
| h1 { | |
| color: #333; | |
| margin-top: 0; | |
| } | |
| .controls { | |
| margin: 20px 0; | |
| } | |
| button { | |
| padding: 12px 24px; | |
| font-size: 16px; | |
| border: none; | |
| border-radius: 5px; | |
| cursor: pointer; | |
| margin-right: 10px; | |
| transition: all 0.3s; | |
| } | |
| #startBtn { | |
| background: #4CAF50; | |
| color: white; | |
| } | |
| #startBtn:hover { | |
| background: #45a049; | |
| } | |
| #startBtn:disabled { | |
| background: #cccccc; | |
| cursor: not-allowed; | |
| } | |
| #stopBtn { | |
| background: #f44336; | |
| color: white; | |
| } | |
| #stopBtn:hover { | |
| background: #da190b; | |
| } | |
| #stopBtn:disabled { | |
| background: #cccccc; | |
| cursor: not-allowed; | |
| } | |
| .status { | |
| padding: 10px; | |
| margin: 15px 0; | |
| border-radius: 5px; | |
| font-weight: bold; | |
| } | |
| .status.listening { | |
| background: #e8f5e9; | |
| color: #2e7d32; | |
| } | |
| .status.idle { | |
| background: #e3f2fd; | |
| color: #1565c0; | |
| } | |
| .status.error { | |
| background: #ffebee; | |
| color: #c62828; | |
| } | |
| .transcript-box { | |
| margin: 20px 0; | |
| } | |
| .transcript-box h3 { | |
| color: #555; | |
| margin-bottom: 10px; | |
| } | |
| textarea { | |
| width: 100%; | |
| min-height: 150px; | |
| padding: 15px; | |
| border: 2px solid #ddd; | |
| border-radius: 5px; | |
| font-size: 16px; | |
| font-family: inherit; | |
| resize: vertical; | |
| box-sizing: border-box; | |
| } | |
| .interim { | |
| color: #999; | |
| font-style: italic; | |
| } | |
| .settings { | |
| margin: 20px 0; | |
| padding: 15px; | |
| background: #f9f9f9; | |
| border-radius: 5px; | |
| } | |
| .settings label { | |
| display: block; | |
| margin: 10px 0; | |
| color: #555; | |
| } | |
| .settings select { | |
| padding: 5px; | |
| margin-left: 10px; | |
| border-radius: 3px; | |
| border: 1px solid #ddd; | |
| } | |
| .metrics { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |
| gap: 15px; | |
| margin: 20px 0; | |
| } | |
| .metric { | |
| padding: 15px; | |
| background: #f0f0f0; | |
| border-radius: 5px; | |
| } | |
| .metric-label { | |
| font-size: 12px; | |
| color: #666; | |
| text-transform: uppercase; | |
| } | |
| .metric-value { | |
| font-size: 24px; | |
| font-weight: bold; | |
| color: #333; | |
| margin-top: 5px; | |
| } | |
| .info { | |
| background: #fff3cd; | |
| border: 1px solid #ffc107; | |
| padding: 15px; | |
| border-radius: 5px; | |
| margin: 20px 0; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>🎤 Chrome Speech-to-Text Quality Test</h1> | |
| <div class="info"> | |
| <strong>Note:</strong> This uses Chrome's Web Speech API. Make sure to allow microphone access when prompted. | |
| </div> | |
| <div class="settings"> | |
| <h3>Settings</h3> | |
| <label> | |
| Language: | |
| <select id="languageSelect"> | |
| <option value="en-US">English (US)</option> | |
| <option value="en-GB">English (UK)</option> | |
| <option value="es-ES">Spanish (Spain)</option> | |
| <option value="es-VE">Spanish (Venezuela)</option> | |
| <option value="es-MX">Spanish (Mexico)</option> | |
| <option value="fr-FR">French</option> | |
| <option value="de-DE">German</option> | |
| <option value="it-IT">Italian</option> | |
| <option value="pt-BR">Portuguese (Brazil)</option> | |
| <option value="ja-JP">Japanese</option> | |
| <option value="zh-CN">Chinese (Mandarin)</option> | |
| </select> | |
| </label> | |
| <label> | |
| <input type="checkbox" id="continuousMode" checked> Continuous mode | |
| </label> | |
| <label> | |
| <input type="checkbox" id="interimResults" checked> Show interim results | |
| </label> | |
| </div> | |
| <div class="controls"> | |
| <button id="startBtn">Start Recording</button> | |
| <button id="stopBtn" disabled>Stop Recording</button> | |
| <button id="clearBtn">Clear Transcript</button> | |
| </div> | |
| <div id="status" class="status idle">Ready to start</div> | |
| <div class="metrics"> | |
| <div class="metric"> | |
| <div class="metric-label">Words Detected</div> | |
| <div class="metric-value" id="wordCount">0</div> | |
| </div> | |
| <div class="metric"> | |
| <div class="metric-label">Recording Time</div> | |
| <div class="metric-value" id="recordingTime">0s</div> | |
| </div> | |
| <div class="metric"> | |
| <div class="metric-label">Confidence Avg</div> | |
| <div class="metric-value" id="confidenceAvg">0%</div> | |
| </div> | |
| </div> | |
| <div class="transcript-box"> | |
| <h3>Final Transcript</h3> | |
| <textarea id="finalTranscript" placeholder="Your speech will appear here..."></textarea> | |
| </div> | |
| <div class="transcript-box"> | |
| <h3>Interim Results (what's being processed)</h3> | |
| <div id="interimTranscript" class="interim" style="padding: 15px; background: #f9f9f9; border-radius: 5px; min-height: 50px;"> | |
| <em>Interim results will appear here...</em> | |
| </div> | |
| </div> | |
| <div class="transcript-box"> | |
| <h3>Detailed Log</h3> | |
| <textarea id="detailedLog" placeholder="Detailed recognition events will be logged here..." style="min-height: 200px; font-family: monospace; font-size: 12px;"></textarea> | |
| </div> | |
| </div> | |
| <script> | |
| const startBtn = document.getElementById('startBtn'); | |
| const stopBtn = document.getElementById('stopBtn'); | |
| const clearBtn = document.getElementById('clearBtn'); | |
| const statusDiv = document.getElementById('status'); | |
| const finalTranscriptArea = document.getElementById('finalTranscript'); | |
| const interimTranscriptDiv = document.getElementById('interimTranscript'); | |
| const detailedLogArea = document.getElementById('detailedLog'); | |
| const languageSelect = document.getElementById('languageSelect'); | |
| const continuousModeCheckbox = document.getElementById('continuousMode'); | |
| const interimResultsCheckbox = document.getElementById('interimResults'); | |
| const wordCountDiv = document.getElementById('wordCount'); | |
| const recordingTimeDiv = document.getElementById('recordingTime'); | |
| const confidenceAvgDiv = document.getElementById('confidenceAvg'); | |
| let recognition; | |
| let finalTranscript = ''; | |
| let startTime; | |
| let timerInterval; | |
| let confidenceScores = []; | |
| // Check for browser support | |
| if (!('webkitSpeechRecognition' in window) && !('SpeechRecognition' in window)) { | |
| statusDiv.textContent = 'Speech recognition not supported in this browser. Please use Chrome.'; | |
| statusDiv.className = 'status error'; | |
| startBtn.disabled = true; | |
| } else { | |
| const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; | |
| recognition = new SpeechRecognition(); | |
| function initRecognition() { | |
| recognition.continuous = continuousModeCheckbox.checked; | |
| recognition.interimResults = interimResultsCheckbox.checked; | |
| recognition.lang = languageSelect.value; | |
| recognition.maxAlternatives = 3; | |
| } | |
| initRecognition(); | |
| recognition.onstart = () => { | |
| statusDiv.textContent = '🎤 Listening...'; | |
| statusDiv.className = 'status listening'; | |
| startBtn.disabled = true; | |
| stopBtn.disabled = false; | |
| startTime = Date.now(); | |
| timerInterval = setInterval(updateTimer, 100); | |
| log('Recognition started'); | |
| }; | |
| recognition.onresult = (event) => { | |
| let interimTranscript = ''; | |
| for (let i = event.resultIndex; i < event.results.length; i++) { | |
| const result = event.results[i]; | |
| const transcript = result[0].transcript; | |
| const confidence = result[0].confidence; | |
| if (result.isFinal) { | |
| finalTranscript += transcript + ' '; | |
| finalTranscriptArea.value = finalTranscript; | |
| if (confidence) { | |
| confidenceScores.push(confidence); | |
| updateConfidence(); | |
| } | |
| // Log all alternatives | |
| log(`Final: "${transcript}" (confidence: ${(confidence * 100).toFixed(1)}%)`); | |
| if (result.length > 1) { | |
| for (let j = 1; j < Math.min(result.length, 3); j++) { | |
| log(` Alt ${j}: "${result[j].transcript}" (confidence: ${(result[j].confidence * 100).toFixed(1)}%)`); | |
| } | |
| } | |
| } else { | |
| interimTranscript += transcript; | |
| } | |
| } | |
| if (interimTranscript) { | |
| interimTranscriptDiv.innerHTML = `<em style="color: #999;">${interimTranscript}</em>`; | |
| } | |
| updateWordCount(); | |
| }; | |
| recognition.onerror = (event) => { | |
| statusDiv.textContent = `Error: ${event.error}`; | |
| statusDiv.className = 'status error'; | |
| log(`Error: ${event.error} - ${event.message || 'No additional details'}`); | |
| stopRecording(); | |
| }; | |
| recognition.onend = () => { | |
| statusDiv.textContent = 'Recording stopped'; | |
| statusDiv.className = 'status idle'; | |
| startBtn.disabled = false; | |
| stopBtn.disabled = true; | |
| clearInterval(timerInterval); | |
| log('Recognition ended'); | |
| }; | |
| startBtn.onclick = () => { | |
| initRecognition(); | |
| recognition.start(); | |
| }; | |
| stopBtn.onclick = () => { | |
| stopRecording(); | |
| }; | |
| clearBtn.onclick = () => { | |
| finalTranscript = ''; | |
| finalTranscriptArea.value = ''; | |
| interimTranscriptDiv.innerHTML = '<em>Interim results will appear here...</em>'; | |
| detailedLogArea.value = ''; | |
| confidenceScores = []; | |
| updateWordCount(); | |
| updateConfidence(); | |
| log('Transcript cleared'); | |
| }; | |
| languageSelect.onchange = () => { | |
| log(`Language changed to: ${languageSelect.value}`); | |
| }; | |
| } | |
| function stopRecording() { | |
| if (recognition) { | |
| recognition.stop(); | |
| } | |
| } | |
| function updateTimer() { | |
| const elapsed = Math.floor((Date.now() - startTime) / 1000); | |
| recordingTimeDiv.textContent = `${elapsed}s`; | |
| } | |
| function updateWordCount() { | |
| const words = finalTranscript.trim().split(/\s+/).filter(w => w.length > 0); | |
| wordCountDiv.textContent = words.length; | |
| } | |
| function updateConfidence() { | |
| if (confidenceScores.length === 0) { | |
| confidenceAvgDiv.textContent = '0%'; | |
| return; | |
| } | |
| const avg = confidenceScores.reduce((a, b) => a + b, 0) / confidenceScores.length; | |
| confidenceAvgDiv.textContent = `${(avg * 100).toFixed(1)}%`; | |
| } | |
| function log(message) { | |
| const timestamp = new Date().toLocaleTimeString(); | |
| detailedLogArea.value += `[${timestamp}] ${message}\n`; | |
| detailedLogArea.scrollTop = detailedLogArea.scrollHeight; | |
| } | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment