ciyidogan commited on
Commit
b6ef74b
·
verified ·
1 Parent(s): 66e7ae8

Update flare-ui/src/app/components/chat/realtime-chat.component.ts

Browse files
flare-ui/src/app/components/chat/realtime-chat.component.ts CHANGED
@@ -40,12 +40,9 @@ export class RealtimeChatComponent implements OnInit, OnDestroy, AfterViewChecke
40
  isRecording = false;
41
  isPlayingAudio = false;
42
  currentState: ConversationState = 'idle';
43
- currentTranscription = '';
44
  messages: ConversationMessage[] = [];
45
  error = '';
46
  loading = false;
47
-
48
- isVisualizationActive = false;
49
 
50
  conversationStates: ConversationState[] = [
51
  'idle', 'listening', 'processing_stt', 'processing_llm', 'processing_tts', 'playing_audio'
@@ -56,6 +53,7 @@ export class RealtimeChatComponent implements OnInit, OnDestroy, AfterViewChecke
56
  private shouldScrollToBottom = false;
57
  private animationId: number | null = null;
58
  private currentAudio: HTMLAudioElement | null = null;
 
59
 
60
  constructor(
61
  private conversationManager: ConversationManagerService,
@@ -105,20 +103,18 @@ export class RealtimeChatComponent implements OnInit, OnDestroy, AfterViewChecke
105
  this.conversationManager.currentState$.pipe(
106
  takeUntil(this.destroyed$)
107
  ).subscribe(state => {
108
- console.log('📊 UI received conversation state:', state);
109
  this.currentState = state;
110
 
111
- // State ne olursa olsun, konuşma aktifse recording true
112
- this.isRecording = this.isConversationActive && state !== 'error';
113
- console.log('🎤 Recording state updated:', this.isRecording, 'State:', state);
114
- });
115
-
116
- // Subscribe to transcription
117
- this.conversationManager.transcription$.pipe(
118
- takeUntil(this.destroyed$)
119
- ).subscribe(text => {
120
- console.log('🎙️ Transcription update:', text ? `"${text}"` : '(empty)');
121
- this.currentTranscription = text;
122
  });
123
 
124
  // Subscribe to errors
@@ -137,7 +133,7 @@ export class RealtimeChatComponent implements OnInit, OnDestroy, AfterViewChecke
137
  this.shouldScrollToBottom = true;
138
  }
139
  }
140
-
141
  ngAfterViewChecked(): void {
142
  if (this.shouldScrollToBottom) {
143
  this.scrollToBottom();
@@ -313,13 +309,7 @@ export class RealtimeChatComponent implements OnInit, OnDestroy, AfterViewChecke
313
  }
314
 
315
  private startVisualization(): void {
316
- if (!this.audioVisualizer) {
317
- console.warn('Audio visualizer element not found');
318
- return;
319
- }
320
-
321
- if (this.animationId) {
322
- console.log('🎨 Visualization already running');
323
  return;
324
  }
325
 
@@ -334,33 +324,62 @@ export class RealtimeChatComponent implements OnInit, OnDestroy, AfterViewChecke
334
  canvas.width = canvas.offsetWidth;
335
  canvas.height = canvas.offsetHeight;
336
 
337
- console.log('🎨 Starting continuous visualization');
 
 
 
 
338
 
339
- // Subscribe to audio service volume level
340
- this.subscriptions.add(
341
- this.audioService.volumeLevel$.subscribe(volume => {
342
- // Konuşma aktif olduğu sürece her zaman visualize et
343
- if (this.isVisualizationActive) {
344
- this.drawVolumeVisualization(ctx, canvas, volume);
345
- }
346
- })
347
- );
348
 
349
  // Animation loop
350
  const animate = () => {
351
- // Visualization aktif olduğu sürece devam et
352
- if (!this.isVisualizationActive) {
353
- console.log('🎨 Stopping animation - visualization inactive');
354
  this.clearVisualization();
355
  return;
356
  }
357
 
358
- // Clear canvas with fade effect
359
- ctx.fillStyle = 'rgba(26, 26, 26, 0.1)';
360
  ctx.fillRect(0, 0, canvas.width, canvas.height);
361
 
362
- // Always draw center line
363
- ctx.strokeStyle = 'rgba(76, 175, 80, 0.3)';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
364
  ctx.lineWidth = 1;
365
  ctx.beginPath();
366
  ctx.moveTo(0, canvas.height / 2);
@@ -372,7 +391,7 @@ export class RealtimeChatComponent implements OnInit, OnDestroy, AfterViewChecke
372
 
373
  animate();
374
  }
375
-
376
  private drawVolumeVisualization(ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, volume: number): void {
377
  const barCount = 64;
378
  const barWidth = canvas.width / barCount;
@@ -406,6 +425,11 @@ export class RealtimeChatComponent implements OnInit, OnDestroy, AfterViewChecke
406
  this.animationId = null;
407
  }
408
 
 
 
 
 
 
409
  this.clearVisualization();
410
  }
411
 
@@ -420,6 +444,7 @@ export class RealtimeChatComponent implements OnInit, OnDestroy, AfterViewChecke
420
  }
421
  }
