Spaces:
Running
Running
Update flare-ui/src/app/components/chat/realtime-chat.component.ts
Browse files
flare-ui/src/app/components/chat/realtime-chat.component.ts
CHANGED
@@ -105,23 +105,24 @@ export class RealtimeChatComponent implements OnInit, OnDestroy, AfterViewChecke
|
|
105 |
).subscribe(state => {
|
106 |
console.log('📊 Conversation state:', state);
|
107 |
this.currentState = state;
|
108 |
-
this.updateRecordingState(state);
|
109 |
|
110 |
-
//
|
111 |
if (state === 'listening') {
|
112 |
this.isRecording = true;
|
|
|
|
|
|
|
|
|
113 |
} else {
|
114 |
this.isRecording = false;
|
115 |
}
|
116 |
});
|
117 |
|
118 |
-
// Subscribe to transcription
|
119 |
this.conversationManager.transcription$.pipe(
|
120 |
takeUntil(this.destroyed$)
|
121 |
).subscribe(text => {
|
122 |
-
|
123 |
-
console.log('🎙️ Transcription:', text);
|
124 |
-
}
|
125 |
this.currentTranscription = text;
|
126 |
});
|
127 |
|
@@ -140,7 +141,7 @@ export class RealtimeChatComponent implements OnInit, OnDestroy, AfterViewChecke
|
|
140 |
this.messages = initialMessages;
|
141 |
this.shouldScrollToBottom = true;
|
142 |
}
|
143 |
-
|
144 |
|
145 |
ngAfterViewChecked(): void {
|
146 |
if (this.shouldScrollToBottom) {
|
@@ -327,67 +328,85 @@ export class RealtimeChatComponent implements OnInit, OnDestroy, AfterViewChecke
|
|
327 |
}
|
328 |
|
329 |
private startVisualization(): void {
|
330 |
-
if (!this.audioVisualizer)
|
|
|
|
|
|
|
331 |
|
332 |
const canvas = this.audioVisualizer.nativeElement;
|
333 |
const ctx = canvas.getContext('2d');
|
334 |
-
if (!ctx)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
335 |
|
336 |
-
let
|
337 |
|
338 |
-
// Subscribe to volume
|
339 |
this.subscriptions.add(
|
340 |
this.audioService.volumeLevel$.subscribe(volume => {
|
341 |
-
|
|
|
|
|
342 |
})
|
343 |
);
|
344 |
|
345 |
// Animation loop
|
346 |
-
const
|
347 |
-
if (!this.isConversationActive) {
|
348 |
this.clearVisualization();
|
349 |
return;
|
350 |
}
|
351 |
|
352 |
-
// Clear canvas
|
353 |
-
ctx.fillStyle = '
|
354 |
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
355 |
|
356 |
-
|
357 |
-
|
358 |
-
|
359 |
-
|
360 |
-
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
const y = canvas.height - barHeight;
|
366 |
-
|
367 |
-
ctx.fillRect(x, y, barWidth - 2, barHeight);
|
368 |
-
}
|
369 |
}
|
370 |
|
371 |
-
this.animationId = requestAnimationFrame(
|
372 |
};
|
373 |
|
374 |
-
|
375 |
}
|
376 |
-
|
377 |
-
private
|
378 |
-
|
379 |
-
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
380 |
-
|
381 |
-
ctx.fillStyle = '#4caf50';
|
382 |
-
const barCount = 50;
|
383 |
const barWidth = canvas.width / barCount;
|
|
|
384 |
|
385 |
for (let i = 0; i < barCount; i++) {
|
386 |
-
|
|
|
|
|
|
|
387 |
const x = i * barWidth;
|
388 |
-
const y = canvas.height - barHeight;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
389 |
|
390 |
-
|
|
|
|
|
391 |
}
|
392 |
}
|
393 |
|
@@ -406,7 +425,7 @@ export class RealtimeChatComponent implements OnInit, OnDestroy, AfterViewChecke
|
|
406 |
const canvas = this.audioVisualizer.nativeElement;
|
407 |
const ctx = canvas.getContext('2d');
|
408 |
if (ctx) {
|
409 |
-
ctx.fillStyle = '#
|
410 |
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
411 |
}
|
412 |
}
|
|
|
105 |
).subscribe(state => {
|
106 |
console.log('📊 Conversation state:', state);
|
107 |
this.currentState = state;
|
|
|
108 |
|
109 |
+
// Update recording state based on conversation state
|
110 |
if (state === 'listening') {
|
111 |
this.isRecording = true;
|
112 |
+
// Make sure visualization is running
|
113 |
+
if (!this.animationId && this.isConversationActive) {
|
114 |
+
this.startVisualization();
|
115 |
+
}
|
116 |
} else {
|
117 |
this.isRecording = false;
|
118 |
}
|
119 |
});
|
120 |
|
121 |
+
// Subscribe to transcription - HER ZAMAN GÖSTER
|
122 |
this.conversationManager.transcription$.pipe(
|
123 |
takeUntil(this.destroyed$)
|
124 |
).subscribe(text => {
|
125 |
+
console.log('🎙️ Transcription update:', text);
|
|
|
|
|
126 |
this.currentTranscription = text;
|
127 |
});
|
128 |
|
|
|
141 |
this.messages = initialMessages;
|
142 |
this.shouldScrollToBottom = true;
|
143 |
}
|
144 |
+
}
|
145 |
|
146 |
ngAfterViewChecked(): void {
|
147 |
if (this.shouldScrollToBottom) {
|
|
|
328 |
}
|
329 |
|
330 |
private startVisualization(): void {
|
331 |
+
if (!this.audioVisualizer) {
|
332 |
+
console.warn('Audio visualizer element not found');
|
333 |
+
return;
|
334 |
+
}
|
335 |
|
336 |
const canvas = this.audioVisualizer.nativeElement;
|
337 |
const ctx = canvas.getContext('2d');
|
338 |
+
if (!ctx) {
|
339 |
+
console.warn('Could not get canvas context');
|
340 |
+
return;
|
341 |
+
}
|
342 |
+
|
343 |
+
// Set canvas size
|
344 |
+
canvas.width = canvas.offsetWidth;
|
345 |
+
canvas.height = canvas.offsetHeight;
|
346 |
|
347 |
+
let animationRunning = true;
|
348 |
|
349 |
+
// Subscribe to audio service volume level
|
350 |
this.subscriptions.add(
|
351 |
this.audioService.volumeLevel$.subscribe(volume => {
|
352 |
+
if (this.isRecording && volume > 0) {
|
353 |
+
this.drawVolumeVisualization(ctx, canvas, volume);
|
354 |
+
}
|
355 |
})
|
356 |
);
|
357 |
|
358 |
// Animation loop
|
359 |
+
const animate = () => {
|
360 |
+
if (!animationRunning || !this.isConversationActive) {
|
361 |
this.clearVisualization();
|
362 |
return;
|
363 |
}
|
364 |
|
365 |
+
// Clear canvas with fade effect
|
366 |
+
ctx.fillStyle = 'rgba(26, 26, 26, 0.1)';
|
367 |
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
368 |
|
369 |
+
// Draw visualization only when recording
|
370 |
+
if (this.isRecording) {
|
371 |
+
// Draw center line
|
372 |
+
ctx.strokeStyle = 'rgba(76, 175, 80, 0.3)';
|
373 |
+
ctx.lineWidth = 1;
|
374 |
+
ctx.beginPath();
|
375 |
+
ctx.moveTo(0, canvas.height / 2);
|
376 |
+
ctx.lineTo(canvas.width, canvas.height / 2);
|
377 |
+
ctx.stroke();
|
|
|
|
|
|
|
|
|
378 |
}
|
379 |
|
380 |
+
this.animationId = requestAnimationFrame(animate);
|
381 |
};
|
382 |
|
383 |
+
animate();
|
384 |
}
|
385 |
+
|
386 |
+
private drawVolumeVisualization(ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, volume: number): void {
|
387 |
+
const barCount = 64;
|
|
|
|
|
|
|
|
|
388 |
const barWidth = canvas.width / barCount;
|
389 |
+
const barSpacing = 2;
|
390 |
|
391 |
for (let i = 0; i < barCount; i++) {
|
392 |
+
// Create wave effect
|
393 |
+
const waveOffset = Math.sin((i / barCount) * Math.PI * 2 + Date.now() * 0.002) * 0.3;
|
394 |
+
const barHeight = (volume * 0.7 + waveOffset + Math.random() * 0.3) * canvas.height * 0.8;
|
395 |
+
|
396 |
const x = i * barWidth;
|
397 |
+
const y = (canvas.height - barHeight) / 2;
|
398 |
+
|
399 |
+
// Gradient color based on height
|
400 |
+
const hue = 120; // Green
|
401 |
+
const saturation = 70;
|
402 |
+
const lightness = 40 + (barHeight / canvas.height) * 30;
|
403 |
+
|
404 |
+
ctx.fillStyle = `hsl(${hue}, ${saturation}%, ${lightness}%)`;
|
405 |
+
ctx.fillRect(x + barSpacing / 2, y, barWidth - barSpacing, barHeight);
|
406 |
|
407 |
+
// Mirror effect
|
408 |
+
ctx.fillStyle = `hsla(${hue}, ${saturation}%, ${lightness}%, 0.3)`;
|
409 |
+
ctx.fillRect(x + barSpacing / 2, canvas.height - y, barWidth - barSpacing, -barHeight * 0.3);
|
410 |
}
|
411 |
}
|
412 |
|
|
|
425 |
const canvas = this.audioVisualizer.nativeElement;
|
426 |
const ctx = canvas.getContext('2d');
|
427 |
if (ctx) {
|
428 |
+
ctx.fillStyle = '#212121';
|
429 |
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
430 |
}
|
431 |
}
|