<!DOCTYPE html> <html> <head> <style> .speech-container { font-family: -apple-system, BlinkMacSystemFont, sans-serif; padding: 20px; border-radius: 10px; background: white; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .controls { display: flex; gap: 10px; margin-bottom: 15px; } button { padding: 8px 16px; border-radius: 5px; border: none; background: #2196F3; color: white; cursor: pointer; transition: background 0.3s; } button:disabled { background: #ccc; cursor: not-allowed; } button:hover:not(:disabled) { background: #1976D2; } #status { margin: 10px 0; padding: 10px; border-radius: 5px; background: #E3F2FD; } #output { margin-top: 15px; padding: 15px; border-radius: 5px; background: #F5F5F5; min-height: 100px; max-height: 300px; overflow-y: auto; white-space: pre-wrap; } </style> </head> <body> <div class="speech-container"> <div class="controls"> <button id="start">Start Listening</button> <button id="stop" disabled>Stop</button> <button id="clear">Clear</button> </div> <div id="status">Ready</div> <div id="output"></div> </div> <script> // Streamlit Component Initialization function sendMessageToStreamlit(type, data) { const outData = Object.assign({ isStreamlitMessage: true, type: type, }, data); window.parent.postMessage(outData, "*"); } function init() { sendMessageToStreamlit("streamlit:componentReady", {apiVersion: 1}); } function setFrameHeight(height) { sendMessageToStreamlit("streamlit:setFrameHeight", {height: height}); } function sendDataToStreamlit(data) { sendMessageToStreamlit("streamlit:setComponentValue", data); } // Speech Recognition Setup if (!('webkitSpeechRecognition' in window)) { document.getElementById('status').textContent = 'Speech recognition not supported'; } else { const recognition = new webkitSpeechRecognition(); const startButton = document.getElementById('start'); const stopButton = document.getElementById('stop'); const clearButton = document.getElementById('clear'); const status = document.getElementById('status'); const output = document.getElementById('output'); let fullTranscript = ''; let lastUpdate = Date.now(); recognition.continuous = true; recognition.interimResults = true; startButton.onclick = () => { try { recognition.start(); status.textContent = 'Listening...'; startButton.disabled = true; stopButton.disabled = false; } catch (e) { console.error('Recognition error:', e); status.textContent = 'Error: ' + e.message; } }; stopButton.onclick = () => { recognition.stop(); status.textContent = 'Stopped'; startButton.disabled = false; stopButton.disabled = true; }; clearButton.onclick = () => { fullTranscript = ''; output.textContent = ''; sendDataToStreamlit({ value: '', dataType: "json", }); }; recognition.onresult = (event) => { let interimTranscript = ''; let finalTranscript = ''; for (let i = event.resultIndex; i < event.results.length; i++) { const transcript = event.results[i][0].transcript; if (event.results[i].isFinal) { finalTranscript += transcript + '\n'; } else { interimTranscript += transcript; } } if (finalTranscript || (Date.now() - lastUpdate > 3000)) { if (finalTranscript) { fullTranscript += finalTranscript; sendDataToStreamlit({ value: fullTranscript, dataType: "json", }); } lastUpdate = Date.now(); } output.textContent = fullTranscript + (interimTranscript ? '... ' + interimTranscript : ''); output.scrollTop = output.scrollHeight; }; recognition.onend = () => { if (!stopButton.disabled) { try { recognition.start(); } catch (e) { console.error('Failed to restart recognition:', e); status.textContent = 'Error restarting: ' + e.message; startButton.disabled = false; stopButton.disabled = true; } } }; recognition.onerror = (event) => { console.error('Recognition error:', event.error); status.textContent = 'Error: ' + event.error; if (event.error === 'not-allowed') { startButton.disabled = false; stopButton.disabled = true; } }; } // Initialize component init(); // Set initial height window.addEventListener("load", () => { setFrameHeight(document.documentElement.scrollHeight); }); </script> </body> </html>