422
 
 
423
  private cleanupAudio(): void {
424
  if (this.currentAudio) {
425
  this.currentAudio.pause();
 
40
  isRecording = false;
41
  isPlayingAudio = false;
42
  currentState: ConversationState = 'idle';
 
43
  messages: ConversationMessage[] = [];
44
  error = '';
45
  loading = false;
 
 
46
 
47
  conversationStates: ConversationState[] = [
48
  'idle', 'listening', 'processing_stt', 'processing_llm', 'processing_tts', 'playing_audio'
 
53
  private shouldScrollToBottom = false;
54
  private animationId: number | null = null;
55
  private currentAudio: HTMLAudioElement | null = null;
56
+ private volumeUpdateSubscription?: Subscription;
57
 
58
  constructor(
59
  private conversationManager: ConversationManagerService,
 
103
  this.conversationManager.currentState$.pipe(
104
  takeUntil(this.destroyed$)
105
  ).subscribe(state => {
106
+ console.log('📊 Conversation state:', state);
107
  this.currentState = state;
108
 
109
+ // Update recording state based on conversation state
110
+ this.isRecording = (state === 'listening');
111
+
112
+ // Start/stop visualization based on state
113
+ if (state === 'listening' && this.isConversationActive) {
114
+ this.startVisualization();
115
+ } else {
116
+ this.stopVisualization();
117
+ }
 
 
118
  });
119
 
120
  // Subscribe to errors
 
133
  this.shouldScrollToBottom = true;
134
  }
135
  }
136
+
137
  ngAfterViewChecked(): void {
138
  if (this.shouldScrollToBottom) {
139
  this.scrollToBottom();
 
309
  }
310
 
311
  private startVisualization(): void {
312
+ if (!this.audioVisualizer || this.animationId) {
 
 
 
 
 
 
313
  return;
314
  }
315
 
 
324
  canvas.width = canvas.offsetWidth;
325
  canvas.height = canvas.offsetHeight;
326
 
327
+ // Create gradient for bars
328
+ const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
329
+ gradient.addColorStop(0, '#4caf50');
330
+ gradient.addColorStop(0.5, '#66bb6a');
331
+ gradient.addColorStop(1, '#4caf50');
332
 
333
+ let lastVolume = 0;
334
+ let targetVolume = 0;
335
+ const smoothingFactor = 0.8;
336
+
337
+ // Subscribe to volume updates
338
+ this.volumeUpdateSubscription = this.audioService.volumeLevel$.subscribe(volume => {
339
+ targetVolume = volume;
340
+ });
 
341
 
342
  // Animation loop
343
  const animate = () => {
344
+ if (!this.isRecording || !this.isConversationActive) {
 
 
345
  this.clearVisualization();
346
  return;
347
  }
348
 
349
+ // Clear canvas
350
+ ctx.fillStyle = '#1a1a1a';
351
  ctx.fillRect(0, 0, canvas.width, canvas.height);
352
 
353
+ // Smooth volume transition
354
+ lastVolume = lastVolume * smoothingFactor + targetVolume * (1 - smoothingFactor);
355
+
356
+ // Draw frequency bars
357
+ const barCount = 32;
358
+ const barWidth = canvas.width / barCount;
359
+ const barSpacing = 2;
360
+
361
+ for (let i = 0; i < barCount; i++) {
362
+ // Create natural wave effect based on volume
363
+ const frequencyFactor = Math.sin((i / barCount) * Math.PI);
364
+ const timeFactor = Math.sin(Date.now() * 0.001 + i * 0.2) * 0.2 + 0.8;
365
+ const randomFactor = 0.8 + Math.random() * 0.2;
366
+
367
+ const barHeight = lastVolume * canvas.height * 0.7 * frequencyFactor * timeFactor * randomFactor;
368
+
369
+ const x = i * barWidth;
370
+ const y = (canvas.height - barHeight) / 2;
371
+
372
+ // Draw bar
373
+ ctx.fillStyle = gradient;
374
+ ctx.fillRect(x + barSpacing / 2, y, barWidth - barSpacing, barHeight);
375
+
376
+ // Draw reflection
377
+ ctx.fillStyle = 'rgba(76, 175, 80, 0.2)';
378
+ ctx.fillRect(x + barSpacing / 2, canvas.height - y, barWidth - barSpacing, -barHeight * 0.3);
379
+ }
380
+
381
+ // Draw center line
382
+ ctx.strokeStyle = 'rgba(76, 175, 80, 0.5)';
383
  ctx.lineWidth = 1;
384
  ctx.beginPath();
385
  ctx.moveTo(0, canvas.height / 2);
 
391
 
392
  animate();
393
  }
394
+
395
  private drawVolumeVisualization(ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, volume: number): void {
396
  const barCount = 64;
397
  const barWidth = canvas.width / barCount;
 
425
  this.animationId = null;
426
  }
427
 
428
+ if (this.volumeUpdateSubscription) {
429
+ this.volumeUpdateSubscription.unsubscribe();
430
+ this.volumeUpdateSubscription = undefined;
431
+ }
432
+
433
  this.clearVisualization();
434
  }
435
 
 
444
  }
445
  }
446
 
447
+
448
  private cleanupAudio(): void {
449
  if (this.currentAudio) {
450
  this.currentAudio.pause();