throw errors if websocket connection fails

This commit is contained in:
Quentin Fuxa 2024-12-24 19:31:08 +01:00
parent 0553b75415
commit 50bbd26517

View file

@ -1,178 +1,240 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Audio Transcription</title> <title>Audio Transcription</title>
<style> <style>
body { body {
font-family: 'Inter', sans-serif; font-family: 'Inter', sans-serif;
text-align: center; text-align: center;
margin: 20px; margin: 20px;
} }
#recordButton { #recordButton {
width: 80px; width: 80px;
height: 80px; height: 80px;
font-size: 36px; font-size: 36px;
border: none; border: none;
border-radius: 50%; border-radius: 50%;
background-color: white; background-color: white;
cursor: pointer; cursor: pointer;
box-shadow: 0 0px 10px rgba(0, 0, 0, 0.2); box-shadow: 0 0px 10px rgba(0, 0, 0, 0.2);
transition: background-color 0.3s ease, transform 0.2s ease; transition: background-color 0.3s ease, transform 0.2s ease;
} }
#recordButton.recording { #recordButton.recording {
background-color: #ff4d4d; background-color: #ff4d4d;
color: white; color: white;
} }
#recordButton:active { #recordButton:active {
transform: scale(0.95); transform: scale(0.95);
} }
#transcriptions { #transcriptions {
margin-top: 20px; margin-top: 20px;
font-size: 18px; font-size: 18px;
text-align: left; text-align: left;
} }
.transcription { .transcription {
display: inline; display: inline;
color: black; color: black;
} }
.buffer { .buffer {
display: inline; display: inline;
color: rgb(197, 197, 197); color: rgb(197, 197, 197);
} }
.settings-container { .settings-container {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
gap: 15px; gap: 15px;
margin-top: 20px; margin-top: 20px;
} }
.settings { .settings {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
gap: 5px; gap: 5px;
} }
#chunkSelector, #websocketInput { #chunkSelector,
font-size: 16px; #websocketInput {
padding: 5px; font-size: 16px;
border-radius: 5px; padding: 5px;
border: 1px solid #ddd; border-radius: 5px;
background-color: #f9f9f9; border: 1px solid #ddd;
} background-color: #f9f9f9;
#websocketInput { }
width: 200px; #websocketInput {
} width: 200px;
#chunkSelector:focus, #websocketInput:focus { }
outline: none; #chunkSelector:focus,
border-color: #007bff; #websocketInput:focus {
} outline: none;
label { border-color: #007bff;
font-size: 14px; }
} label {
</style> font-size: 14px;
}
</style>
</head> </head>
<body> <body>
<div class="settings-container"> <div class="settings-container">
<button id="recordButton">🎙️</button> <button id="recordButton">🎙️</button>
<div class="settings"> <div class="settings">
<div> <div>
<label for="chunkSelector">Chunk size (ms):</label> <label for="chunkSelector">Chunk size (ms):</label>
<select id="chunkSelector"> <select id="chunkSelector">
<option value="500">500 ms</option> <option value="500">500 ms</option>
<option value="1000" selected>1000 ms</option> <option value="1000" selected>1000 ms</option>
<option value="2000">2000 ms</option> <option value="2000">2000 ms</option>
<option value="3000">3000 ms</option> <option value="3000">3000 ms</option>
<option value="4000">4000 ms</option> <option value="4000">4000 ms</option>
<option value="5000">5000 ms</option> <option value="5000">5000 ms</option>
</select> </select>
</div> </div>
<div> <div>
<label for="websocketInput">WebSocket URL:</label> <label for="websocketInput">WebSocket URL:</label>
<input id="websocketInput" type="text" value="ws://localhost:8000/ws" /> <input id="websocketInput" type="text" value="ws://localhost:8000/ws" />
</div> </div>
</div>
</div> </div>
<p id="status"></p> </div>
<p id="status"></p>
<div id="transcriptions"></div> <div id="transcriptions"></div>
<script> <script>
let isRecording = false, websocket, recorder, chunkDuration = 1000, websocketUrl = "ws://localhost:8000/ws"; let isRecording = false,
websocket,
recorder,
chunkDuration = 1000,
websocketUrl = "ws://localhost:8000/ws";
const statusText = document.getElementById("status"); // Tracks whether the user voluntarily closed the WebSocket
const recordButton = document.getElementById("recordButton"); let userClosing = false;
const chunkSelector = document.getElementById("chunkSelector");
const websocketInput = document.getElementById("websocketInput");
const transcriptionsDiv = document.getElementById("transcriptions");
let fullTranscription = ""; // Store confirmed transcription const statusText = document.getElementById("status");
const recordButton = document.getElementById("recordButton");
const chunkSelector = document.getElementById("chunkSelector");
const websocketInput = document.getElementById("websocketInput");
const transcriptionsDiv = document.getElementById("transcriptions");
// Update chunk duration based on the selector let fullTranscription = ""; // Store confirmed transcription
chunkSelector.addEventListener("change", () => {
chunkDuration = parseInt(chunkSelector.value);
});
// Update WebSocket URL dynamically // Update chunk duration based on the selector
websocketInput.addEventListener("change", () => { chunkSelector.addEventListener("change", () => {
websocketUrl = websocketInput.value; chunkDuration = parseInt(chunkSelector.value);
}); });
function setupWebSocket() { // Update WebSocket URL dynamically, with some basic checks
websocket = new WebSocket(websocketUrl); websocketInput.addEventListener("change", () => {
websocket.onmessage = (event) => { const urlValue = websocketInput.value.trim();
const data = JSON.parse(event.data);
const { transcription, buffer } = data;
// Update confirmed transcription // Quick check to see if it starts with ws:// or wss://
fullTranscription += transcription; if (!urlValue.startsWith("ws://") && !urlValue.startsWith("wss://")) {
statusText.textContent =
"Invalid WebSocket URL. It should start with ws:// or wss://";
return;
}
websocketUrl = urlValue;
statusText.textContent = "WebSocket URL updated. Ready to connect.";
});
// Update the transcription display function setupWebSocket() {
transcriptionsDiv.innerHTML = ` try {
<span class="transcription">${fullTranscription}</span> websocket = new WebSocket(websocketUrl);
<span class="buffer">${buffer}</span> } catch (error) {
`; statusText.textContent =
}; "Invalid WebSocket URL. Please check the URL and try again.";
throw error;
}
websocket.onerror = () => { websocket.onopen = () => {
statusText.textContent = "Error connecting to WebSocket"; statusText.textContent = "Connected to server";
stopRecording(); // Stop recording if WebSocket fails };
};
websocket.onclose = (event) => {
if (userClosing) {
statusText.textContent = "WebSocket closed by user.";
} else {
statusText.textContent = "Disconnected from server (unexpected).";
}
userClosing = false;
};
websocket.onerror = () => {
statusText.textContent = "Error connecting to WebSocket";
stopRecording();
};
websocket.onmessage = (event) => {
const data = JSON.parse(event.data);
const { transcription, buffer } = data;
// Update confirmed transcription
fullTranscription += transcription;
// Update the transcription display
transcriptionsDiv.innerHTML = `
<span class="transcription">${fullTranscription}</span>
<span class="buffer">${buffer}</span>
`;
};
}
async function startRecording() {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
recorder = new MediaRecorder(stream, { mimeType: "audio/webm" });
recorder.ondataavailable = (e) => {
if (websocket && websocket.readyState === WebSocket.OPEN) {
websocket.send(e.data);
}
};
recorder.start(chunkDuration);
isRecording = true;
updateUI();
} catch (err) {
statusText.textContent =
"Error accessing microphone. Please allow microphone access.";
}
}
function stopRecording() {
userClosing = true;
recorder?.stop();
recorder = null;
isRecording = false;
websocket?.close();
websocket = null;
updateUI();
}
async function toggleRecording() {
if (!isRecording) {
fullTranscription = "";
transcriptionsDiv.innerHTML = "";
try {
setupWebSocket();
} catch (err) {
return;
} }
async function startRecording() { await startRecording();
const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); } else {
recorder = new MediaRecorder(stream, { mimeType: "audio/webm" }); stopRecording();
recorder.ondataavailable = (e) => websocket?.send(e.data); }
recorder.start(chunkDuration); // Use dynamic chunk duration }
isRecording = true;
updateUI();
}
function stopRecording() { function updateUI() {
recorder?.stop(); recordButton.classList.toggle("recording", isRecording);
recorder = null; statusText.textContent = isRecording
isRecording = false; ? "Recording..."
websocket?.close(); : "Click to start transcription";
websocket = null; }
updateUI();
}
async function toggleRecording() { recordButton.addEventListener("click", toggleRecording);
if (isRecording) stopRecording(); </script>
else {
setupWebSocket();
await startRecording();
}
}
function updateUI() {
recordButton.classList.toggle("recording", isRecording);
statusText.textContent = isRecording ? "Recording..." : "Click to start transcription";
}
recordButton.addEventListener("click", toggleRecording);
</script>
</body> </body>
</html> </html>