Files
voicecoach/web-client/app.js
T
jeremy 59856bd4fc feat: initial commit — VoiceCoach real-time voice coaching pipeline
Services: Pipecat agent (LiveKit + Claude + Whisper STT + Piper TTS),
SpeechBrain analysis service, Piper TTS service, web client (nginx).
Includes LiveKit server config and docker-compose orchestration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 01:15:24 -05:00

78 lines
2.3 KiB
JavaScript
Executable File

const LIVEKIT_URL = "wss://voice.hobbs.farm";
let room = null;
async function connect() {
setStatus("connecting", "Connecting...");
try {
// Request a token from the token service
const response = await fetch("/api/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ identity: "jeremy-" + Date.now() }),
});
if (!response.ok) throw new Error("Failed to get token");
const { token } = await response.json();
room = new LivekitClient.Room({
audioCaptureDefaults: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
},
});
room.on(LivekitClient.RoomEvent.Connected, () => {
setStatus("connected", "Connected");
document.getElementById("connectBtn").style.display = "none";
document.getElementById("disconnectBtn").style.display = "block";
});
room.on(LivekitClient.RoomEvent.Disconnected, () => {
setStatus("disconnected", "Disconnected");
document.getElementById("connectBtn").style.display = "block";
document.getElementById("disconnectBtn").style.display = "none";
});
room.on(LivekitClient.RoomEvent.TrackSubscribed, (track) => {
if (track.kind === LivekitClient.Track.Kind.Audio) {
const element = track.attach();
document.body.appendChild(element);
element.play();
}
});
await room.connect(LIVEKIT_URL, token);
await room.localParticipant.setMicrophoneEnabled(true);
} catch (err) {
setStatus("disconnected", "Error: " + err.message);
console.error(err);
}
}
async function disconnect() {
if (room) {
await room.disconnect();
room = null;
}
}
function setMode(mode) {
document.getElementById("modeDisplay").textContent = "Mode: " + mode;
if (room && room.localParticipant) {
const encoder = new TextEncoder();
room.localParticipant.publishData(
encoder.encode(JSON.stringify({ type: "mode_change", mode })),
{ reliable: true }
);
}
}
function setStatus(state, text) {
const el = document.getElementById("status");
el.className = "status " + state;
el.textContent = text;
}
document.getElementById("connectBtn").addEventListener("click", connect);
document.getElementById("disconnectBtn").addEventListener("click", disconnect);