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> |