Spaces:
Building
Building
Update flare-ui/src/app/services/conversation-manager.service.ts
Browse files
flare-ui/src/app/services/conversation-manager.service.ts
CHANGED
@@ -296,44 +296,6 @@ export class ConversationManagerService implements OnDestroy {
|
|
296 |
}
|
297 |
}
|
298 |
|
299 |
-
private handleTTSAudio(message: any): void {
|
300 |
-
try {
|
301 |
-
// Validate audio data
|
302 |
-
if (!message['data']) {
|
303 |
-
console.warn('TTS audio message missing data');
|
304 |
-
return;
|
305 |
-
}
|
306 |
-
|
307 |
-
// Accumulate audio chunks
|
308 |
-
this.audioQueue.push(message['data']);
|
309 |
-
|
310 |
-
if (message['is_last']) {
|
311 |
-
// All chunks received, create audio blob
|
312 |
-
const audioData = this.audioQueue.join('');
|
313 |
-
const audioBlob = this.base64ToBlob(audioData, message['mime_type'] || 'audio/mpeg');
|
314 |
-
const audioUrl = URL.createObjectURL(audioBlob);
|
315 |
-
|
316 |
-
// Update last message with audio URL
|
317 |
-
const messages = this.messagesSubject.value;
|
318 |
-
if (messages.length > 0 && messages[messages.length - 1].role === 'assistant') {
|
319 |
-
messages[messages.length - 1].audioUrl = audioUrl;
|
320 |
-
this.messagesSubject.next([...messages]);
|
321 |
-
}
|
322 |
-
|
323 |
-
// Clear queue
|
324 |
-
this.audioQueue = [];
|
325 |
-
|
326 |
-
// Auto-play if welcome message
|
327 |
-
if (message['is_welcome']) {
|
328 |
-
setTimeout(() => this.playAudio(audioUrl), 100);
|
329 |
-
}
|
330 |
-
}
|
331 |
-
} catch (error) {
|
332 |
-
console.error('Error handling TTS audio:', error);
|
333 |
-
this.audioQueue = []; // Clear queue on error
|
334 |
-
}
|
335 |
-
}
|
336 |
-
|
337 |
private playQueuedAudio(): void {
|
338 |
const messages = this.messagesSubject.value;
|
339 |
const lastMessage = messages[messages.length - 1];
|
@@ -474,9 +436,72 @@ export class ConversationManagerService implements OnDestroy {
|
|
474 |
this.messagesSubject.next([...messages]);
|
475 |
}
|
476 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
477 |
private base64ToBlob(base64: string, mimeType: string): Blob {
|
478 |
try {
|
479 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
480 |
const byteNumbers = new Array(byteCharacters.length);
|
481 |
|
482 |
for (let i = 0; i < byteCharacters.length; i++) {
|
@@ -487,6 +512,7 @@ export class ConversationManagerService implements OnDestroy {
|
|
487 |
return new Blob([byteArray], { type: mimeType });
|
488 |
} catch (error) {
|
489 |
console.error('Error converting base64 to blob:', error);
|
|
|
490 |
throw new Error('Failed to convert audio data');
|
491 |
}
|
492 |
}
|
|
|
296 |
}
|
297 |
}
|
298 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
299 |
private playQueuedAudio(): void {
|
300 |
const messages = this.messagesSubject.value;
|
301 |
const lastMessage = messages[messages.length - 1];
|
|
|
436 |
this.messagesSubject.next([...messages]);
|
437 |
}
|
438 |
|
439 |
+
private handleTTSAudio(message: any): void {
|
440 |
+
try {
|
441 |
+
// Validate audio data
|
442 |
+
if (!message['data']) {
|
443 |
+
console.warn('TTS audio message missing data');
|
444 |
+
return;
|
445 |
+
}
|
446 |
+
|
447 |
+
// Check if we need to accumulate chunks
|
448 |
+
if (!message['is_last']) {
|
449 |
+
// Accumulate audio chunks
|
450 |
+
this.audioQueue.push(message['data']);
|
451 |
+
return;
|
452 |
+
}
|
453 |
+
|
454 |
+
// All chunks received, create audio blob
|
455 |
+
this.audioQueue.push(message['data']); // Add last chunk
|
456 |
+
const audioData = this.audioQueue.join('');
|
457 |
+
|
458 |
+
try {
|
459 |
+
const audioBlob = this.base64ToBlob(audioData, message['mime_type'] || 'audio/mpeg');
|
460 |
+
const audioUrl = URL.createObjectURL(audioBlob);
|
461 |
+
|
462 |
+
// Update last message with audio URL
|
463 |
+
const messages = this.messagesSubject.value;
|
464 |
+
if (messages.length > 0 && messages[messages.length - 1].role === 'assistant') {
|
465 |
+
messages[messages.length - 1].audioUrl = audioUrl;
|
466 |
+
this.messagesSubject.next([...messages]);
|
467 |
+
}
|
468 |
+
|
469 |
+
// Clear queue
|
470 |
+
this.audioQueue = [];
|
471 |
+
|
472 |
+
// Auto-play if in playing_audio state
|
473 |
+
if (this.currentStateSubject.value === 'playing_audio') {
|
474 |
+
this.playQueuedAudio();
|
475 |
+
}
|
476 |
+
} catch (error) {
|
477 |
+
console.error('Error creating audio blob:', error);
|
478 |
+
this.audioQueue = []; // Clear queue on error
|
479 |
+
throw error;
|
480 |
+
}
|
481 |
+
} catch (error) {
|
482 |
+
console.error('Error handling TTS audio:', error);
|
483 |
+
this.audioQueue = []; // Clear queue on error
|
484 |
+
}
|
485 |
+
}
|
486 |
+
|
487 |
private base64ToBlob(base64: string, mimeType: string): Blob {
|
488 |
try {
|
489 |
+
// Clean base64 string - remove any whitespace or newlines
|
490 |
+
const cleanBase64 = base64.replace(/\s/g, '');
|
491 |
+
|
492 |
+
// Validate base64 string
|
493 |
+
if (!cleanBase64 || cleanBase64.length === 0) {
|
494 |
+
throw new Error('Empty base64 string');
|
495 |
+
}
|
496 |
+
|
497 |
+
// Check if base64 string is valid
|
498 |
+
const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
|
499 |
+
if (!base64Regex.test(cleanBase64)) {
|
500 |
+
throw new Error('Invalid base64 string format');
|
501 |
+
}
|
502 |
+
|
503 |
+
// Decode base64
|
504 |
+
const byteCharacters = atob(cleanBase64);
|
505 |
const byteNumbers = new Array(byteCharacters.length);
|
506 |
|
507 |
for (let i = 0; i < byteCharacters.length; i++) {
|
|
|
512 |
return new Blob([byteArray], { type: mimeType });
|
513 |
} catch (error) {
|
514 |
console.error('Error converting base64 to blob:', error);
|
515 |
+
console.error('Base64 preview:', base64.substring(0, 100) + '...');
|
516 |
throw new Error('Failed to convert audio data');
|
517 |
}
|
518 |
}
|