Spaces:
Paused
Paused
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Whisper Live Transcription</title> | |
| <style> | |
| body { | |
| font-family: Arial, sans-serif; | |
| max-width: 800px; | |
| margin: 0 auto; | |
| padding: 20px; | |
| background-color: #f5f5f5; | |
| } | |
| .container { | |
| background-color: white; | |
| padding: 20px; | |
| border-radius: 8px; | |
| box-shadow: 0 2px 4px rgba(0,0,0,0.1); | |
| } | |
| .controls { | |
| margin: 20px 0; | |
| } | |
| button { | |
| padding: 10px 20px; | |
| margin: 5px; | |
| border: none; | |
| border-radius: 4px; | |
| background-color: #007bff; | |
| color: white; | |
| cursor: pointer; | |
| } | |
| button:disabled { | |
| background-color: #cccccc; | |
| cursor: not-allowed; | |
| } | |
| #clearBtn { | |
| background-color: #dc3545; | |
| } | |
| #clearBtn:hover { | |
| background-color: #c82333; | |
| } | |
| #transcription { | |
| margin-top: 20px; | |
| padding: 15px; | |
| border: 1px solid #ddd; | |
| border-radius: 4px; | |
| min-height: 100px; | |
| white-space: pre-wrap; | |
| font-family: monospace; | |
| line-height: 1.5; | |
| } | |
| .status { | |
| margin: 10px 0; | |
| padding: 10px; | |
| border-radius: 4px; | |
| } | |
| .config { | |
| margin: 10px 0; | |
| padding: 10px; | |
| background-color: #f8f9fa; | |
| border-radius: 4px; | |
| } | |
| .config input { | |
| margin-left: 10px; | |
| padding: 5px; | |
| border: 1px solid #ddd; | |
| border-radius: 4px; | |
| } | |
| .connected { | |
| background-color: #d4edda; | |
| color: #155724; | |
| } | |
| .disconnected { | |
| background-color: #f8d7da; | |
| color: #721c24; | |
| } | |
| #error-message { | |
| color: #721c24; | |
| margin: 10px 0; | |
| padding: 10px; | |
| background-color: #f8d7da; | |
| border-radius: 4px; | |
| display: none; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>Whisper Live Transcription</h1> | |
| <div class="config"> | |
| <label for="wsUrl">WebSocket URL:</label> | |
| <input type="text" id="wsUrl" value="ws://" style="width: 300px;"> | |
| </div> | |
| <div id="status" class="status disconnected">Disconnected</div> | |
| <div id="error-message"></div> | |
| <div class="controls"> | |
| <button id="startBtn">Start Recording</button> | |
| <button id="stopBtn" disabled>Stop Recording</button> | |
| <button id="reconnectBtn">Reconnect</button> | |
| <button id="clearBtn">Clear</button> | |
| </div> | |
| <div id="transcription"></div> | |
| </div> | |
| <script> | |
| // Get the current server's address | |
| const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; | |
| const wsUrlInput = document.getElementById('wsUrl'); | |
| wsUrlInput.value = `${protocol}//${window.location.host}/asr`; | |
| let mediaRecorder; | |
| let websocket; | |
| let audioContext; | |
| let audioInput; | |
| let audioStream; | |
| let isRecording = false; | |
| const transcriptionDiv = document.getElementById('transcription'); | |
| const statusDiv = document.getElementById('status'); | |
| const errorMessageDiv = document.getElementById('error-message'); | |
| const startBtn = document.getElementById('startBtn'); | |
| const stopBtn = document.getElementById('stopBtn'); | |
| const reconnectBtn = document.getElementById('reconnectBtn'); | |
| const clearBtn = document.getElementById('clearBtn'); | |
| let ws = null; | |
| let audioChunks = []; | |
| let reconnectAttempts = 0; | |
| const maxReconnectAttempts = 5; | |
| const reconnectDelay = 2000; // 2 seconds | |
| function showError(message) { | |
| errorMessageDiv.textContent = message; | |
| errorMessageDiv.style.display = 'block'; | |
| } | |
| function hideError() { | |
| errorMessageDiv.style.display = 'none'; | |
| } | |
| function updateStatus(connected) { | |
| statusDiv.textContent = connected ? 'Connected' : 'Disconnected'; | |
| statusDiv.className = `status ${connected ? 'connected' : 'disconnected'}`; | |
| reconnectBtn.disabled = connected; | |
| } | |
| function connectWebSocket() { | |
| if (ws) { | |
| ws.close(); | |
| } | |
| const wsUrl = wsUrlInput.value; | |
| console.log('Attempting to connect to:', wsUrl); | |
| ws = new WebSocket(wsUrl); | |
| ws.onopen = () => { | |
| console.log('WebSocket connection established'); | |
| updateStatus(true); | |
| startBtn.disabled = false; | |
| stopBtn.disabled = true; | |
| hideError(); | |
| reconnectAttempts = 0; | |
| }; | |
| ws.onclose = (event) => { | |
| console.log('WebSocket connection closed:', event.code, event.reason); | |
| updateStatus(false); | |
| startBtn.disabled = true; | |
| stopBtn.disabled = true; | |
| if (event.code === 1006) { | |
| showError('Connection lost. Click "Reconnect" to try again.'); | |
| } | |
| }; | |
| ws.onerror = (error) => { | |
| console.error('WebSocket error:', error); | |
| updateStatus(false); | |
| showError('Connection error occurred. Click "Reconnect" to try again.'); | |
| }; | |
| ws.onmessage = (event) => { | |
| try { | |
| console.log('Received message:', event.data); | |
| const response = JSON.parse(event.data); | |
| console.log('Parsed response:', response); | |
| if (response.text) { | |
| console.log('Adding text:', response.text); | |
| transcriptionDiv.textContent += response.text + '\n'; | |
| } else if (response.partial) { | |
| console.log('Adding partial text:', response.partial); | |
| transcriptionDiv.textContent = transcriptionDiv.textContent.replace(/[^.!?]+$/, '') + response.partial; | |
| } else if (response.error) { | |
| console.error('Server error:', response.error); | |
| showError('Server error: ' + response.error); | |
| } else if (response.buffer_transcription) { | |
| console.log('Adding buffer transcription:', response.buffer_transcription); | |
| transcriptionDiv.textContent += response.buffer_transcription + '\n'; | |
| } else if (response.full_transcription) { | |
| console.log('Adding full transcription:', response.full_transcription); | |
| transcriptionDiv.textContent += response.full_transcription + '\n'; | |
| } else if (typeof response === 'string') { | |
| console.log('Adding raw text:', response); | |
| transcriptionDiv.textContent += response + '\n'; | |
| } | |
| } catch (error) { | |
| console.error('Error parsing message:', error); | |
| console.error('Raw message:', event.data); | |
| } | |
| }; | |
| } | |
| async function startRecording() { | |
| try { | |
| const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); | |
| mediaRecorder = new MediaRecorder(stream); | |
| audioChunks = []; | |
| mediaRecorder.ondataavailable = (event) => { | |
| if (event.data.size > 0 && ws && ws.readyState === WebSocket.OPEN) { | |
| ws.send(event.data); | |
| } | |
| }; | |
| mediaRecorder.start(100); // Send chunks every 100ms | |
| startBtn.disabled = true; | |
| stopBtn.disabled = false; | |
| } catch (error) { | |
| console.error('Error accessing microphone:', error); | |
| showError('Error accessing microphone. Please ensure you have granted microphone permissions.'); | |
| } | |
| } | |
| function stopRecording() { | |
| if (mediaRecorder && mediaRecorder.state !== 'inactive') { | |
| mediaRecorder.stop(); | |
| mediaRecorder.stream.getTracks().forEach(track => track.stop()); | |
| startBtn.disabled = false; | |
| stopBtn.disabled = true; | |
| } | |
| } | |
| function clearTranscription() { | |
| transcriptionDiv.textContent = ''; | |
| } | |
| startBtn.addEventListener('click', startRecording); | |
| stopBtn.addEventListener('click', stopRecording); | |
| reconnectBtn.addEventListener('click', connectWebSocket); | |
| clearBtn.addEventListener('click', clearTranscription); | |
| // Connect to WebSocket when page loads | |
| connectWebSocket(); | |
| </script> | |
| </body> | |
| </html> |