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>
This commit is contained in:
Executable
+77
@@ -0,0 +1,77 @@
|
||||
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);
|
||||
Reference in New Issue
Block a user