refactor: improve TTS abort handling and add debug logging

This commit is contained in:
Mohamed Boudra
2025-10-20 16:40:57 +02:00
parent 97f2ff2cac
commit d2de52c57d
3 changed files with 52 additions and 31 deletions
@@ -26,16 +26,25 @@ export class TTSManager {
public async generateAndWaitForPlayback(
text: string,
emitMessage: (msg: SessionOutboundMessage) => void,
abortSignal?: AbortSignal
abortSignal: AbortSignal
): Promise<void> {
// Check if already aborted
if (abortSignal?.aborted) {
throw new Error("TTS playback aborted");
if (abortSignal.aborted) {
console.log(
`[TTS-Manager ${this.sessionId}] Aborted before generating audio`
);
return;
}
// Generate TTS audio
const { audio, format } = await synthesizeSpeech(text);
if (abortSignal.aborted) {
console.log(
`[TTS-Manager ${this.sessionId}] Aborted after generating audio`
);
return;
}
// Create unique ID for this audio segment
const audioId = uuidv4();
@@ -48,17 +57,18 @@ export class TTSManager {
this.pendingPlaybacks.set(audioId, { resolve, reject });
// Handle abort signal
if (abortSignal) {
onAbort = () => {
// Clean up pending playback
this.pendingPlaybacks.delete(audioId);
// Reject with abort error
reject(new Error("TTS playback aborted"));
};
onAbort = () => {
console.log(
`[TTS-Manager ${this.sessionId}] Aborted while waiting for playback`
);
// Clean up pending playback
this.pendingPlaybacks.delete(audioId);
// Reject with abort error
resolve();
};
// Listen for abort (once: true for auto-cleanup if abort fires)
abortSignal.addEventListener("abort", onAbort, { once: true });
}
// Listen for abort (once: true for auto-cleanup if abort fires)
abortSignal.addEventListener("abort", onAbort, { once: true });
});
// Clean up abort listener when promise settles (in case abort never fires)
@@ -135,6 +135,7 @@ export class Session {
`[Session ${this.clientId}] Waiting for aborted stream to finish cleanup`
);
await this.currentStreamPromise;
console.log(`[Session ${this.clientId}] Aborted stream finished cleanup`);
}
// Emit user message activity log
@@ -45,17 +45,22 @@ export class VoiceAssistantWebSocketServer {
this.conversationIdToWs.set(session.getConversationId(), ws);
console.log(
`[WS] Client connected: ${clientId} with conversation ${session.getConversationId()} (total: ${this.sessions.size})`
`[WS] Client connected: ${clientId} with conversation ${session.getConversationId()} (total: ${
this.sessions.size
})`
);
// Send welcome message
this.sendToClient(ws, wrapSessionMessage({
type: "status",
payload: {
status: "connected",
message: "WebSocket connection established",
},
}));
this.sendToClient(
ws,
wrapSessionMessage({
type: "status",
payload: {
status: "connected",
message: "WebSocket connection established",
},
})
);
// Set up message handler
ws.on("message", (data) => {
@@ -68,7 +73,9 @@ export class VoiceAssistantWebSocketServer {
if (!session) return;
console.log(
`[WS] Client disconnected: ${clientId} (total: ${this.sessions.size - 1})`
`[WS] Client disconnected: ${clientId} (total: ${
this.sessions.size - 1
})`
);
// Clean up session
@@ -110,7 +117,7 @@ export class VoiceAssistantWebSocketServer {
// Validate with Zod
const message = WSInboundMessageSchema.parse(parsed);
console.log(`[WS] Received message type: ${message.type}`);
console.log(`[WS] Received message type: ${message.type}`, message);
// Handle WebSocket-level messages
switch (message.type) {
@@ -138,13 +145,16 @@ export class VoiceAssistantWebSocketServer {
} catch (error: any) {
console.error("[WS] Failed to parse/handle message:", error);
// Send error to client
this.sendToClient(ws, wrapSessionMessage({
type: "status",
payload: {
status: "error",
message: `Invalid message: ${error.message}`,
},
}));
this.sendToClient(
ws,
wrapSessionMessage({
type: "status",
payload: {
status: "error",
message: `Invalid message: ${error.message}`,
},
})
);
}
}