|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<title>Continuous Speech Input</title> |
|
<style> |
|
body { |
|
font-family: sans-serif; |
|
padding: 20px; |
|
max-width: 800px; |
|
margin: 0 auto; |
|
} |
|
button { |
|
padding: 10px 20px; |
|
margin: 10px 5px; |
|
font-size: 16px; |
|
border: none; |
|
border-radius: 5px; |
|
cursor: pointer; |
|
color: white; |
|
} |
|
#start { background: #ff4b4b; } |
|
#stop { background: #4b4bff; } |
|
#clear { background: #666; } |
|
#status { |
|
margin: 10px 0; |
|
padding: 10px; |
|
background: #e8f5e9; |
|
border-radius: 4px; |
|
} |
|
#output { |
|
white-space: pre-wrap; |
|
padding: 15px; |
|
background: #f5f5f5; |
|
border-radius: 4px; |
|
margin: 10px 0; |
|
min-height: 100px; |
|
max-height: 400px; |
|
overflow-y: auto; |
|
} |
|
.controls { |
|
margin: 10px 0; |
|
display: flex; |
|
gap: 10px; |
|
} |
|
button:disabled { |
|
opacity: 0.6; |
|
cursor: not-allowed; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<input id="myinput" value="" style="display: none;" /> |
|
|
|
<div class="controls"> |
|
<button id="start">Start Listening</button> |
|
<button id="stop" disabled>Stop Listening</button> |
|
<button id="clear">Clear Text</button> |
|
</div> |
|
<div id="status">Ready to record...</div> |
|
<div id="output"></div> |
|
|
|
<input type="hidden" id="streamlit-data" value=""> |
|
|
|
<script> |
|
if (!('webkitSpeechRecognition' in window)) { |
|
document.getElementById('status').textContent = 'Speech recognition not supported in this browser'; |
|
} 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 lastUpdateTime = Date.now(); |
|
|
|
recognition.continuous = true; |
|
recognition.interimResults = true; |
|
|
|
const startRecognition = () => { |
|
try { |
|
recognition.start(); |
|
status.textContent = 'Listening...'; |
|
startButton.disabled = true; |
|
stopButton.disabled = false; |
|
} catch (e) { |
|
console.error(e); |
|
status.textContent = 'Error: ' + e.message; |
|
} |
|
}; |
|
|
|
|
|
window.addEventListener('load', () => { |
|
setTimeout(startRecognition, 1000); |
|
}); |
|
|
|
startButton.onclick = startRecognition; |
|
|
|
stopButton.onclick = () => { |
|
recognition.stop(); |
|
status.textContent = 'Stopped'; |
|
startButton.disabled = false; |
|
stopButton.disabled = true; |
|
}; |
|
|
|
clearButton.onclick = () => { |
|
fullTranscript = ''; |
|
output.textContent = ''; |
|
document.getElementById('streamlit-data').value = ''; |
|
sendDataToPython({ |
|
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() - lastUpdateTime > 5000)) { |
|
if (finalTranscript) { |
|
fullTranscript += finalTranscript; |
|
document.getElementById('streamlit-data').value = fullTranscript; |
|
} |
|
lastUpdateTime = Date.now(); |
|
} |
|
|
|
output.textContent = fullTranscript + (interimTranscript ? '... ' + interimTranscript : ''); |
|
output.scrollTop = output.scrollHeight; |
|
|
|
sendDataToPython({ |
|
value: fullTranscript, |
|
dataType: "json", |
|
}); |
|
}; |
|
|
|
recognition.onend = () => { |
|
if (!stopButton.disabled) { |
|
try { |
|
recognition.start(); |
|
console.log('Restarted recognition'); |
|
} catch (e) { |
|
console.error('Failed to restart:', 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' || event.error === 'service-not-allowed') { |
|
startButton.disabled = false; |
|
stopButton.disabled = true; |
|
} |
|
}; |
|
} |
|
|
|
|
|
function sendMessageToStreamlitClient(type, data) { |
|
var outData = Object.assign({ |
|
isStreamlitMessage: true, |
|
type: type, |
|
}, data); |
|
window.parent.postMessage(outData, "*"); |
|
} |
|
|
|
function init() { |
|
sendMessageToStreamlitClient("streamlit:componentReady", {apiVersion: 1}); |
|
} |
|
|
|
function setFrameHeight(height) { |
|
sendMessageToStreamlitClient("streamlit:setFrameHeight", {height: height}); |
|
} |
|
|
|
function sendDataToPython(data) { |
|
sendMessageToStreamlitClient("streamlit:setComponentValue", data); |
|
} |
|
|
|
function onDataFromPython(event) { |
|
if (event.data.type !== "streamlit:render") return; |
|
document.getElementById("myinput").value = event.data.args.my_input_value; |
|
} |
|
|
|
window.addEventListener("message", onDataFromPython); |
|
init(); |
|
|
|
window.addEventListener("load", function() { |
|
window.setTimeout(function() { |
|
setFrameHeight(document.documentElement.clientHeight) |
|
}, 0); |
|
}); |
|
|
|
setFrameHeight(400); |
|
</script> |
|
</body> |
|
</html> |