@hivegpt/hiveai-angular 0.0.597 → 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.
@@ -88,6 +88,7 @@ class AudioAnalyzerService {
88
88
  this.ngZone = ngZone;
89
89
  this.audioContext = null;
90
90
  this.analyserNode = null;
91
+ this.sourceNode = null;
91
92
  this.dataArray = null;
92
93
  this.animationFrameId = null;
93
94
  this.isRunning = false;
@@ -101,6 +102,7 @@ class AudioAnalyzerService {
101
102
  this.WAVEFORM_BAR_COUNT = 60;
102
103
  // Amplify raw amplitude so normal speech (±10–20 units) maps to visible levels
103
104
  this.SENSITIVITY_MULTIPLIER = 5;
105
+ this.barsBuffer = new Array(this.WAVEFORM_BAR_COUNT).fill(0);
104
106
  this.audioLevels$ = this.audioLevelsSubject.asObservable();
105
107
  this.isUserSpeaking$ = this.isUserSpeakingSubject.asObservable();
106
108
  }
@@ -113,13 +115,13 @@ class AudioAnalyzerService {
113
115
  if (this.audioContext.state === 'suspended') {
114
116
  this.audioContext.resume().catch(console.warn);
115
117
  }
116
- const source = this.audioContext.createMediaStreamSource(stream);
118
+ this.sourceNode = this.audioContext.createMediaStreamSource(stream);
117
119
  this.analyserNode = this.audioContext.createAnalyser();
118
120
  this.analyserNode.fftSize = 2048;
119
121
  this.analyserNode.smoothingTimeConstant = 0.4;
120
122
  const bufferLength = this.analyserNode.frequencyBinCount;
121
123
  this.dataArray = new Uint8Array(bufferLength);
122
- source.connect(this.analyserNode);
124
+ this.sourceNode.connect(this.analyserNode);
123
125
  this.isRunning = true;
124
126
  this.noiseFloorSamples = [];
125
127
  this.noiseFloor = 0;
@@ -136,6 +138,10 @@ class AudioAnalyzerService {
136
138
  cancelAnimationFrame(this.animationFrameId);
137
139
  this.animationFrameId = null;
138
140
  }
141
+ if (this.sourceNode) {
142
+ this.sourceNode.disconnect();
143
+ this.sourceNode = null;
144
+ }
139
145
  if (this.analyserNode) {
140
146
  this.analyserNode.disconnect();
141
147
  this.analyserNode = null;
@@ -173,11 +179,12 @@ class AudioAnalyzerService {
173
179
  const isSpeaking = rms > threshold;
174
180
  // Generate waveform bars
175
181
  const bars = this.generateWaveformBars(this.dataArray);
176
- // Emit inside Angular's zone so change detection picks up UI updates
177
- this.ngZone.run(() => {
178
- this.isUserSpeakingSubject.next(isSpeaking);
179
- this.audioLevelsSubject.next(bars);
180
- });
182
+ // Only trigger change detection when speaking state actually changes
183
+ if (isSpeaking !== this.isUserSpeakingSubject.value) {
184
+ this.ngZone.run(() => this.isUserSpeakingSubject.next(isSpeaking));
185
+ }
186
+ // Emit audio levels outside Angular's zone — no CD triggered here
187
+ this.audioLevelsSubject.next(bars);
181
188
  this.animationFrameId = requestAnimationFrame(() => this.analyze());
182
189
  }
183
190
  calculateRMS(data) {
@@ -189,16 +196,13 @@ class AudioAnalyzerService {
189
196
  return Math.sqrt(sum / data.length);
190
197
  }
191
198
  generateWaveformBars(data) {
192
- const bars = [];
193
199
  const step = Math.floor(data.length / this.WAVEFORM_BAR_COUNT);
194
200
  for (let i = 0; i < this.WAVEFORM_BAR_COUNT; i++) {
195
- const index = i * step;
196
- const value = data[index];
197
- // Normalize and amplify so quiet speech produces visible movement
201
+ const value = data[i * step];
198
202
  const normalized = Math.abs((value - 128) / 128) * 100 * this.SENSITIVITY_MULTIPLIER;
199
- bars.push(Math.min(100, Math.max(0, normalized)));
203
+ this.barsBuffer[i] = Math.min(100, Math.max(0, normalized));
200
204
  }
201
- return bars;
205
+ return this.barsBuffer;
202
206
  }
203
207
  }
204
208
  AudioAnalyzerService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AudioAnalyzerService, deps: [{ token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable });
@@ -416,7 +420,7 @@ class VoiceAgentService {
416
420
  this.botAudioElement = null;
417
421
  this.lifecycleToken = 0;
418
422
  this.subscriptions = new Subscription();
419
- this.destroy$ = new Subject();
423
+ this.pcEventCleanup = [];
420
424
  this.callState$ = this.callStateSubject.asObservable();
421
425
  this.statusText$ = this.statusTextSubject.asObservable();
422
426
  this.duration$ = this.durationSubject.asObservable();
@@ -425,12 +429,7 @@ class VoiceAgentService {
425
429
  this.audioLevels$ = this.audioLevelsSubject.asObservable();
426
430
  this.userTranscript$ = this.userTranscriptSubject.asObservable();
427
431
  this.botTranscript$ = this.botTranscriptSubject.asObservable();
428
- this.subscriptions.add(this.audioAnalyzer.audioLevels$.pipe(throttleTime(50)).subscribe((levels) => this.audioLevelsSubject.next(levels)));
429
- }
430
- ngOnDestroy() {
431
- this.destroy$.next();
432
- this.subscriptions.unsubscribe();
433
- void this.disconnect();
432
+ this.subscriptions.add(this.audioAnalyzer.audioLevels$.pipe(throttleTime(50)).subscribe((levels) => this.ngZone.run(() => this.audioLevelsSubject.next(levels))));
434
433
  }
435
434
  /** Reset to idle (e.g. when modal re-opens so user can click Start Call). */
436
435
  resetToIdle() {
@@ -520,32 +519,36 @@ class VoiceAgentService {
520
519
  });
521
520
  this.pcClient = pcClient;
522
521
  // Bot audio arrives as a MediaStreamTrack — wire to a hidden <audio> element
523
- pcClient.on(RTVIEvent.TrackStarted, (track, participant) => {
522
+ const onTrackStarted = (track, participant) => {
524
523
  if (!this.isLifecycleActive(connectToken))
525
524
  return;
526
525
  if (!(participant === null || participant === void 0 ? void 0 : participant.local) && track.kind === 'audio') {
527
526
  this.ngZone.run(() => this.setupBotAudioTrack(track));
528
527
  }
529
- });
528
+ };
529
+ pcClient.on(RTVIEvent.TrackStarted, onTrackStarted);
530
530
  // Speaking state comes straight from RTVI events
531
- pcClient.on(RTVIEvent.BotStartedSpeaking, () => this.ngZone.run(() => {
531
+ const onBotStarted = () => this.ngZone.run(() => {
532
532
  if (!this.isLifecycleActive(connectToken))
533
533
  return;
534
534
  this.onBotStartedSpeaking();
535
- }));
536
- pcClient.on(RTVIEvent.BotStoppedSpeaking, () => this.ngZone.run(() => {
535
+ });
536
+ pcClient.on(RTVIEvent.BotStartedSpeaking, onBotStarted);
537
+ const onBotStopped = () => this.ngZone.run(() => {
537
538
  if (!this.isLifecycleActive(connectToken))
538
539
  return;
539
540
  this.onBotStoppedSpeaking();
540
- }));
541
- pcClient.on(RTVIEvent.UserStartedSpeaking, () => this.ngZone.run(() => {
541
+ });
542
+ pcClient.on(RTVIEvent.BotStoppedSpeaking, onBotStopped);
543
+ const onUserStarted = () => this.ngZone.run(() => {
542
544
  if (!this.isLifecycleActive(connectToken))
543
545
  return;
544
546
  this.isUserSpeakingSubject.next(true);
545
547
  this.callStateSubject.next('listening');
546
548
  this.statusTextSubject.next('Listening...');
547
- }));
548
- pcClient.on(RTVIEvent.UserStoppedSpeaking, () => this.ngZone.run(() => {
549
+ });
550
+ pcClient.on(RTVIEvent.UserStartedSpeaking, onUserStarted);
551
+ const onUserStopped = () => this.ngZone.run(() => {
549
552
  if (!this.isLifecycleActive(connectToken))
550
553
  return;
551
554
  this.isUserSpeakingSubject.next(false);
@@ -554,7 +557,16 @@ class VoiceAgentService {
554
557
  this.callStateSubject.next('connected');
555
558
  this.statusTextSubject.next('Processing...');
556
559
  }
557
- }));
560
+ });
561
+ pcClient.on(RTVIEvent.UserStoppedSpeaking, onUserStopped);
562
+ // Store cleanup fns so listeners are removed on disconnect
563
+ this.pcEventCleanup = [
564
+ () => pcClient.off(RTVIEvent.TrackStarted, onTrackStarted),
565
+ () => pcClient.off(RTVIEvent.BotStartedSpeaking, onBotStarted),
566
+ () => pcClient.off(RTVIEvent.BotStoppedSpeaking, onBotStopped),
567
+ () => pcClient.off(RTVIEvent.UserStartedSpeaking, onUserStarted),
568
+ () => pcClient.off(RTVIEvent.UserStoppedSpeaking, onUserStopped),
569
+ ];
558
570
  // Acquire mic (triggers browser permission prompt)
559
571
  yield pcClient.initDevices();
560
572
  // Build headers using the browser Headers API (required by pipecat's APIRequest type)
@@ -686,6 +698,9 @@ class VoiceAgentService {
686
698
  return __awaiter(this, void 0, void 0, function* () {
687
699
  if (this.pcClient) {
688
700
  try {
701
+ // Remove all event listeners before disconnecting
702
+ this.pcEventCleanup.forEach(fn => fn());
703
+ this.pcEventCleanup = [];
689
704
  yield this.pcClient.disconnect();
690
705
  }
691
706
  catch (_a) {
@@ -765,6 +780,9 @@ class VoiceAgentModalComponent {
765
780
  this.isMicMuted = false;
766
781
  this.isUserSpeaking = false;
767
782
  this.audioLevels = [];
783
+ this.cdPending = false;
784
+ this.envelopeCache = [];
785
+ this.MAX_BAR_HEIGHT = 42;
768
786
  this.subscriptions = [];
769
787
  this.isConnecting = false;
770
788
  }
@@ -796,28 +814,29 @@ class VoiceAgentModalComponent {
796
814
  }
797
815
  this.subscriptions.push(this.voiceAgentService.callState$.subscribe(state => {
798
816
  this.callState = state;
799
- this.cdr.markForCheck();
817
+ this.scheduleCD();
800
818
  }));
801
819
  this.subscriptions.push(this.voiceAgentService.statusText$.subscribe(text => {
802
820
  this.statusText = text;
803
- this.cdr.markForCheck();
821
+ this.scheduleCD();
804
822
  }));
805
823
  this.subscriptions.push(this.voiceAgentService.duration$.subscribe(duration => {
806
824
  this.duration = duration;
807
- this.cdr.markForCheck();
825
+ this.scheduleCD();
808
826
  }));
809
827
  this.subscriptions.push(this.voiceAgentService.isMicMuted$.subscribe(muted => {
810
828
  this.isMicMuted = muted;
811
- this.cdr.markForCheck();
829
+ this.scheduleCD();
812
830
  }));
813
831
  this.subscriptions.push(this.voiceAgentService.isUserSpeaking$.subscribe(speaking => {
814
832
  this.isUserSpeaking = speaking;
815
- this.cdr.markForCheck();
833
+ this.scheduleCD();
816
834
  }));
817
835
  this.subscriptions.push(this.voiceAgentService.audioLevels$.subscribe(levels => {
818
836
  this.audioLevels = levels;
819
- this.cdr.markForCheck();
837
+ this.scheduleCD();
820
838
  }));
839
+ this.initEnvelopeCache(60);
821
840
  // Always start from a fresh voice session to avoid stale/disconnected state flashes.
822
841
  void this.openFreshCallSession();
823
842
  }
@@ -825,6 +844,16 @@ class VoiceAgentModalComponent {
825
844
  this.subscriptions.forEach(sub => sub.unsubscribe());
826
845
  this.disconnect();
827
846
  }
847
+ /** Batch multiple subscription emissions into a single CD cycle per frame. */
848
+ scheduleCD() {
849
+ if (!this.cdPending) {
850
+ this.cdPending = true;
851
+ requestAnimationFrame(() => {
852
+ this.cdPending = false;
853
+ this.cdr.markForCheck();
854
+ });
855
+ }
856
+ }
828
857
  startCall() {
829
858
  return __awaiter(this, void 0, void 0, function* () {
830
859
  if (this.isConnecting || (this.callState !== 'idle' && this.callState !== 'ended'))
@@ -855,22 +884,27 @@ class VoiceAgentModalComponent {
855
884
  toggleMic() {
856
885
  this.voiceAgentService.toggleMic();
857
886
  }
858
- /**
859
- * Map audio level (0–100) to waveform bar height in px with a gaussian
860
- * bell-curve envelope so centre bars are tallest and edge bars appear
861
- * as tiny dots — matching the audio-waveform reference design.
862
- */
863
- getWaveformHeight(level, index) {
864
- const n = Math.min(100, Math.max(0, level !== null && level !== void 0 ? level : 0));
865
- const total = this.audioLevels.length || 60;
887
+ /** Pre-compute gaussian envelope so the hot path is a single lookup + multiply. */
888
+ initEnvelopeCache(total) {
866
889
  const center = (total - 1) / 2;
867
890
  const sigma = total / 5;
868
- const envelope = Math.exp(-Math.pow(index - center, 2) / (2 * sigma * sigma));
869
- // Minimum height scales with envelope so edges show as dots even in silence
870
- const minHeight = 2 + envelope * 3;
871
- const maxHeight = 4 + envelope * 38;
872
- return minHeight + (n / 100) * (maxHeight - minHeight);
891
+ this.envelopeCache = [];
892
+ for (let i = 0; i < total; i++) {
893
+ const envelope = Math.exp(-(Math.pow((i - center), 2)) / (2 * sigma * sigma));
894
+ const minH = 2 + envelope * 3;
895
+ const maxH = 4 + envelope * 38;
896
+ this.envelopeCache.push({ min: minH, range: maxH - minH });
897
+ }
898
+ }
899
+ /** Returns a 0–1 scale factor for the waveform bar transform (GPU-composited). */
900
+ getWaveformScale(level, index) {
901
+ const cached = this.envelopeCache[index];
902
+ if (!cached)
903
+ return 0.05;
904
+ const n = Math.min(100, Math.max(0, level !== null && level !== void 0 ? level : 0));
905
+ return (cached.min + (n / 100) * cached.range) / this.MAX_BAR_HEIGHT;
873
906
  }
907
+ trackByIndex(index) { return index; }
874
908
  /** Status label for active call — driven by callState + service statusText. */
875
909
  get statusLabel() {
876
910
  switch (this.callState) {
@@ -906,10 +940,10 @@ class VoiceAgentModalComponent {
906
940
  }
907
941
  }
908
942
  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 });
909
- 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 });
943
+ 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 });
910
944
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: VoiceAgentModalComponent, decorators: [{
911
945
  type: Component,
912
- 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"] }]
946
+ 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"] }]
913
947
  }], ctorParameters: function () { return [{ type: VoiceAgentService }, { type: AudioAnalyzerService }, { type: i0.Injector }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { close: [{
914
948
  type: Output
915
949
  }], apiUrl: [{
@@ -2266,8 +2300,8 @@ class ChatDrawerComponent {
2266
2300
  }
2267
2301
  getStatusTextFromSessionRoles(sessionRoles) {
2268
2302
  if (!Array.isArray(sessionRoles) || sessionRoles.length === 0) {
2269
- return (this.getTranslation('You can join this meeting') ||
2270
- 'You can join this meeting');
2303
+ return (this.getTranslation('') ||
2304
+ '');
2271
2305
  }
2272
2306
  const first = sessionRoles[0];
2273
2307
  if (typeof first === 'string') {
@@ -2278,8 +2312,8 @@ class ChatDrawerComponent {
2278
2312
  if (first && typeof first === 'object' && first.description) {
2279
2313
  return String(first.description).trim();
2280
2314
  }
2281
- return (this.getTranslation('You can join this meeting') ||
2282
- 'You can join this meeting');
2315
+ return (this.getTranslation('') ||
2316
+ '');
2283
2317
  }
2284
2318
  mapSessionsToCardData(sessions) {
2285
2319
  if (!Array.isArray(sessions))