@hivegpt/hiveai-angular 0.0.598 → 0.0.599

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -87,6 +87,7 @@ class AudioAnalyzerService {
87
87
  this.ngZone = ngZone;
88
88
  this.audioContext = null;
89
89
  this.analyserNode = null;
90
+ this.sourceNode = null;
90
91
  this.dataArray = null;
91
92
  this.animationFrameId = null;
92
93
  this.isRunning = false;
@@ -100,6 +101,7 @@ class AudioAnalyzerService {
100
101
  this.WAVEFORM_BAR_COUNT = 60;
101
102
  // Amplify raw amplitude so normal speech (±10–20 units) maps to visible levels
102
103
  this.SENSITIVITY_MULTIPLIER = 5;
104
+ this.barsBuffer = new Array(this.WAVEFORM_BAR_COUNT).fill(0);
103
105
  this.audioLevels$ = this.audioLevelsSubject.asObservable();
104
106
  this.isUserSpeaking$ = this.isUserSpeakingSubject.asObservable();
105
107
  }
@@ -112,13 +114,13 @@ class AudioAnalyzerService {
112
114
  if (this.audioContext.state === 'suspended') {
113
115
  this.audioContext.resume().catch(console.warn);
114
116
  }
115
- const source = this.audioContext.createMediaStreamSource(stream);
117
+ this.sourceNode = this.audioContext.createMediaStreamSource(stream);
116
118
  this.analyserNode = this.audioContext.createAnalyser();
117
119
  this.analyserNode.fftSize = 2048;
118
120
  this.analyserNode.smoothingTimeConstant = 0.4;
119
121
  const bufferLength = this.analyserNode.frequencyBinCount;
120
122
  this.dataArray = new Uint8Array(bufferLength);
121
- source.connect(this.analyserNode);
123
+ this.sourceNode.connect(this.analyserNode);
122
124
  this.isRunning = true;
123
125
  this.noiseFloorSamples = [];
124
126
  this.noiseFloor = 0;
@@ -135,6 +137,10 @@ class AudioAnalyzerService {
135
137
  cancelAnimationFrame(this.animationFrameId);
136
138
  this.animationFrameId = null;
137
139
  }
140
+ if (this.sourceNode) {
141
+ this.sourceNode.disconnect();
142
+ this.sourceNode = null;
143
+ }
138
144
  if (this.analyserNode) {
139
145
  this.analyserNode.disconnect();
140
146
  this.analyserNode = null;
@@ -172,11 +178,12 @@ class AudioAnalyzerService {
172
178
  const isSpeaking = rms > threshold;
173
179
  // Generate waveform bars
174
180
  const bars = this.generateWaveformBars(this.dataArray);
175
- // Emit inside Angular's zone so change detection picks up UI updates
176
- this.ngZone.run(() => {
177
- this.isUserSpeakingSubject.next(isSpeaking);
178
- this.audioLevelsSubject.next(bars);
179
- });
181
+ // Only trigger change detection when speaking state actually changes
182
+ if (isSpeaking !== this.isUserSpeakingSubject.value) {
183
+ this.ngZone.run(() => this.isUserSpeakingSubject.next(isSpeaking));
184
+ }
185
+ // Emit audio levels outside Angular's zone — no CD triggered here
186
+ this.audioLevelsSubject.next(bars);
180
187
  this.animationFrameId = requestAnimationFrame(() => this.analyze());
181
188
  }
182
189
  calculateRMS(data) {
@@ -188,16 +195,13 @@ class AudioAnalyzerService {
188
195
  return Math.sqrt(sum / data.length);
189
196
  }
190
197
  generateWaveformBars(data) {
191
- const bars = [];
192
198
  const step = Math.floor(data.length / this.WAVEFORM_BAR_COUNT);
193
199
  for (let i = 0; i < this.WAVEFORM_BAR_COUNT; i++) {
194
- const index = i * step;
195
- const value = data[index];
196
- // Normalize and amplify so quiet speech produces visible movement
200
+ const value = data[i * step];
197
201
  const normalized = Math.abs((value - 128) / 128) * 100 * this.SENSITIVITY_MULTIPLIER;
198
- bars.push(Math.min(100, Math.max(0, normalized)));
202
+ this.barsBuffer[i] = Math.min(100, Math.max(0, normalized));
199
203
  }
200
- return bars;
204
+ return this.barsBuffer;
201
205
  }
202
206
  }
203
207
  AudioAnalyzerService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AudioAnalyzerService, deps: [{ token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable });
@@ -413,7 +417,7 @@ class VoiceAgentService {
413
417
  this.botAudioElement = null;
414
418
  this.lifecycleToken = 0;
415
419
  this.subscriptions = new Subscription();
416
- this.destroy$ = new Subject();
420
+ this.pcEventCleanup = [];
417
421
  this.callState$ = this.callStateSubject.asObservable();
418
422
  this.statusText$ = this.statusTextSubject.asObservable();
419
423
  this.duration$ = this.durationSubject.asObservable();
@@ -422,12 +426,7 @@ class VoiceAgentService {
422
426
  this.audioLevels$ = this.audioLevelsSubject.asObservable();
423
427
  this.userTranscript$ = this.userTranscriptSubject.asObservable();
424
428
  this.botTranscript$ = this.botTranscriptSubject.asObservable();
425
- this.subscriptions.add(this.audioAnalyzer.audioLevels$.pipe(throttleTime(50)).subscribe((levels) => this.audioLevelsSubject.next(levels)));
426
- }
427
- ngOnDestroy() {
428
- this.destroy$.next();
429
- this.subscriptions.unsubscribe();
430
- void this.disconnect();
429
+ this.subscriptions.add(this.audioAnalyzer.audioLevels$.pipe(throttleTime(50)).subscribe((levels) => this.ngZone.run(() => this.audioLevelsSubject.next(levels))));
431
430
  }
432
431
  /** Reset to idle (e.g. when modal re-opens so user can click Start Call). */
433
432
  resetToIdle() {
@@ -514,32 +513,36 @@ class VoiceAgentService {
514
513
  });
515
514
  this.pcClient = pcClient;
516
515
  // Bot audio arrives as a MediaStreamTrack — wire to a hidden <audio> element
517
- pcClient.on(RTVIEvent.TrackStarted, (track, participant) => {
516
+ const onTrackStarted = (track, participant) => {
518
517
  if (!this.isLifecycleActive(connectToken))
519
518
  return;
520
519
  if (!participant?.local && track.kind === 'audio') {
521
520
  this.ngZone.run(() => this.setupBotAudioTrack(track));
522
521
  }
523
- });
522
+ };
523
+ pcClient.on(RTVIEvent.TrackStarted, onTrackStarted);
524
524
  // Speaking state comes straight from RTVI events
525
- pcClient.on(RTVIEvent.BotStartedSpeaking, () => this.ngZone.run(() => {
525
+ const onBotStarted = () => this.ngZone.run(() => {
526
526
  if (!this.isLifecycleActive(connectToken))
527
527
  return;
528
528
  this.onBotStartedSpeaking();
529
- }));
530
- pcClient.on(RTVIEvent.BotStoppedSpeaking, () => this.ngZone.run(() => {
529
+ });
530
+ pcClient.on(RTVIEvent.BotStartedSpeaking, onBotStarted);
531
+ const onBotStopped = () => this.ngZone.run(() => {
531
532
  if (!this.isLifecycleActive(connectToken))
532
533
  return;
533
534
  this.onBotStoppedSpeaking();
534
- }));
535
- pcClient.on(RTVIEvent.UserStartedSpeaking, () => this.ngZone.run(() => {
535
+ });
536
+ pcClient.on(RTVIEvent.BotStoppedSpeaking, onBotStopped);
537
+ const onUserStarted = () => this.ngZone.run(() => {
536
538
  if (!this.isLifecycleActive(connectToken))
537
539
  return;
538
540
  this.isUserSpeakingSubject.next(true);
539
541
  this.callStateSubject.next('listening');
540
542
  this.statusTextSubject.next('Listening...');
541
- }));
542
- pcClient.on(RTVIEvent.UserStoppedSpeaking, () => this.ngZone.run(() => {
543
+ });
544
+ pcClient.on(RTVIEvent.UserStartedSpeaking, onUserStarted);
545
+ const onUserStopped = () => this.ngZone.run(() => {
543
546
  if (!this.isLifecycleActive(connectToken))
544
547
  return;
545
548
  this.isUserSpeakingSubject.next(false);
@@ -548,7 +551,16 @@ class VoiceAgentService {
548
551
  this.callStateSubject.next('connected');
549
552
  this.statusTextSubject.next('Processing...');
550
553
  }
551
- }));
554
+ });
555
+ pcClient.on(RTVIEvent.UserStoppedSpeaking, onUserStopped);
556
+ // Store cleanup fns so listeners are removed on disconnect
557
+ this.pcEventCleanup = [
558
+ () => pcClient.off(RTVIEvent.TrackStarted, onTrackStarted),
559
+ () => pcClient.off(RTVIEvent.BotStartedSpeaking, onBotStarted),
560
+ () => pcClient.off(RTVIEvent.BotStoppedSpeaking, onBotStopped),
561
+ () => pcClient.off(RTVIEvent.UserStartedSpeaking, onUserStarted),
562
+ () => pcClient.off(RTVIEvent.UserStoppedSpeaking, onUserStopped),
563
+ ];
552
564
  // Acquire mic (triggers browser permission prompt)
553
565
  await pcClient.initDevices();
554
566
  // Build headers using the browser Headers API (required by pipecat's APIRequest type)
@@ -676,6 +688,9 @@ class VoiceAgentService {
676
688
  async cleanupPipecatClient() {
677
689
  if (this.pcClient) {
678
690
  try {
691
+ // Remove all event listeners before disconnecting
692
+ this.pcEventCleanup.forEach(fn => fn());
693
+ this.pcEventCleanup = [];
679
694
  await this.pcClient.disconnect();
680
695
  }
681
696
  catch {
@@ -752,6 +767,9 @@ class VoiceAgentModalComponent {
752
767
  this.isMicMuted = false;
753
768
  this.isUserSpeaking = false;
754
769
  this.audioLevels = [];
770
+ this.cdPending = false;
771
+ this.envelopeCache = [];
772
+ this.MAX_BAR_HEIGHT = 42;
755
773
  this.subscriptions = [];
756
774
  this.isConnecting = false;
757
775
  }
@@ -782,28 +800,29 @@ class VoiceAgentModalComponent {
782
800
  }
783
801
  this.subscriptions.push(this.voiceAgentService.callState$.subscribe(state => {
784
802
  this.callState = state;
785
- this.cdr.markForCheck();
803
+ this.scheduleCD();
786
804
  }));
787
805
  this.subscriptions.push(this.voiceAgentService.statusText$.subscribe(text => {
788
806
  this.statusText = text;
789
- this.cdr.markForCheck();
807
+ this.scheduleCD();
790
808
  }));
791
809
  this.subscriptions.push(this.voiceAgentService.duration$.subscribe(duration => {
792
810
  this.duration = duration;
793
- this.cdr.markForCheck();
811
+ this.scheduleCD();
794
812
  }));
795
813
  this.subscriptions.push(this.voiceAgentService.isMicMuted$.subscribe(muted => {
796
814
  this.isMicMuted = muted;
797
- this.cdr.markForCheck();
815
+ this.scheduleCD();
798
816
  }));
799
817
  this.subscriptions.push(this.voiceAgentService.isUserSpeaking$.subscribe(speaking => {
800
818
  this.isUserSpeaking = speaking;
801
- this.cdr.markForCheck();
819
+ this.scheduleCD();
802
820
  }));
803
821
  this.subscriptions.push(this.voiceAgentService.audioLevels$.subscribe(levels => {
804
822
  this.audioLevels = levels;
805
- this.cdr.markForCheck();
823
+ this.scheduleCD();
806
824
  }));
825
+ this.initEnvelopeCache(60);
807
826
  // Always start from a fresh voice session to avoid stale/disconnected state flashes.
808
827
  void this.openFreshCallSession();
809
828
  }
@@ -811,6 +830,16 @@ class VoiceAgentModalComponent {
811
830
  this.subscriptions.forEach(sub => sub.unsubscribe());
812
831
  this.disconnect();
813
832
  }
833
+ /** Batch multiple subscription emissions into a single CD cycle per frame. */
834
+ scheduleCD() {
835
+ if (!this.cdPending) {
836
+ this.cdPending = true;
837
+ requestAnimationFrame(() => {
838
+ this.cdPending = false;
839
+ this.cdr.markForCheck();
840
+ });
841
+ }
842
+ }
814
843
  async startCall() {
815
844
  if (this.isConnecting || (this.callState !== 'idle' && this.callState !== 'ended'))
816
845
  return;
@@ -835,22 +864,27 @@ class VoiceAgentModalComponent {
835
864
  toggleMic() {
836
865
  this.voiceAgentService.toggleMic();
837
866
  }
838
- /**
839
- * Map audio level (0–100) to waveform bar height in px with a gaussian
840
- * bell-curve envelope so centre bars are tallest and edge bars appear
841
- * as tiny dots — matching the audio-waveform reference design.
842
- */
843
- getWaveformHeight(level, index) {
844
- const n = Math.min(100, Math.max(0, level ?? 0));
845
- const total = this.audioLevels.length || 60;
867
+ /** Pre-compute gaussian envelope so the hot path is a single lookup + multiply. */
868
+ initEnvelopeCache(total) {
846
869
  const center = (total - 1) / 2;
847
870
  const sigma = total / 5;
848
- const envelope = Math.exp(-Math.pow(index - center, 2) / (2 * sigma * sigma));
849
- // Minimum height scales with envelope so edges show as dots even in silence
850
- const minHeight = 2 + envelope * 3;
851
- const maxHeight = 4 + envelope * 38;
852
- return minHeight + (n / 100) * (maxHeight - minHeight);
871
+ this.envelopeCache = [];
872
+ for (let i = 0; i < total; i++) {
873
+ const envelope = Math.exp(-((i - center) ** 2) / (2 * sigma * sigma));
874
+ const minH = 2 + envelope * 3;
875
+ const maxH = 4 + envelope * 38;
876
+ this.envelopeCache.push({ min: minH, range: maxH - minH });
877
+ }
878
+ }
879
+ /** Returns a 0–1 scale factor for the waveform bar transform (GPU-composited). */
880
+ getWaveformScale(level, index) {
881
+ const cached = this.envelopeCache[index];
882
+ if (!cached)
883
+ return 0.05;
884
+ const n = Math.min(100, Math.max(0, level ?? 0));
885
+ return (cached.min + (n / 100) * cached.range) / this.MAX_BAR_HEIGHT;
853
886
  }
887
+ trackByIndex(index) { return index; }
854
888
  /** Status label for active call — driven by callState + service statusText. */
855
889
  get statusLabel() {
856
890
  switch (this.callState) {
@@ -881,10 +915,10 @@ class VoiceAgentModalComponent {
881
915
  }
882
916
  }
883
917
  VoiceAgentModalComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: VoiceAgentModalComponent, deps: [{ token: VoiceAgentService }, { token: AudioAnalyzerService }, { token: i0.Injector }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
884
- VoiceAgentModalComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: VoiceAgentModalComponent, selector: "hivegpt-voice-agent-modal", inputs: { apiUrl: "apiUrl", token: "token", botId: "botId", conversationId: "conversationId", apiKey: "apiKey", eventToken: "eventToken", eventId: "eventId", eventUrl: "eventUrl", domainAuthority: "domainAuthority", agentName: "agentName", agentRole: "agentRole", agentAvatar: "agentAvatar", usersApiUrl: "usersApiUrl" }, outputs: { close: "close" }, ngImport: i0, template: "<div class=\"voice-agent-modal-overlay\" (click)=\"endCall()\">\n <div\n class=\"voice-container voice-agent-modal\"\n (click)=\"$event.stopPropagation()\"\n >\n <!-- Header -->\n <div class=\"header\">\n <div class=\"header-left\">\n <div class=\"header-icon\">\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M12 1C8.13 1 5 4.13 5 8V14C5 17.87 8.13 21 12 21C15.87 21 19 17.87 19 14V8C19 4.13 15.87 1 12 1Z\"\n fill=\"currentColor\"\n />\n <path\n d=\"M12 23C10.34 23 9 21.66 9 20H15C15 21.66 13.66 23 12 23Z\"\n fill=\"currentColor\"\n />\n </svg>\n </div>\n <span class=\"header-title\">Voice</span>\n </div>\n <button\n class=\"close-button\"\n (click)=\"endCall()\"\n type=\"button\"\n aria-label=\"Close\"\n >\n <svg\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M18 6L6 18M6 6L18 18\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n </div>\n\n <!-- Avatar Section with glow -->\n <div class=\"avatar-section\">\n <div class=\"avatar-glow\" [class.glow-talking]=\"isBotTalking\" [class.glow-listening]=\"callState === 'listening'\"></div>\n\n <!-- Particle ring \u2014 visible while bot is talking -->\n <div *ngIf=\"isBotTalking\" class=\"particles-container\">\n <span *ngFor=\"let i of [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]\"\n class=\"particle\"\n [style.--i]=\"i\"\n [style.animationDelay]=\"(i * 0.15) + 's'\">\n </span>\n </div>\n\n <div class=\"avatar-wrapper\" [class.speaking]=\"isBotTalking\" [class.listening]=\"callState === 'listening'\">\n <img class=\"avatar-image\" [src]=\"displayAvatarUrl\" alt=\"Nia\" />\n </div>\n </div>\n\n <!-- Agent Info: Nia + Collaboration Manager AI Agent Specialist -->\n <div class=\"agent-info\">\n <div class=\"agent-name\">\n Nia\n <span class=\"ai-badge\">AI</span>\n </div>\n <p class=\"agent-role\">COP30 AI Agent </p>\n </div>\n\n <!-- Start Call (when idle only) -->\n <div *ngIf=\"callState === 'idle'\" class=\"start-call-section\">\n <p *ngIf=\"statusText === 'Connection failed'\" class=\"error-message\">\n {{ statusText }}\n </p>\n <button\n class=\"start-call-button\"\n type=\"button\"\n [disabled]=\"isConnecting\"\n (click)=\"startCall()\"\n >\n <span *ngIf=\"isConnecting\">Connecting...</span>\n <span *ngIf=\"!isConnecting && statusText === 'Connection failed'\"\n >Retry</span\n >\n <span *ngIf=\"!isConnecting && statusText !== 'Connection failed'\"\n >Start Call</span\n >\n </button>\n </div>\n\n <!-- Call Ended: status + Call Again / Back to Chat -->\n <div *ngIf=\"callState === 'ended'\" class=\"call-ended-section\">\n <p class=\"call-ended-status\">\n <span class=\"status-text\">Call Ended</span>\n <span class=\"status-timer\">{{ duration }}</span>\n </p>\n <div class=\"call-ended-controls\">\n <button\n class=\"action-btn\"\n type=\"button\"\n (click)=\"callAgain()\"\n title=\"Call Again\"\n >\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path d=\"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\" />\n <path d=\"M3 3v5h5\" />\n </svg>\n Call Again\n </button>\n <button\n class=\"action-btn\"\n type=\"button\"\n (click)=\"backToChat()\"\n title=\"Back to Chat\"\n >\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\" />\n </svg>\n Back to Chat\n </button>\n </div>\n </div>\n\n <!-- Status (when connecting or in-call: Talking... / Listening / Connected + timer) -->\n <div\n class=\"status-indicator status-inline\"\n *ngIf=\"callState !== 'idle' && callState !== 'ended'\"\n >\n <div *ngIf=\"callState === 'connecting'\" class=\"status-connecting\">\n <svg\n class=\"spinner\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <circle\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-dasharray=\"31.416\"\n stroke-dashoffset=\"31.416\"\n >\n <animate\n attributeName=\"stroke-dasharray\"\n dur=\"2s\"\n values=\"0 31.416;15.708 15.708;0 31.416;0 31.416\"\n repeatCount=\"indefinite\"\n />\n <animate\n attributeName=\"stroke-dashoffset\"\n dur=\"2s\"\n values=\"0;-15.708;-31.416;-31.416\"\n repeatCount=\"indefinite\"\n />\n </circle>\n </svg>\n <span class=\"status-text\">{{ statusText }}</span>\n </div>\n <div\n *ngIf=\"callState !== 'connecting'\"\n class=\"status-connected status-inline-row\"\n >\n <span class=\"status-text\" [class.status-talking]=\"isBotTalking\" [class.status-listening]=\"callState === 'listening'\" [class.status-processing]=\"isProcessing\">\n {{ statusLabel }}\n </span>\n\n <!-- Animated bars \u2014 visible while bot is talking -->\n <div *ngIf=\"isBotTalking\" class=\"voice-visualizer\">\n <div class=\"vbar\"></div>\n <div class=\"vbar\"></div>\n <div class=\"vbar\"></div>\n <div class=\"vbar\"></div>\n </div>\n\n <!-- Bouncing dots \u2014 visible during processing pause -->\n <div *ngIf=\"isProcessing\" class=\"processing-dots\">\n <span></span><span></span><span></span>\n </div>\n\n <span class=\"status-timer\">{{ duration }}</span>\n </div>\n </div>\n\n <!-- Waveform: always visible during an active call, active (coloured) when user speaks -->\n <div\n *ngIf=\"callState === 'connected' || callState === 'listening' || callState === 'talking'\"\n class=\"waveform-container\"\n >\n <div class=\"waveform-bars\">\n <div\n *ngFor=\"let level of audioLevels; let i = index\"\n class=\"waveform-bar\"\n [class.active]=\"isUserActive\"\n [style.height.px]=\"getWaveformHeight(level, i)\"\n ></div>\n </div>\n </div>\n\n <!-- Call Controls (when connected) -->\n <div\n class=\"controls\"\n *ngIf=\"\n callState === 'connecting' ||\n callState === 'connected' ||\n callState === 'listening' ||\n callState === 'talking'\n \"\n >\n <div\n style=\"\n display: flex;\n align-items: center;\n gap: 2px;\n flex-direction: column;\n \"\n >\n <button\n class=\"control-btn mic-btn\"\n [class.muted]=\"isMicMuted\"\n (click)=\"toggleMic()\"\n type=\"button\"\n [title]=\"isMicMuted ? 'Unmute' : 'Mute'\"\n >\n <!-- Microphone icon (unmuted) -->\n <svg\n *ngIf=\"!isMicMuted\"\n width=\"24\"\n height=\"24\"\n viewBox=\"-5 0 32 32\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"currentColor\"\n >\n <g transform=\"translate(-105, -307)\">\n <path\n d=\"M111,314 C111,311.238 113.239,309 116,309 C118.761,309 121,311.238 121,314 L121,324 C121,326.762 118.761,329 116,329 C113.239,329 111,326.762 111,324 L111,314 L111,314 Z M116,331 C119.866,331 123,327.866 123,324 L123,314 C123,310.134 119.866,307 116,307 C112.134,307 109,310.134 109,314 L109,324 C109,327.866 112.134,331 116,331 L116,331 Z M127,326 L125,326 C124.089,330.007 120.282,333 116,333 C111.718,333 107.911,330.007 107,326 L105,326 C105.883,330.799 110.063,334.51 115,334.955 L115,337 L114,337 C113.448,337 113,337.448 113,338 C113,338.553 113.448,339 114,339 L118,339 C118.552,339 119,338.553 119,338 C119,337.448 118.552,337 118,337 L117,337 L117,334.955 C121.937,334.51 126.117,330.799 127,326 L127,326 Z\"\n />\n </g>\n </svg>\n <!-- Microphone icon (muted) -->\n <svg\n *ngIf=\"isMicMuted\"\n width=\"24\"\n height=\"24\"\n viewBox=\"-5 0 32 32\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"currentColor\"\n >\n <g transform=\"translate(-105, -307)\">\n <path\n d=\"M111,314 C111,311.238 113.239,309 116,309 C118.761,309 121,311.238 121,314 L121,324 C121,326.762 118.761,329 116,329 C113.239,329 111,326.762 111,324 L111,314 L111,314 Z M116,331 C119.866,331 123,327.866 123,324 L123,314 C123,310.134 119.866,307 116,307 C112.134,307 109,310.134 109,314 L109,324 C109,327.866 112.134,331 116,331 L116,331 Z M127,326 L125,326 C124.089,330.007 120.282,333 116,333 C111.718,333 107.911,330.007 107,326 L105,326 C105.883,330.799 110.063,334.51 115,334.955 L115,337 L114,337 C113.448,337 113,337.448 113,338 C113,338.553 113.448,339 114,339 L118,339 C118.552,339 119,338.553 119,338 C119,337.448 118.552,337 118,337 L117,337 L117,334.955 C121.937,334.51 126.117,330.799 127,326 L127,326 Z\"\n />\n </g>\n <path\n d=\"M2 2 L30 30\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n <span class=\"control-label\">Mute</span>\n </div>\n\n <div\n style=\"\n display: flex;\n align-items: center;\n gap: 2px;\n flex-direction: column;\n \"\n >\n <button\n class=\"control-btn end-call-btn\"\n (click)=\"hangUp()\"\n type=\"button\"\n title=\"End Call\"\n >\n <svg\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M18 6L6 18M6 6L18 18\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n <span class=\"control-label\">End Call</span>\n </div>\n </div>\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block}.voice-agent-modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);display:flex;justify-content:flex-end;align-items:flex-end;z-index:99999;backdrop-filter:blur(4px);padding:24px;font-family:Segoe UI,Tahoma,Geneva,Verdana,sans-serif}.voice-container.voice-agent-modal{width:100%;max-width:440px;background:white;border-radius:30px;padding:30px;box-shadow:0 10px 40px #0000001a;text-align:center;position:relative;display:flex;flex-direction:column;align-items:center;min-height:600px;animation:modalEnter .3s ease-out}@keyframes modalEnter{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.header{width:100%;display:flex;align-items:center;justify-content:space-between;margin-bottom:5px}.header-left{display:flex;align-items:center;gap:8px}.header-icon{width:28px;height:28px;background:#0f172a;border-radius:50%;color:#fff;display:flex;align-items:center;justify-content:center}.header-title{font-size:18px;font-weight:500;color:#0f172a}.close-button{background:none;border:none;cursor:pointer;padding:8px;color:#0f172a;display:flex;align-items:center;justify-content:center;transition:color .2s}.close-button:hover{color:#475569}.avatar-section{position:relative;margin-bottom:24px}.avatar-wrapper{width:180px;height:180px;border-radius:50%;padding:6px;background:#0ea5a4;background:linear-gradient(135deg,#ccfbf1 0%,#0ea5a4 100%);display:flex;align-items:center;justify-content:center;position:relative}.avatar-image{width:100%;height:100%;border-radius:50%;object-fit:cover;border:4px solid white}.avatar-glow{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:240px;height:240px;background:radial-gradient(circle,rgba(14,165,164,.2) 0%,transparent 70%);z-index:-1;pointer-events:none;transition:opacity .4s ease}.avatar-glow.glow-talking{width:280px;height:280px;background:radial-gradient(circle,rgba(14,165,164,.35) 0%,transparent 65%);animation:glowPulse 1.5s ease-in-out infinite}.avatar-glow.glow-listening{background:radial-gradient(circle,rgba(99,102,241,.25) 0%,transparent 65%)}@keyframes glowPulse{0%,to{opacity:.7;transform:translate(-50%,-50%) scale(1)}50%{opacity:1;transform:translate(-50%,-50%) scale(1.08)}}.avatar-wrapper.speaking{animation:avatarPulse 1.4s ease-in-out infinite}.avatar-wrapper.listening{animation:avatarListenPulse 1.8s ease-in-out infinite}@keyframes avatarPulse{0%,to{box-shadow:0 0 #0ea5a480}50%{box-shadow:0 0 0 18px #0ea5a400}}@keyframes avatarListenPulse{0%,to{box-shadow:0 0 #6366f166}50%{box-shadow:0 0 0 14px #6366f100}}.particles-container{position:absolute;top:50%;left:50%;width:0;height:0;z-index:2;pointer-events:none}.particle{position:absolute;width:7px;height:7px;border-radius:50%;background:#0ea5a4;opacity:0;animation:particleOrbit 2.4s ease-in-out infinite;animation-delay:var(--delay, 0s);transform-origin:0 0}@keyframes particleOrbit{0%{opacity:0;transform:rotate(calc(var(--i, 0) * 22.5deg)) translateY(-108px) scale(.4)}25%{opacity:.9}75%{opacity:.9}to{opacity:0;transform:rotate(calc(var(--i, 0) * 22.5deg + 45deg)) translateY(-108px) scale(.4)}}.agent-info{margin-bottom:40px}.agent-name{font-size:24px;font-weight:700;color:#0f172a;margin-bottom:8px;display:flex;align-items:center;justify-content:center;gap:8px}.ai-badge{background:#0ea5a4;color:#fff;font-size:10px;font-weight:700;padding:2px 6px;border-radius:6px}.agent-role{font-size:16px;color:#0f172a;font-weight:500;margin:0}.start-call-section{display:flex;flex-direction:column;align-items:center;gap:16px;margin-bottom:24px}.error-message{color:#dc2626;font-size:14px;margin:0}.start-call-button{padding:14px 32px;font-size:16px;font-weight:600;color:#fff;background:#0ea5a4;border:none;border-radius:12px;cursor:pointer;transition:background .2s}.start-call-button:hover:not(:disabled){background:#0d9488}.start-call-button:disabled{opacity:.7;cursor:not-allowed!important}.status-indicator{display:flex;align-items:center;justify-content:center;gap:12px;margin-bottom:10px}.status-connecting{display:flex;align-items:center;gap:12px}.spinner{color:#0ea5a4;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.status-text{font-size:16px;color:#0f172a;font-weight:400;transition:color .25s ease}.status-text.status-talking{color:#0ea5a4;font-weight:500}.status-text.status-listening{color:#2a2a2c;font-weight:500}.status-text.status-processing{color:#94a3b8}.status-timer{font-size:16px;color:#0f172a;font-weight:500}.voice-visualizer{display:flex;align-items:center;gap:3px;height:18px}.vbar{width:3px;height:6px;background:#0ea5a4;border-radius:2px;animation:vbarBounce 1s ease-in-out infinite}.vbar:nth-child(1){animation-delay:0s}.vbar:nth-child(2){animation-delay:.15s}.vbar:nth-child(3){animation-delay:.3s}.vbar:nth-child(4){animation-delay:.45s}@keyframes vbarBounce{0%,to{height:4px;opacity:.5}50%{height:16px;opacity:1}}.processing-dots{display:flex;align-items:center;gap:4px}.processing-dots span{display:inline-block;width:5px;height:5px;border-radius:50%;background:#94a3b8;animation:dotFade 1.2s ease-in-out infinite}.processing-dots span:nth-child(1){animation-delay:0s}.processing-dots span:nth-child(2){animation-delay:.2s}.processing-dots span:nth-child(3){animation-delay:.4s}@keyframes dotFade{0%,80%,to{transform:scale(.8);opacity:.4}40%{transform:scale(1.2);opacity:1}}.status-connected{display:flex;flex-direction:column;align-items:center;gap:4px}.status-inline .status-inline-row{flex-direction:row;align-items:center;gap:8px}.call-ended-section{display:flex;flex-direction:column;align-items:center;gap:16px;margin-bottom:24px}.call-ended-status{display:flex;align-items:center;justify-content:center;gap:8px;margin:0;font-size:16px;color:#0f172a}.call-ended-status .status-text{font-weight:400}.call-ended-status .status-timer{font-weight:500}.call-ended-controls{display:flex;justify-content:center;align-items:center;gap:16px;flex-wrap:wrap}.action-btn{display:flex;align-items:center;gap:8px;padding:12px 24px;border-radius:24px;font-size:14px;font-weight:500;color:#0f172a;background:white;border:1px solid #e2e8f0;cursor:pointer;transition:background .2s ease}.action-btn:hover{background:#f8fafc}.waveform-container{width:100%;display:flex;align-items:center;justify-content:center;height:56px;margin-bottom:10px;padding:0 8px}.waveform-bars{display:flex;align-items:center;justify-content:center;gap:2px;height:56px;width:100%}.waveform-bar{flex:0 0 2px;width:2px;min-height:2px;border-radius:99px;background:#cbd5e1;transition:height .1s ease-out}.waveform-bar.active{background:linear-gradient(180deg,#0ea5a4 0%,#0d9488 100%);box-shadow:0 0 4px #0ea5a480}.controls{display:flex;align-items:center;justify-content:center;gap:24px;width:100%}.control-btn{width:60px;height:60px;border-radius:50%;border:none;cursor:pointer;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;transition:transform .2s ease}.control-btn:hover{transform:scale(1.05)}.control-btn:active{transform:scale(.95)}.control-label{font-size:12px;font-weight:500;color:#0f172a}.mic-btn{background:#e2e8f0;color:#475569}.mic-btn .control-label{color:#475569}.mic-btn.muted{background:#e2e8f0;color:#475569}.end-call-btn{background:#ef4444;color:#fff}.end-call-btn .control-label{color:#fff}.end-call-btn:hover{background:#dc2626}\n"], dependencies: [{ kind: "directive", type: i11.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i11.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
918
+ VoiceAgentModalComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: VoiceAgentModalComponent, selector: "hivegpt-voice-agent-modal", inputs: { apiUrl: "apiUrl", token: "token", botId: "botId", conversationId: "conversationId", apiKey: "apiKey", eventToken: "eventToken", eventId: "eventId", eventUrl: "eventUrl", domainAuthority: "domainAuthority", agentName: "agentName", agentRole: "agentRole", agentAvatar: "agentAvatar", usersApiUrl: "usersApiUrl" }, outputs: { close: "close" }, ngImport: i0, template: "<div class=\"voice-agent-modal-overlay\" (click)=\"endCall()\">\n <div\n class=\"voice-container voice-agent-modal\"\n (click)=\"$event.stopPropagation()\"\n >\n <!-- Header -->\n <div class=\"header\">\n <div class=\"header-left\">\n <div class=\"header-icon\">\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M12 1C8.13 1 5 4.13 5 8V14C5 17.87 8.13 21 12 21C15.87 21 19 17.87 19 14V8C19 4.13 15.87 1 12 1Z\"\n fill=\"currentColor\"\n />\n <path\n d=\"M12 23C10.34 23 9 21.66 9 20H15C15 21.66 13.66 23 12 23Z\"\n fill=\"currentColor\"\n />\n </svg>\n </div>\n <span class=\"header-title\">Voice</span>\n </div>\n <button\n class=\"close-button\"\n (click)=\"endCall()\"\n type=\"button\"\n aria-label=\"Close\"\n >\n <svg\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M18 6L6 18M6 6L18 18\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n </div>\n\n <!-- Avatar Section with glow -->\n <div class=\"avatar-section\">\n <div class=\"avatar-glow\" [class.glow-talking]=\"isBotTalking\" [class.glow-listening]=\"callState === 'listening'\"></div>\n\n <!-- Particle ring \u2014 visible while bot is talking -->\n <div *ngIf=\"isBotTalking\" class=\"particles-container\">\n <span *ngFor=\"let i of [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]\"\n class=\"particle\"\n [style.--i]=\"i\"\n [style.animationDelay]=\"(i * 0.15) + 's'\">\n </span>\n </div>\n\n <div class=\"avatar-wrapper\" [class.speaking]=\"isBotTalking\" [class.listening]=\"callState === 'listening'\">\n <img class=\"avatar-image\" [src]=\"displayAvatarUrl\" alt=\"Nia\" />\n </div>\n </div>\n\n <!-- Agent Info: Nia + Collaboration Manager AI Agent Specialist -->\n <div class=\"agent-info\">\n <div class=\"agent-name\">\n Nia\n <span class=\"ai-badge\">AI</span>\n </div>\n <p class=\"agent-role\">COP30 AI Agent </p>\n </div>\n\n <!-- Start Call (when idle only) -->\n <div *ngIf=\"callState === 'idle'\" class=\"start-call-section\">\n <p *ngIf=\"statusText === 'Connection failed'\" class=\"error-message\">\n {{ statusText }}\n </p>\n <button\n class=\"start-call-button\"\n type=\"button\"\n [disabled]=\"isConnecting\"\n (click)=\"startCall()\"\n >\n <span *ngIf=\"isConnecting\">Connecting...</span>\n <span *ngIf=\"!isConnecting && statusText === 'Connection failed'\"\n >Retry</span\n >\n <span *ngIf=\"!isConnecting && statusText !== 'Connection failed'\"\n >Start Call</span\n >\n </button>\n </div>\n\n <!-- Call Ended: status + Call Again / Back to Chat -->\n <div *ngIf=\"callState === 'ended'\" class=\"call-ended-section\">\n <p class=\"call-ended-status\">\n <span class=\"status-text\">Call Ended</span>\n <span class=\"status-timer\">{{ duration }}</span>\n </p>\n <div class=\"call-ended-controls\">\n <button\n class=\"action-btn\"\n type=\"button\"\n (click)=\"callAgain()\"\n title=\"Call Again\"\n >\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path d=\"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\" />\n <path d=\"M3 3v5h5\" />\n </svg>\n Call Again\n </button>\n <button\n class=\"action-btn\"\n type=\"button\"\n (click)=\"backToChat()\"\n title=\"Back to Chat\"\n >\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\" />\n </svg>\n Back to Chat\n </button>\n </div>\n </div>\n\n <!-- Status (when connecting or in-call: Talking... / Listening / Connected + timer) -->\n <div\n class=\"status-indicator status-inline\"\n *ngIf=\"callState !== 'idle' && callState !== 'ended'\"\n >\n <div *ngIf=\"callState === 'connecting'\" class=\"status-connecting\">\n <svg\n class=\"spinner\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <circle\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-dasharray=\"31.416\"\n stroke-dashoffset=\"31.416\"\n >\n <animate\n attributeName=\"stroke-dasharray\"\n dur=\"2s\"\n values=\"0 31.416;15.708 15.708;0 31.416;0 31.416\"\n repeatCount=\"indefinite\"\n />\n <animate\n attributeName=\"stroke-dashoffset\"\n dur=\"2s\"\n values=\"0;-15.708;-31.416;-31.416\"\n repeatCount=\"indefinite\"\n />\n </circle>\n </svg>\n <span class=\"status-text\">{{ statusText }}</span>\n </div>\n <div\n *ngIf=\"callState !== 'connecting'\"\n class=\"status-connected status-inline-row\"\n >\n <span class=\"status-text\" [class.status-talking]=\"isBotTalking\" [class.status-listening]=\"callState === 'listening'\" [class.status-processing]=\"isProcessing\">\n {{ statusLabel }}\n </span>\n\n <!-- Animated bars \u2014 visible while bot is talking -->\n <div *ngIf=\"isBotTalking\" class=\"voice-visualizer\">\n <div class=\"vbar\"></div>\n <div class=\"vbar\"></div>\n <div class=\"vbar\"></div>\n <div class=\"vbar\"></div>\n </div>\n\n <!-- Bouncing dots \u2014 visible during processing pause -->\n <div *ngIf=\"isProcessing\" class=\"processing-dots\">\n <span></span><span></span><span></span>\n </div>\n\n <span class=\"status-timer\">{{ duration }}</span>\n </div>\n </div>\n\n <!-- Waveform: always visible during an active call, active (coloured) when user speaks -->\n <div\n *ngIf=\"callState === 'connected' || callState === 'listening' || callState === 'talking'\"\n class=\"waveform-container\"\n >\n <div class=\"waveform-bars\">\n <div\n *ngFor=\"let level of audioLevels; let i = index; trackBy: trackByIndex\"\n class=\"waveform-bar\"\n [class.active]=\"isUserActive\"\n [style.transform]=\"'scaleY(' + getWaveformScale(level, i) + ')'\"\n ></div>\n </div>\n </div>\n\n <!-- Call Controls (when connected) -->\n <div\n class=\"controls\"\n *ngIf=\"\n callState === 'connecting' ||\n callState === 'connected' ||\n callState === 'listening' ||\n callState === 'talking'\n \"\n >\n <div\n style=\"\n display: flex;\n align-items: center;\n gap: 2px;\n flex-direction: column;\n \"\n >\n <button\n class=\"control-btn mic-btn\"\n [class.muted]=\"isMicMuted\"\n (click)=\"toggleMic()\"\n type=\"button\"\n [title]=\"isMicMuted ? 'Unmute' : 'Mute'\"\n >\n <!-- Microphone icon (unmuted) -->\n <svg\n *ngIf=\"!isMicMuted\"\n width=\"24\"\n height=\"24\"\n viewBox=\"-5 0 32 32\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"currentColor\"\n >\n <g transform=\"translate(-105, -307)\">\n <path\n d=\"M111,314 C111,311.238 113.239,309 116,309 C118.761,309 121,311.238 121,314 L121,324 C121,326.762 118.761,329 116,329 C113.239,329 111,326.762 111,324 L111,314 L111,314 Z M116,331 C119.866,331 123,327.866 123,324 L123,314 C123,310.134 119.866,307 116,307 C112.134,307 109,310.134 109,314 L109,324 C109,327.866 112.134,331 116,331 L116,331 Z M127,326 L125,326 C124.089,330.007 120.282,333 116,333 C111.718,333 107.911,330.007 107,326 L105,326 C105.883,330.799 110.063,334.51 115,334.955 L115,337 L114,337 C113.448,337 113,337.448 113,338 C113,338.553 113.448,339 114,339 L118,339 C118.552,339 119,338.553 119,338 C119,337.448 118.552,337 118,337 L117,337 L117,334.955 C121.937,334.51 126.117,330.799 127,326 L127,326 Z\"\n />\n </g>\n </svg>\n <!-- Microphone icon (muted) -->\n <svg\n *ngIf=\"isMicMuted\"\n width=\"24\"\n height=\"24\"\n viewBox=\"-5 0 32 32\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"currentColor\"\n >\n <g transform=\"translate(-105, -307)\">\n <path\n d=\"M111,314 C111,311.238 113.239,309 116,309 C118.761,309 121,311.238 121,314 L121,324 C121,326.762 118.761,329 116,329 C113.239,329 111,326.762 111,324 L111,314 L111,314 Z M116,331 C119.866,331 123,327.866 123,324 L123,314 C123,310.134 119.866,307 116,307 C112.134,307 109,310.134 109,314 L109,324 C109,327.866 112.134,331 116,331 L116,331 Z M127,326 L125,326 C124.089,330.007 120.282,333 116,333 C111.718,333 107.911,330.007 107,326 L105,326 C105.883,330.799 110.063,334.51 115,334.955 L115,337 L114,337 C113.448,337 113,337.448 113,338 C113,338.553 113.448,339 114,339 L118,339 C118.552,339 119,338.553 119,338 C119,337.448 118.552,337 118,337 L117,337 L117,334.955 C121.937,334.51 126.117,330.799 127,326 L127,326 Z\"\n />\n </g>\n <path\n d=\"M2 2 L30 30\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n <span class=\"control-label\">Mute</span>\n </div>\n\n <div\n style=\"\n display: flex;\n align-items: center;\n gap: 2px;\n flex-direction: column;\n \"\n >\n <button\n class=\"control-btn end-call-btn\"\n (click)=\"hangUp()\"\n type=\"button\"\n title=\"End Call\"\n >\n <svg\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M18 6L6 18M6 6L18 18\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n <span class=\"control-label\">End Call</span>\n </div>\n </div>\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block}.voice-agent-modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);display:flex;justify-content:flex-end;align-items:flex-end;z-index:99999;padding:24px;font-family:Segoe UI,Tahoma,Geneva,Verdana,sans-serif}.voice-container.voice-agent-modal{width:100%;max-width:440px;background:white;border-radius:30px;padding:30px;box-shadow:0 10px 40px #0000001a;text-align:center;position:relative;display:flex;flex-direction:column;align-items:center;min-height:600px;animation:modalEnter .3s ease-out}@keyframes modalEnter{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.header{width:100%;display:flex;align-items:center;justify-content:space-between;margin-bottom:5px}.header-left{display:flex;align-items:center;gap:8px}.header-icon{width:28px;height:28px;background:#0f172a;border-radius:50%;color:#fff;display:flex;align-items:center;justify-content:center}.header-title{font-size:18px;font-weight:500;color:#0f172a}.close-button{background:none;border:none;cursor:pointer;padding:8px;color:#0f172a;display:flex;align-items:center;justify-content:center;transition:color .2s}.close-button:hover{color:#475569}.avatar-section{position:relative;margin-bottom:24px}.avatar-wrapper{width:180px;height:180px;border-radius:50%;padding:6px;background:#0ea5a4;background:linear-gradient(135deg,#ccfbf1 0%,#0ea5a4 100%);display:flex;align-items:center;justify-content:center;position:relative}.avatar-image{width:100%;height:100%;border-radius:50%;object-fit:cover;border:4px solid white}.avatar-glow{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:240px;height:240px;background:radial-gradient(circle,rgba(14,165,164,.2) 0%,transparent 70%);z-index:-1;pointer-events:none;transition:opacity .4s ease}.avatar-glow.glow-talking{width:280px;height:280px;background:radial-gradient(circle,rgba(14,165,164,.35) 0%,transparent 65%);animation:glowPulse 1.5s ease-in-out infinite}.avatar-glow.glow-listening{background:radial-gradient(circle,rgba(99,102,241,.25) 0%,transparent 65%)}@keyframes glowPulse{0%,to{opacity:.7;transform:translate(-50%,-50%) scale(1)}50%{opacity:1;transform:translate(-50%,-50%) scale(1.08)}}.avatar-wrapper.speaking{animation:avatarPulse 1.4s ease-in-out infinite}.avatar-wrapper.listening{animation:avatarListenPulse 1.8s ease-in-out infinite}@keyframes avatarPulse{0%,to{box-shadow:0 0 #0ea5a480}50%{box-shadow:0 0 0 18px #0ea5a400}}@keyframes avatarListenPulse{0%,to{box-shadow:0 0 #6366f166}50%{box-shadow:0 0 0 14px #6366f100}}.particles-container{position:absolute;top:50%;left:50%;width:0;height:0;z-index:2;pointer-events:none}.particle{position:absolute;width:7px;height:7px;border-radius:50%;background:#0ea5a4;opacity:0;animation:particleOrbit 2.4s ease-in-out infinite;animation-delay:var(--delay, 0s);transform-origin:0 0}@keyframes particleOrbit{0%{opacity:0;transform:rotate(calc(var(--i, 0) * 22.5deg)) translateY(-108px) scale(.4)}25%{opacity:.9}75%{opacity:.9}to{opacity:0;transform:rotate(calc(var(--i, 0) * 22.5deg + 45deg)) translateY(-108px) scale(.4)}}.agent-info{margin-bottom:40px}.agent-name{font-size:24px;font-weight:700;color:#0f172a;margin-bottom:8px;display:flex;align-items:center;justify-content:center;gap:8px}.ai-badge{background:#0ea5a4;color:#fff;font-size:10px;font-weight:700;padding:2px 6px;border-radius:6px}.agent-role{font-size:16px;color:#0f172a;font-weight:500;margin:0}.start-call-section{display:flex;flex-direction:column;align-items:center;gap:16px;margin-bottom:24px}.error-message{color:#dc2626;font-size:14px;margin:0}.start-call-button{padding:14px 32px;font-size:16px;font-weight:600;color:#fff;background:#0ea5a4;border:none;border-radius:12px;cursor:pointer;transition:background .2s}.start-call-button:hover:not(:disabled){background:#0d9488}.start-call-button:disabled{opacity:.7;cursor:not-allowed!important}.status-indicator{display:flex;align-items:center;justify-content:center;gap:12px;margin-bottom:10px}.status-connecting{display:flex;align-items:center;gap:12px}.spinner{color:#0ea5a4;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.status-text{font-size:16px;color:#0f172a;font-weight:400;transition:color .25s ease}.status-text.status-talking{color:#0ea5a4;font-weight:500}.status-text.status-listening{color:#2a2a2c;font-weight:500}.status-text.status-processing{color:#94a3b8}.status-timer{font-size:16px;color:#0f172a;font-weight:500}.voice-visualizer{display:flex;align-items:center;gap:3px;height:18px}.vbar{width:3px;height:6px;background:#0ea5a4;border-radius:2px;animation:vbarBounce 1s ease-in-out infinite}.vbar:nth-child(1){animation-delay:0s}.vbar:nth-child(2){animation-delay:.15s}.vbar:nth-child(3){animation-delay:.3s}.vbar:nth-child(4){animation-delay:.45s}@keyframes vbarBounce{0%,to{height:4px;opacity:.5}50%{height:16px;opacity:1}}.processing-dots{display:flex;align-items:center;gap:4px}.processing-dots span{display:inline-block;width:5px;height:5px;border-radius:50%;background:#94a3b8;animation:dotFade 1.2s ease-in-out infinite}.processing-dots span:nth-child(1){animation-delay:0s}.processing-dots span:nth-child(2){animation-delay:.2s}.processing-dots span:nth-child(3){animation-delay:.4s}@keyframes dotFade{0%,80%,to{transform:scale(.8);opacity:.4}40%{transform:scale(1.2);opacity:1}}.status-connected{display:flex;flex-direction:column;align-items:center;gap:4px}.status-inline .status-inline-row{flex-direction:row;align-items:center;gap:8px}.call-ended-section{display:flex;flex-direction:column;align-items:center;gap:16px;margin-bottom:24px}.call-ended-status{display:flex;align-items:center;justify-content:center;gap:8px;margin:0;font-size:16px;color:#0f172a}.call-ended-status .status-text{font-weight:400}.call-ended-status .status-timer{font-weight:500}.call-ended-controls{display:flex;justify-content:center;align-items:center;gap:16px;flex-wrap:wrap}.action-btn{display:flex;align-items:center;gap:8px;padding:12px 24px;border-radius:24px;font-size:14px;font-weight:500;color:#0f172a;background:white;border:1px solid #e2e8f0;cursor:pointer;transition:background .2s ease}.action-btn:hover{background:#f8fafc}.waveform-container{width:100%;display:flex;align-items:center;justify-content:center;height:56px;margin-bottom:10px;padding:0 8px}.waveform-bars{display:flex;align-items:center;justify-content:center;gap:2px;height:56px;width:100%;contain:layout style}.waveform-bar{flex:0 0 2px;width:2px;height:42px;border-radius:99px;background:#cbd5e1;transform-origin:center}.waveform-bar.active{background:linear-gradient(180deg,#0ea5a4 0%,#0d9488 100%);box-shadow:0 0 4px #0ea5a480}.controls{display:flex;align-items:center;justify-content:center;gap:24px;width:100%}.control-btn{width:60px;height:60px;border-radius:50%;border:none;cursor:pointer;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;transition:transform .2s ease}.control-btn:hover{transform:scale(1.05)}.control-btn:active{transform:scale(.95)}.control-label{font-size:12px;font-weight:500;color:#0f172a}.mic-btn{background:#e2e8f0;color:#475569}.mic-btn .control-label{color:#475569}.mic-btn.muted{background:#e2e8f0;color:#475569}.end-call-btn{background:#ef4444;color:#fff}.end-call-btn .control-label{color:#fff}.end-call-btn:hover{background:#dc2626}\n"], dependencies: [{ kind: "directive", type: i11.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i11.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
885
919
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: VoiceAgentModalComponent, decorators: [{
886
920
  type: Component,
887
- args: [{ selector: 'hivegpt-voice-agent-modal', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"voice-agent-modal-overlay\" (click)=\"endCall()\">\n <div\n class=\"voice-container voice-agent-modal\"\n (click)=\"$event.stopPropagation()\"\n >\n <!-- Header -->\n <div class=\"header\">\n <div class=\"header-left\">\n <div class=\"header-icon\">\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M12 1C8.13 1 5 4.13 5 8V14C5 17.87 8.13 21 12 21C15.87 21 19 17.87 19 14V8C19 4.13 15.87 1 12 1Z\"\n fill=\"currentColor\"\n />\n <path\n d=\"M12 23C10.34 23 9 21.66 9 20H15C15 21.66 13.66 23 12 23Z\"\n fill=\"currentColor\"\n />\n </svg>\n </div>\n <span class=\"header-title\">Voice</span>\n </div>\n <button\n class=\"close-button\"\n (click)=\"endCall()\"\n type=\"button\"\n aria-label=\"Close\"\n >\n <svg\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M18 6L6 18M6 6L18 18\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n </div>\n\n <!-- Avatar Section with glow -->\n <div class=\"avatar-section\">\n <div class=\"avatar-glow\" [class.glow-talking]=\"isBotTalking\" [class.glow-listening]=\"callState === 'listening'\"></div>\n\n <!-- Particle ring \u2014 visible while bot is talking -->\n <div *ngIf=\"isBotTalking\" class=\"particles-container\">\n <span *ngFor=\"let i of [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]\"\n class=\"particle\"\n [style.--i]=\"i\"\n [style.animationDelay]=\"(i * 0.15) + 's'\">\n </span>\n </div>\n\n <div class=\"avatar-wrapper\" [class.speaking]=\"isBotTalking\" [class.listening]=\"callState === 'listening'\">\n <img class=\"avatar-image\" [src]=\"displayAvatarUrl\" alt=\"Nia\" />\n </div>\n </div>\n\n <!-- Agent Info: Nia + Collaboration Manager AI Agent Specialist -->\n <div class=\"agent-info\">\n <div class=\"agent-name\">\n Nia\n <span class=\"ai-badge\">AI</span>\n </div>\n <p class=\"agent-role\">COP30 AI Agent </p>\n </div>\n\n <!-- Start Call (when idle only) -->\n <div *ngIf=\"callState === 'idle'\" class=\"start-call-section\">\n <p *ngIf=\"statusText === 'Connection failed'\" class=\"error-message\">\n {{ statusText }}\n </p>\n <button\n class=\"start-call-button\"\n type=\"button\"\n [disabled]=\"isConnecting\"\n (click)=\"startCall()\"\n >\n <span *ngIf=\"isConnecting\">Connecting...</span>\n <span *ngIf=\"!isConnecting && statusText === 'Connection failed'\"\n >Retry</span\n >\n <span *ngIf=\"!isConnecting && statusText !== 'Connection failed'\"\n >Start Call</span\n >\n </button>\n </div>\n\n <!-- Call Ended: status + Call Again / Back to Chat -->\n <div *ngIf=\"callState === 'ended'\" class=\"call-ended-section\">\n <p class=\"call-ended-status\">\n <span class=\"status-text\">Call Ended</span>\n <span class=\"status-timer\">{{ duration }}</span>\n </p>\n <div class=\"call-ended-controls\">\n <button\n class=\"action-btn\"\n type=\"button\"\n (click)=\"callAgain()\"\n title=\"Call Again\"\n >\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path d=\"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\" />\n <path d=\"M3 3v5h5\" />\n </svg>\n Call Again\n </button>\n <button\n class=\"action-btn\"\n type=\"button\"\n (click)=\"backToChat()\"\n title=\"Back to Chat\"\n >\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\" />\n </svg>\n Back to Chat\n </button>\n </div>\n </div>\n\n <!-- Status (when connecting or in-call: Talking... / Listening / Connected + timer) -->\n <div\n class=\"status-indicator status-inline\"\n *ngIf=\"callState !== 'idle' && callState !== 'ended'\"\n >\n <div *ngIf=\"callState === 'connecting'\" class=\"status-connecting\">\n <svg\n class=\"spinner\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <circle\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-dasharray=\"31.416\"\n stroke-dashoffset=\"31.416\"\n >\n <animate\n attributeName=\"stroke-dasharray\"\n dur=\"2s\"\n values=\"0 31.416;15.708 15.708;0 31.416;0 31.416\"\n repeatCount=\"indefinite\"\n />\n <animate\n attributeName=\"stroke-dashoffset\"\n dur=\"2s\"\n values=\"0;-15.708;-31.416;-31.416\"\n repeatCount=\"indefinite\"\n />\n </circle>\n </svg>\n <span class=\"status-text\">{{ statusText }}</span>\n </div>\n <div\n *ngIf=\"callState !== 'connecting'\"\n class=\"status-connected status-inline-row\"\n >\n <span class=\"status-text\" [class.status-talking]=\"isBotTalking\" [class.status-listening]=\"callState === 'listening'\" [class.status-processing]=\"isProcessing\">\n {{ statusLabel }}\n </span>\n\n <!-- Animated bars \u2014 visible while bot is talking -->\n <div *ngIf=\"isBotTalking\" class=\"voice-visualizer\">\n <div class=\"vbar\"></div>\n <div class=\"vbar\"></div>\n <div class=\"vbar\"></div>\n <div class=\"vbar\"></div>\n </div>\n\n <!-- Bouncing dots \u2014 visible during processing pause -->\n <div *ngIf=\"isProcessing\" class=\"processing-dots\">\n <span></span><span></span><span></span>\n </div>\n\n <span class=\"status-timer\">{{ duration }}</span>\n </div>\n </div>\n\n <!-- Waveform: always visible during an active call, active (coloured) when user speaks -->\n <div\n *ngIf=\"callState === 'connected' || callState === 'listening' || callState === 'talking'\"\n class=\"waveform-container\"\n >\n <div class=\"waveform-bars\">\n <div\n *ngFor=\"let level of audioLevels; let i = index\"\n class=\"waveform-bar\"\n [class.active]=\"isUserActive\"\n [style.height.px]=\"getWaveformHeight(level, i)\"\n ></div>\n </div>\n </div>\n\n <!-- Call Controls (when connected) -->\n <div\n class=\"controls\"\n *ngIf=\"\n callState === 'connecting' ||\n callState === 'connected' ||\n callState === 'listening' ||\n callState === 'talking'\n \"\n >\n <div\n style=\"\n display: flex;\n align-items: center;\n gap: 2px;\n flex-direction: column;\n \"\n >\n <button\n class=\"control-btn mic-btn\"\n [class.muted]=\"isMicMuted\"\n (click)=\"toggleMic()\"\n type=\"button\"\n [title]=\"isMicMuted ? 'Unmute' : 'Mute'\"\n >\n <!-- Microphone icon (unmuted) -->\n <svg\n *ngIf=\"!isMicMuted\"\n width=\"24\"\n height=\"24\"\n viewBox=\"-5 0 32 32\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"currentColor\"\n >\n <g transform=\"translate(-105, -307)\">\n <path\n d=\"M111,314 C111,311.238 113.239,309 116,309 C118.761,309 121,311.238 121,314 L121,324 C121,326.762 118.761,329 116,329 C113.239,329 111,326.762 111,324 L111,314 L111,314 Z M116,331 C119.866,331 123,327.866 123,324 L123,314 C123,310.134 119.866,307 116,307 C112.134,307 109,310.134 109,314 L109,324 C109,327.866 112.134,331 116,331 L116,331 Z M127,326 L125,326 C124.089,330.007 120.282,333 116,333 C111.718,333 107.911,330.007 107,326 L105,326 C105.883,330.799 110.063,334.51 115,334.955 L115,337 L114,337 C113.448,337 113,337.448 113,338 C113,338.553 113.448,339 114,339 L118,339 C118.552,339 119,338.553 119,338 C119,337.448 118.552,337 118,337 L117,337 L117,334.955 C121.937,334.51 126.117,330.799 127,326 L127,326 Z\"\n />\n </g>\n </svg>\n <!-- Microphone icon (muted) -->\n <svg\n *ngIf=\"isMicMuted\"\n width=\"24\"\n height=\"24\"\n viewBox=\"-5 0 32 32\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"currentColor\"\n >\n <g transform=\"translate(-105, -307)\">\n <path\n d=\"M111,314 C111,311.238 113.239,309 116,309 C118.761,309 121,311.238 121,314 L121,324 C121,326.762 118.761,329 116,329 C113.239,329 111,326.762 111,324 L111,314 L111,314 Z M116,331 C119.866,331 123,327.866 123,324 L123,314 C123,310.134 119.866,307 116,307 C112.134,307 109,310.134 109,314 L109,324 C109,327.866 112.134,331 116,331 L116,331 Z M127,326 L125,326 C124.089,330.007 120.282,333 116,333 C111.718,333 107.911,330.007 107,326 L105,326 C105.883,330.799 110.063,334.51 115,334.955 L115,337 L114,337 C113.448,337 113,337.448 113,338 C113,338.553 113.448,339 114,339 L118,339 C118.552,339 119,338.553 119,338 C119,337.448 118.552,337 118,337 L117,337 L117,334.955 C121.937,334.51 126.117,330.799 127,326 L127,326 Z\"\n />\n </g>\n <path\n d=\"M2 2 L30 30\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n <span class=\"control-label\">Mute</span>\n </div>\n\n <div\n style=\"\n display: flex;\n align-items: center;\n gap: 2px;\n flex-direction: column;\n \"\n >\n <button\n class=\"control-btn end-call-btn\"\n (click)=\"hangUp()\"\n type=\"button\"\n title=\"End Call\"\n >\n <svg\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M18 6L6 18M6 6L18 18\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n <span class=\"control-label\">End Call</span>\n </div>\n </div>\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block}.voice-agent-modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);display:flex;justify-content:flex-end;align-items:flex-end;z-index:99999;backdrop-filter:blur(4px);padding:24px;font-family:Segoe UI,Tahoma,Geneva,Verdana,sans-serif}.voice-container.voice-agent-modal{width:100%;max-width:440px;background:white;border-radius:30px;padding:30px;box-shadow:0 10px 40px #0000001a;text-align:center;position:relative;display:flex;flex-direction:column;align-items:center;min-height:600px;animation:modalEnter .3s ease-out}@keyframes modalEnter{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.header{width:100%;display:flex;align-items:center;justify-content:space-between;margin-bottom:5px}.header-left{display:flex;align-items:center;gap:8px}.header-icon{width:28px;height:28px;background:#0f172a;border-radius:50%;color:#fff;display:flex;align-items:center;justify-content:center}.header-title{font-size:18px;font-weight:500;color:#0f172a}.close-button{background:none;border:none;cursor:pointer;padding:8px;color:#0f172a;display:flex;align-items:center;justify-content:center;transition:color .2s}.close-button:hover{color:#475569}.avatar-section{position:relative;margin-bottom:24px}.avatar-wrapper{width:180px;height:180px;border-radius:50%;padding:6px;background:#0ea5a4;background:linear-gradient(135deg,#ccfbf1 0%,#0ea5a4 100%);display:flex;align-items:center;justify-content:center;position:relative}.avatar-image{width:100%;height:100%;border-radius:50%;object-fit:cover;border:4px solid white}.avatar-glow{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:240px;height:240px;background:radial-gradient(circle,rgba(14,165,164,.2) 0%,transparent 70%);z-index:-1;pointer-events:none;transition:opacity .4s ease}.avatar-glow.glow-talking{width:280px;height:280px;background:radial-gradient(circle,rgba(14,165,164,.35) 0%,transparent 65%);animation:glowPulse 1.5s ease-in-out infinite}.avatar-glow.glow-listening{background:radial-gradient(circle,rgba(99,102,241,.25) 0%,transparent 65%)}@keyframes glowPulse{0%,to{opacity:.7;transform:translate(-50%,-50%) scale(1)}50%{opacity:1;transform:translate(-50%,-50%) scale(1.08)}}.avatar-wrapper.speaking{animation:avatarPulse 1.4s ease-in-out infinite}.avatar-wrapper.listening{animation:avatarListenPulse 1.8s ease-in-out infinite}@keyframes avatarPulse{0%,to{box-shadow:0 0 #0ea5a480}50%{box-shadow:0 0 0 18px #0ea5a400}}@keyframes avatarListenPulse{0%,to{box-shadow:0 0 #6366f166}50%{box-shadow:0 0 0 14px #6366f100}}.particles-container{position:absolute;top:50%;left:50%;width:0;height:0;z-index:2;pointer-events:none}.particle{position:absolute;width:7px;height:7px;border-radius:50%;background:#0ea5a4;opacity:0;animation:particleOrbit 2.4s ease-in-out infinite;animation-delay:var(--delay, 0s);transform-origin:0 0}@keyframes particleOrbit{0%{opacity:0;transform:rotate(calc(var(--i, 0) * 22.5deg)) translateY(-108px) scale(.4)}25%{opacity:.9}75%{opacity:.9}to{opacity:0;transform:rotate(calc(var(--i, 0) * 22.5deg + 45deg)) translateY(-108px) scale(.4)}}.agent-info{margin-bottom:40px}.agent-name{font-size:24px;font-weight:700;color:#0f172a;margin-bottom:8px;display:flex;align-items:center;justify-content:center;gap:8px}.ai-badge{background:#0ea5a4;color:#fff;font-size:10px;font-weight:700;padding:2px 6px;border-radius:6px}.agent-role{font-size:16px;color:#0f172a;font-weight:500;margin:0}.start-call-section{display:flex;flex-direction:column;align-items:center;gap:16px;margin-bottom:24px}.error-message{color:#dc2626;font-size:14px;margin:0}.start-call-button{padding:14px 32px;font-size:16px;font-weight:600;color:#fff;background:#0ea5a4;border:none;border-radius:12px;cursor:pointer;transition:background .2s}.start-call-button:hover:not(:disabled){background:#0d9488}.start-call-button:disabled{opacity:.7;cursor:not-allowed!important}.status-indicator{display:flex;align-items:center;justify-content:center;gap:12px;margin-bottom:10px}.status-connecting{display:flex;align-items:center;gap:12px}.spinner{color:#0ea5a4;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.status-text{font-size:16px;color:#0f172a;font-weight:400;transition:color .25s ease}.status-text.status-talking{color:#0ea5a4;font-weight:500}.status-text.status-listening{color:#2a2a2c;font-weight:500}.status-text.status-processing{color:#94a3b8}.status-timer{font-size:16px;color:#0f172a;font-weight:500}.voice-visualizer{display:flex;align-items:center;gap:3px;height:18px}.vbar{width:3px;height:6px;background:#0ea5a4;border-radius:2px;animation:vbarBounce 1s ease-in-out infinite}.vbar:nth-child(1){animation-delay:0s}.vbar:nth-child(2){animation-delay:.15s}.vbar:nth-child(3){animation-delay:.3s}.vbar:nth-child(4){animation-delay:.45s}@keyframes vbarBounce{0%,to{height:4px;opacity:.5}50%{height:16px;opacity:1}}.processing-dots{display:flex;align-items:center;gap:4px}.processing-dots span{display:inline-block;width:5px;height:5px;border-radius:50%;background:#94a3b8;animation:dotFade 1.2s ease-in-out infinite}.processing-dots span:nth-child(1){animation-delay:0s}.processing-dots span:nth-child(2){animation-delay:.2s}.processing-dots span:nth-child(3){animation-delay:.4s}@keyframes dotFade{0%,80%,to{transform:scale(.8);opacity:.4}40%{transform:scale(1.2);opacity:1}}.status-connected{display:flex;flex-direction:column;align-items:center;gap:4px}.status-inline .status-inline-row{flex-direction:row;align-items:center;gap:8px}.call-ended-section{display:flex;flex-direction:column;align-items:center;gap:16px;margin-bottom:24px}.call-ended-status{display:flex;align-items:center;justify-content:center;gap:8px;margin:0;font-size:16px;color:#0f172a}.call-ended-status .status-text{font-weight:400}.call-ended-status .status-timer{font-weight:500}.call-ended-controls{display:flex;justify-content:center;align-items:center;gap:16px;flex-wrap:wrap}.action-btn{display:flex;align-items:center;gap:8px;padding:12px 24px;border-radius:24px;font-size:14px;font-weight:500;color:#0f172a;background:white;border:1px solid #e2e8f0;cursor:pointer;transition:background .2s ease}.action-btn:hover{background:#f8fafc}.waveform-container{width:100%;display:flex;align-items:center;justify-content:center;height:56px;margin-bottom:10px;padding:0 8px}.waveform-bars{display:flex;align-items:center;justify-content:center;gap:2px;height:56px;width:100%}.waveform-bar{flex:0 0 2px;width:2px;min-height:2px;border-radius:99px;background:#cbd5e1;transition:height .1s ease-out}.waveform-bar.active{background:linear-gradient(180deg,#0ea5a4 0%,#0d9488 100%);box-shadow:0 0 4px #0ea5a480}.controls{display:flex;align-items:center;justify-content:center;gap:24px;width:100%}.control-btn{width:60px;height:60px;border-radius:50%;border:none;cursor:pointer;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;transition:transform .2s ease}.control-btn:hover{transform:scale(1.05)}.control-btn:active{transform:scale(.95)}.control-label{font-size:12px;font-weight:500;color:#0f172a}.mic-btn{background:#e2e8f0;color:#475569}.mic-btn .control-label{color:#475569}.mic-btn.muted{background:#e2e8f0;color:#475569}.end-call-btn{background:#ef4444;color:#fff}.end-call-btn .control-label{color:#fff}.end-call-btn:hover{background:#dc2626}\n"] }]
921
+ args: [{ selector: 'hivegpt-voice-agent-modal', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"voice-agent-modal-overlay\" (click)=\"endCall()\">\n <div\n class=\"voice-container voice-agent-modal\"\n (click)=\"$event.stopPropagation()\"\n >\n <!-- Header -->\n <div class=\"header\">\n <div class=\"header-left\">\n <div class=\"header-icon\">\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M12 1C8.13 1 5 4.13 5 8V14C5 17.87 8.13 21 12 21C15.87 21 19 17.87 19 14V8C19 4.13 15.87 1 12 1Z\"\n fill=\"currentColor\"\n />\n <path\n d=\"M12 23C10.34 23 9 21.66 9 20H15C15 21.66 13.66 23 12 23Z\"\n fill=\"currentColor\"\n />\n </svg>\n </div>\n <span class=\"header-title\">Voice</span>\n </div>\n <button\n class=\"close-button\"\n (click)=\"endCall()\"\n type=\"button\"\n aria-label=\"Close\"\n >\n <svg\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M18 6L6 18M6 6L18 18\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n </div>\n\n <!-- Avatar Section with glow -->\n <div class=\"avatar-section\">\n <div class=\"avatar-glow\" [class.glow-talking]=\"isBotTalking\" [class.glow-listening]=\"callState === 'listening'\"></div>\n\n <!-- Particle ring \u2014 visible while bot is talking -->\n <div *ngIf=\"isBotTalking\" class=\"particles-container\">\n <span *ngFor=\"let i of [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]\"\n class=\"particle\"\n [style.--i]=\"i\"\n [style.animationDelay]=\"(i * 0.15) + 's'\">\n </span>\n </div>\n\n <div class=\"avatar-wrapper\" [class.speaking]=\"isBotTalking\" [class.listening]=\"callState === 'listening'\">\n <img class=\"avatar-image\" [src]=\"displayAvatarUrl\" alt=\"Nia\" />\n </div>\n </div>\n\n <!-- Agent Info: Nia + Collaboration Manager AI Agent Specialist -->\n <div class=\"agent-info\">\n <div class=\"agent-name\">\n Nia\n <span class=\"ai-badge\">AI</span>\n </div>\n <p class=\"agent-role\">COP30 AI Agent </p>\n </div>\n\n <!-- Start Call (when idle only) -->\n <div *ngIf=\"callState === 'idle'\" class=\"start-call-section\">\n <p *ngIf=\"statusText === 'Connection failed'\" class=\"error-message\">\n {{ statusText }}\n </p>\n <button\n class=\"start-call-button\"\n type=\"button\"\n [disabled]=\"isConnecting\"\n (click)=\"startCall()\"\n >\n <span *ngIf=\"isConnecting\">Connecting...</span>\n <span *ngIf=\"!isConnecting && statusText === 'Connection failed'\"\n >Retry</span\n >\n <span *ngIf=\"!isConnecting && statusText !== 'Connection failed'\"\n >Start Call</span\n >\n </button>\n </div>\n\n <!-- Call Ended: status + Call Again / Back to Chat -->\n <div *ngIf=\"callState === 'ended'\" class=\"call-ended-section\">\n <p class=\"call-ended-status\">\n <span class=\"status-text\">Call Ended</span>\n <span class=\"status-timer\">{{ duration }}</span>\n </p>\n <div class=\"call-ended-controls\">\n <button\n class=\"action-btn\"\n type=\"button\"\n (click)=\"callAgain()\"\n title=\"Call Again\"\n >\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path d=\"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\" />\n <path d=\"M3 3v5h5\" />\n </svg>\n Call Again\n </button>\n <button\n class=\"action-btn\"\n type=\"button\"\n (click)=\"backToChat()\"\n title=\"Back to Chat\"\n >\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\" />\n </svg>\n Back to Chat\n </button>\n </div>\n </div>\n\n <!-- Status (when connecting or in-call: Talking... / Listening / Connected + timer) -->\n <div\n class=\"status-indicator status-inline\"\n *ngIf=\"callState !== 'idle' && callState !== 'ended'\"\n >\n <div *ngIf=\"callState === 'connecting'\" class=\"status-connecting\">\n <svg\n class=\"spinner\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <circle\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-dasharray=\"31.416\"\n stroke-dashoffset=\"31.416\"\n >\n <animate\n attributeName=\"stroke-dasharray\"\n dur=\"2s\"\n values=\"0 31.416;15.708 15.708;0 31.416;0 31.416\"\n repeatCount=\"indefinite\"\n />\n <animate\n attributeName=\"stroke-dashoffset\"\n dur=\"2s\"\n values=\"0;-15.708;-31.416;-31.416\"\n repeatCount=\"indefinite\"\n />\n </circle>\n </svg>\n <span class=\"status-text\">{{ statusText }}</span>\n </div>\n <div\n *ngIf=\"callState !== 'connecting'\"\n class=\"status-connected status-inline-row\"\n >\n <span class=\"status-text\" [class.status-talking]=\"isBotTalking\" [class.status-listening]=\"callState === 'listening'\" [class.status-processing]=\"isProcessing\">\n {{ statusLabel }}\n </span>\n\n <!-- Animated bars \u2014 visible while bot is talking -->\n <div *ngIf=\"isBotTalking\" class=\"voice-visualizer\">\n <div class=\"vbar\"></div>\n <div class=\"vbar\"></div>\n <div class=\"vbar\"></div>\n <div class=\"vbar\"></div>\n </div>\n\n <!-- Bouncing dots \u2014 visible during processing pause -->\n <div *ngIf=\"isProcessing\" class=\"processing-dots\">\n <span></span><span></span><span></span>\n </div>\n\n <span class=\"status-timer\">{{ duration }}</span>\n </div>\n </div>\n\n <!-- Waveform: always visible during an active call, active (coloured) when user speaks -->\n <div\n *ngIf=\"callState === 'connected' || callState === 'listening' || callState === 'talking'\"\n class=\"waveform-container\"\n >\n <div class=\"waveform-bars\">\n <div\n *ngFor=\"let level of audioLevels; let i = index; trackBy: trackByIndex\"\n class=\"waveform-bar\"\n [class.active]=\"isUserActive\"\n [style.transform]=\"'scaleY(' + getWaveformScale(level, i) + ')'\"\n ></div>\n </div>\n </div>\n\n <!-- Call Controls (when connected) -->\n <div\n class=\"controls\"\n *ngIf=\"\n callState === 'connecting' ||\n callState === 'connected' ||\n callState === 'listening' ||\n callState === 'talking'\n \"\n >\n <div\n style=\"\n display: flex;\n align-items: center;\n gap: 2px;\n flex-direction: column;\n \"\n >\n <button\n class=\"control-btn mic-btn\"\n [class.muted]=\"isMicMuted\"\n (click)=\"toggleMic()\"\n type=\"button\"\n [title]=\"isMicMuted ? 'Unmute' : 'Mute'\"\n >\n <!-- Microphone icon (unmuted) -->\n <svg\n *ngIf=\"!isMicMuted\"\n width=\"24\"\n height=\"24\"\n viewBox=\"-5 0 32 32\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"currentColor\"\n >\n <g transform=\"translate(-105, -307)\">\n <path\n d=\"M111,314 C111,311.238 113.239,309 116,309 C118.761,309 121,311.238 121,314 L121,324 C121,326.762 118.761,329 116,329 C113.239,329 111,326.762 111,324 L111,314 L111,314 Z M116,331 C119.866,331 123,327.866 123,324 L123,314 C123,310.134 119.866,307 116,307 C112.134,307 109,310.134 109,314 L109,324 C109,327.866 112.134,331 116,331 L116,331 Z M127,326 L125,326 C124.089,330.007 120.282,333 116,333 C111.718,333 107.911,330.007 107,326 L105,326 C105.883,330.799 110.063,334.51 115,334.955 L115,337 L114,337 C113.448,337 113,337.448 113,338 C113,338.553 113.448,339 114,339 L118,339 C118.552,339 119,338.553 119,338 C119,337.448 118.552,337 118,337 L117,337 L117,334.955 C121.937,334.51 126.117,330.799 127,326 L127,326 Z\"\n />\n </g>\n </svg>\n <!-- Microphone icon (muted) -->\n <svg\n *ngIf=\"isMicMuted\"\n width=\"24\"\n height=\"24\"\n viewBox=\"-5 0 32 32\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"currentColor\"\n >\n <g transform=\"translate(-105, -307)\">\n <path\n d=\"M111,314 C111,311.238 113.239,309 116,309 C118.761,309 121,311.238 121,314 L121,324 C121,326.762 118.761,329 116,329 C113.239,329 111,326.762 111,324 L111,314 L111,314 Z M116,331 C119.866,331 123,327.866 123,324 L123,314 C123,310.134 119.866,307 116,307 C112.134,307 109,310.134 109,314 L109,324 C109,327.866 112.134,331 116,331 L116,331 Z M127,326 L125,326 C124.089,330.007 120.282,333 116,333 C111.718,333 107.911,330.007 107,326 L105,326 C105.883,330.799 110.063,334.51 115,334.955 L115,337 L114,337 C113.448,337 113,337.448 113,338 C113,338.553 113.448,339 114,339 L118,339 C118.552,339 119,338.553 119,338 C119,337.448 118.552,337 118,337 L117,337 L117,334.955 C121.937,334.51 126.117,330.799 127,326 L127,326 Z\"\n />\n </g>\n <path\n d=\"M2 2 L30 30\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n <span class=\"control-label\">Mute</span>\n </div>\n\n <div\n style=\"\n display: flex;\n align-items: center;\n gap: 2px;\n flex-direction: column;\n \"\n >\n <button\n class=\"control-btn end-call-btn\"\n (click)=\"hangUp()\"\n type=\"button\"\n title=\"End Call\"\n >\n <svg\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M18 6L6 18M6 6L18 18\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n <span class=\"control-label\">End Call</span>\n </div>\n </div>\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block}.voice-agent-modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);display:flex;justify-content:flex-end;align-items:flex-end;z-index:99999;padding:24px;font-family:Segoe UI,Tahoma,Geneva,Verdana,sans-serif}.voice-container.voice-agent-modal{width:100%;max-width:440px;background:white;border-radius:30px;padding:30px;box-shadow:0 10px 40px #0000001a;text-align:center;position:relative;display:flex;flex-direction:column;align-items:center;min-height:600px;animation:modalEnter .3s ease-out}@keyframes modalEnter{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.header{width:100%;display:flex;align-items:center;justify-content:space-between;margin-bottom:5px}.header-left{display:flex;align-items:center;gap:8px}.header-icon{width:28px;height:28px;background:#0f172a;border-radius:50%;color:#fff;display:flex;align-items:center;justify-content:center}.header-title{font-size:18px;font-weight:500;color:#0f172a}.close-button{background:none;border:none;cursor:pointer;padding:8px;color:#0f172a;display:flex;align-items:center;justify-content:center;transition:color .2s}.close-button:hover{color:#475569}.avatar-section{position:relative;margin-bottom:24px}.avatar-wrapper{width:180px;height:180px;border-radius:50%;padding:6px;background:#0ea5a4;background:linear-gradient(135deg,#ccfbf1 0%,#0ea5a4 100%);display:flex;align-items:center;justify-content:center;position:relative}.avatar-image{width:100%;height:100%;border-radius:50%;object-fit:cover;border:4px solid white}.avatar-glow{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:240px;height:240px;background:radial-gradient(circle,rgba(14,165,164,.2) 0%,transparent 70%);z-index:-1;pointer-events:none;transition:opacity .4s ease}.avatar-glow.glow-talking{width:280px;height:280px;background:radial-gradient(circle,rgba(14,165,164,.35) 0%,transparent 65%);animation:glowPulse 1.5s ease-in-out infinite}.avatar-glow.glow-listening{background:radial-gradient(circle,rgba(99,102,241,.25) 0%,transparent 65%)}@keyframes glowPulse{0%,to{opacity:.7;transform:translate(-50%,-50%) scale(1)}50%{opacity:1;transform:translate(-50%,-50%) scale(1.08)}}.avatar-wrapper.speaking{animation:avatarPulse 1.4s ease-in-out infinite}.avatar-wrapper.listening{animation:avatarListenPulse 1.8s ease-in-out infinite}@keyframes avatarPulse{0%,to{box-shadow:0 0 #0ea5a480}50%{box-shadow:0 0 0 18px #0ea5a400}}@keyframes avatarListenPulse{0%,to{box-shadow:0 0 #6366f166}50%{box-shadow:0 0 0 14px #6366f100}}.particles-container{position:absolute;top:50%;left:50%;width:0;height:0;z-index:2;pointer-events:none}.particle{position:absolute;width:7px;height:7px;border-radius:50%;background:#0ea5a4;opacity:0;animation:particleOrbit 2.4s ease-in-out infinite;animation-delay:var(--delay, 0s);transform-origin:0 0}@keyframes particleOrbit{0%{opacity:0;transform:rotate(calc(var(--i, 0) * 22.5deg)) translateY(-108px) scale(.4)}25%{opacity:.9}75%{opacity:.9}to{opacity:0;transform:rotate(calc(var(--i, 0) * 22.5deg + 45deg)) translateY(-108px) scale(.4)}}.agent-info{margin-bottom:40px}.agent-name{font-size:24px;font-weight:700;color:#0f172a;margin-bottom:8px;display:flex;align-items:center;justify-content:center;gap:8px}.ai-badge{background:#0ea5a4;color:#fff;font-size:10px;font-weight:700;padding:2px 6px;border-radius:6px}.agent-role{font-size:16px;color:#0f172a;font-weight:500;margin:0}.start-call-section{display:flex;flex-direction:column;align-items:center;gap:16px;margin-bottom:24px}.error-message{color:#dc2626;font-size:14px;margin:0}.start-call-button{padding:14px 32px;font-size:16px;font-weight:600;color:#fff;background:#0ea5a4;border:none;border-radius:12px;cursor:pointer;transition:background .2s}.start-call-button:hover:not(:disabled){background:#0d9488}.start-call-button:disabled{opacity:.7;cursor:not-allowed!important}.status-indicator{display:flex;align-items:center;justify-content:center;gap:12px;margin-bottom:10px}.status-connecting{display:flex;align-items:center;gap:12px}.spinner{color:#0ea5a4;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.status-text{font-size:16px;color:#0f172a;font-weight:400;transition:color .25s ease}.status-text.status-talking{color:#0ea5a4;font-weight:500}.status-text.status-listening{color:#2a2a2c;font-weight:500}.status-text.status-processing{color:#94a3b8}.status-timer{font-size:16px;color:#0f172a;font-weight:500}.voice-visualizer{display:flex;align-items:center;gap:3px;height:18px}.vbar{width:3px;height:6px;background:#0ea5a4;border-radius:2px;animation:vbarBounce 1s ease-in-out infinite}.vbar:nth-child(1){animation-delay:0s}.vbar:nth-child(2){animation-delay:.15s}.vbar:nth-child(3){animation-delay:.3s}.vbar:nth-child(4){animation-delay:.45s}@keyframes vbarBounce{0%,to{height:4px;opacity:.5}50%{height:16px;opacity:1}}.processing-dots{display:flex;align-items:center;gap:4px}.processing-dots span{display:inline-block;width:5px;height:5px;border-radius:50%;background:#94a3b8;animation:dotFade 1.2s ease-in-out infinite}.processing-dots span:nth-child(1){animation-delay:0s}.processing-dots span:nth-child(2){animation-delay:.2s}.processing-dots span:nth-child(3){animation-delay:.4s}@keyframes dotFade{0%,80%,to{transform:scale(.8);opacity:.4}40%{transform:scale(1.2);opacity:1}}.status-connected{display:flex;flex-direction:column;align-items:center;gap:4px}.status-inline .status-inline-row{flex-direction:row;align-items:center;gap:8px}.call-ended-section{display:flex;flex-direction:column;align-items:center;gap:16px;margin-bottom:24px}.call-ended-status{display:flex;align-items:center;justify-content:center;gap:8px;margin:0;font-size:16px;color:#0f172a}.call-ended-status .status-text{font-weight:400}.call-ended-status .status-timer{font-weight:500}.call-ended-controls{display:flex;justify-content:center;align-items:center;gap:16px;flex-wrap:wrap}.action-btn{display:flex;align-items:center;gap:8px;padding:12px 24px;border-radius:24px;font-size:14px;font-weight:500;color:#0f172a;background:white;border:1px solid #e2e8f0;cursor:pointer;transition:background .2s ease}.action-btn:hover{background:#f8fafc}.waveform-container{width:100%;display:flex;align-items:center;justify-content:center;height:56px;margin-bottom:10px;padding:0 8px}.waveform-bars{display:flex;align-items:center;justify-content:center;gap:2px;height:56px;width:100%;contain:layout style}.waveform-bar{flex:0 0 2px;width:2px;height:42px;border-radius:99px;background:#cbd5e1;transform-origin:center}.waveform-bar.active{background:linear-gradient(180deg,#0ea5a4 0%,#0d9488 100%);box-shadow:0 0 4px #0ea5a480}.controls{display:flex;align-items:center;justify-content:center;gap:24px;width:100%}.control-btn{width:60px;height:60px;border-radius:50%;border:none;cursor:pointer;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;transition:transform .2s ease}.control-btn:hover{transform:scale(1.05)}.control-btn:active{transform:scale(.95)}.control-label{font-size:12px;font-weight:500;color:#0f172a}.mic-btn{background:#e2e8f0;color:#475569}.mic-btn .control-label{color:#475569}.mic-btn.muted{background:#e2e8f0;color:#475569}.end-call-btn{background:#ef4444;color:#fff}.end-call-btn .control-label{color:#fff}.end-call-btn:hover{background:#dc2626}\n"] }]
888
922
  }], ctorParameters: function () { return [{ type: VoiceAgentService }, { type: AudioAnalyzerService }, { type: i0.Injector }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { close: [{
889
923
  type: Output
890
924
  }], apiUrl: [{