@hivegpt/hiveai-angular 0.0.596 → 0.0.598

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.
@@ -2,9 +2,9 @@ import { ComponentPortal } from '@angular/cdk/portal';
2
2
  import * as i1 from '@angular/common/http';
3
3
  import { HttpHeaders } from '@angular/common/http';
4
4
  import * as i0 from '@angular/core';
5
- import { InjectionToken, Injectable, PLATFORM_ID, Inject, Optional, EventEmitter, Component, Output, Input, ViewChild, ElementRef, Injector, ChangeDetectionStrategy, ViewChildren, NgModule, Pipe } from '@angular/core';
5
+ import { InjectionToken, Injectable, PLATFORM_ID, Inject, Optional, EventEmitter, Component, ChangeDetectionStrategy, Output, Input, ViewChild, ElementRef, Injector, ViewChildren, NgModule, Pipe } from '@angular/core';
6
6
  import { BehaviorSubject, of, throwError, Subject, Subscription } from 'rxjs';
7
- import { switchMap, catchError, filter, take, map, tap } from 'rxjs/operators';
7
+ import { switchMap, catchError, filter, take, distinctUntilChanged, throttleTime, map, tap } from 'rxjs/operators';
8
8
  import * as i1$1 from '@angular/forms';
9
9
  import { Validators, FormsModule, ReactiveFormsModule } from '@angular/forms';
10
10
  import * as SpeechSDK from 'microsoft-cognitiveservices-speech-sdk';
@@ -84,7 +84,8 @@ const VOICE_MODAL_CLOSE_CALLBACK = new InjectionToken('VOICE_MODAL_CLOSE_CALLBAC
84
84
  * VoiceAgentService may combine this with WebSocket server events for call state.
85
85
  */
86
86
  class AudioAnalyzerService {
87
- constructor() {
87
+ constructor(ngZone) {
88
+ this.ngZone = ngZone;
88
89
  this.audioContext = null;
89
90
  this.analyserNode = null;
90
91
  this.dataArray = null;
@@ -109,6 +110,9 @@ class AudioAnalyzerService {
109
110
  }
110
111
  try {
111
112
  this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
113
+ if (this.audioContext.state === 'suspended') {
114
+ this.audioContext.resume().catch(console.warn);
115
+ }
112
116
  const source = this.audioContext.createMediaStreamSource(stream);
113
117
  this.analyserNode = this.audioContext.createAnalyser();
114
118
  this.analyserNode.fftSize = 2048;
@@ -119,7 +123,7 @@ class AudioAnalyzerService {
119
123
  this.isRunning = true;
120
124
  this.noiseFloorSamples = [];
121
125
  this.noiseFloor = 0;
122
- this.analyze();
126
+ this.ngZone.runOutsideAngular(() => this.analyze());
123
127
  }
124
128
  catch (error) {
125
129
  console.error('Error starting audio analyzer:', error);
@@ -167,10 +171,13 @@ class AudioAnalyzerService {
167
171
  // Detect if user is speaking
168
172
  const threshold = this.noiseFloor * this.SPEAKING_THRESHOLD_MULTIPLIER;
169
173
  const isSpeaking = rms > threshold;
170
- this.isUserSpeakingSubject.next(isSpeaking);
171
174
  // Generate waveform bars
172
175
  const bars = this.generateWaveformBars(this.dataArray);
173
- this.audioLevelsSubject.next(bars);
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
+ });
174
181
  this.animationFrameId = requestAnimationFrame(() => this.analyze());
175
182
  }
176
183
  calculateRMS(data) {
@@ -194,14 +201,14 @@ class AudioAnalyzerService {
194
201
  return bars;
195
202
  }
196
203
  }
197
- AudioAnalyzerService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AudioAnalyzerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
204
+ AudioAnalyzerService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AudioAnalyzerService, deps: [{ token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable });
198
205
  AudioAnalyzerService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AudioAnalyzerService, providedIn: 'root' });
199
206
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: AudioAnalyzerService, decorators: [{
200
207
  type: Injectable,
201
208
  args: [{
202
209
  providedIn: 'root'
203
210
  }]
204
- }] });
211
+ }], ctorParameters: function () { return [{ type: i0.NgZone }]; } });
205
212
 
206
213
  /**
207
214
  * Default matches Pro-Events `AppConstants.authTokenKey` — same localStorage shape
@@ -414,11 +421,11 @@ class VoiceAgentService {
414
421
  this.statusText$ = this.statusTextSubject.asObservable();
415
422
  this.duration$ = this.durationSubject.asObservable();
416
423
  this.isMicMuted$ = this.isMicMutedSubject.asObservable();
417
- this.isUserSpeaking$ = this.isUserSpeakingSubject.asObservable();
424
+ this.isUserSpeaking$ = this.isUserSpeakingSubject.asObservable().pipe(distinctUntilChanged());
418
425
  this.audioLevels$ = this.audioLevelsSubject.asObservable();
419
426
  this.userTranscript$ = this.userTranscriptSubject.asObservable();
420
427
  this.botTranscript$ = this.botTranscriptSubject.asObservable();
421
- this.subscriptions.add(this.audioAnalyzer.audioLevels$.subscribe((levels) => this.audioLevelsSubject.next(levels)));
428
+ this.subscriptions.add(this.audioAnalyzer.audioLevels$.pipe(throttleTime(50)).subscribe((levels) => this.audioLevelsSubject.next(levels)));
422
429
  }
423
430
  ngOnDestroy() {
424
431
  this.destroy$.next();
@@ -593,7 +600,6 @@ class VoiceAgentService {
593
600
  const shouldBeMuted = this.isMicMutedSubject.value;
594
601
  (_a = this.pcClient) === null || _a === void 0 ? void 0 : _a.enableMic(!shouldBeMuted);
595
602
  this.isMicMutedSubject.next(shouldBeMuted);
596
- this.startLocalMicAnalyzer();
597
603
  }
598
604
  onPipecatDisconnected() {
599
605
  this.stopDurationTimer();
@@ -637,10 +643,13 @@ class VoiceAgentService {
637
643
  if (!this.botAudioElement) {
638
644
  this.botAudioElement = new Audio();
639
645
  this.botAudioElement.autoplay = true;
646
+ this.botAudioElement.playsInline = true;
647
+ this.botAudioElement.setAttribute('playsinline', '');
640
648
  }
641
649
  const existing = (_a = this.botAudioElement.srcObject) === null || _a === void 0 ? void 0 : _a.getAudioTracks()[0];
642
650
  if ((existing === null || existing === void 0 ? void 0 : existing.id) === track.id)
643
651
  return;
652
+ this.botAudioElement.pause();
644
653
  this.botAudioElement.srcObject = new MediaStream([track]);
645
654
  this.botAudioElement.play().catch((err) => console.warn('[HiveGpt Voice] Bot audio play blocked', err));
646
655
  }
@@ -702,11 +711,13 @@ class VoiceAgentService {
702
711
  const elapsed = Math.floor((Date.now() - this.callStartTime) / 1000);
703
712
  const m = Math.floor(elapsed / 60);
704
713
  const s = elapsed % 60;
705
- this.durationSubject.next(`${m}:${String(s).padStart(2, '0')}`);
714
+ this.ngZone.run(() => this.durationSubject.next(`${m}:${String(s).padStart(2, '0')}`));
706
715
  }
707
716
  };
708
717
  tick();
709
- this.durationInterval = setInterval(tick, 1000);
718
+ this.ngZone.runOutsideAngular(() => {
719
+ this.durationInterval = setInterval(tick, 1000);
720
+ });
710
721
  }
711
722
  stopDurationTimer() {
712
723
  if (this.durationInterval) {
@@ -730,10 +741,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
730
741
  } });
731
742
 
732
743
  class VoiceAgentModalComponent {
733
- constructor(voiceAgentService, audioAnalyzer, injector) {
744
+ constructor(voiceAgentService, audioAnalyzer, injector, cdr) {
734
745
  this.voiceAgentService = voiceAgentService;
735
746
  this.audioAnalyzer = audioAnalyzer;
736
747
  this.injector = injector;
748
+ this.cdr = cdr;
737
749
  this.close = new EventEmitter();
738
750
  this.apiKey = '';
739
751
  this.eventToken = '';
@@ -784,21 +796,27 @@ class VoiceAgentModalComponent {
784
796
  }
785
797
  this.subscriptions.push(this.voiceAgentService.callState$.subscribe(state => {
786
798
  this.callState = state;
799
+ this.cdr.markForCheck();
787
800
  }));
788
801
  this.subscriptions.push(this.voiceAgentService.statusText$.subscribe(text => {
789
802
  this.statusText = text;
803
+ this.cdr.markForCheck();
790
804
  }));
791
805
  this.subscriptions.push(this.voiceAgentService.duration$.subscribe(duration => {
792
806
  this.duration = duration;
807
+ this.cdr.markForCheck();
793
808
  }));
794
809
  this.subscriptions.push(this.voiceAgentService.isMicMuted$.subscribe(muted => {
795
810
  this.isMicMuted = muted;
811
+ this.cdr.markForCheck();
796
812
  }));
797
813
  this.subscriptions.push(this.voiceAgentService.isUserSpeaking$.subscribe(speaking => {
798
814
  this.isUserSpeaking = speaking;
815
+ this.cdr.markForCheck();
799
816
  }));
800
817
  this.subscriptions.push(this.voiceAgentService.audioLevels$.subscribe(levels => {
801
818
  this.audioLevels = levels;
819
+ this.cdr.markForCheck();
802
820
  }));
803
821
  // Always start from a fresh voice session to avoid stale/disconnected state flashes.
804
822
  void this.openFreshCallSession();
@@ -887,12 +905,12 @@ class VoiceAgentModalComponent {
887
905
  });
888
906
  }
889
907
  }
890
- VoiceAgentModalComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: VoiceAgentModalComponent, deps: [{ token: VoiceAgentService }, { token: AudioAnalyzerService }, { token: i0.Injector }], target: i0.ɵɵFactoryTarget.Component });
891
- 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"] }] });
908
+ 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 });
892
910
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: VoiceAgentModalComponent, decorators: [{
893
911
  type: Component,
894
- args: [{ selector: 'hivegpt-voice-agent-modal', 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"] }]
895
- }], ctorParameters: function () { return [{ type: VoiceAgentService }, { type: AudioAnalyzerService }, { type: i0.Injector }]; }, propDecorators: { close: [{
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"] }]
913
+ }], ctorParameters: function () { return [{ type: VoiceAgentService }, { type: AudioAnalyzerService }, { type: i0.Injector }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { close: [{
896
914
  type: Output
897
915
  }], apiUrl: [{
898
916
  type: Input
@@ -2248,8 +2266,8 @@ class ChatDrawerComponent {
2248
2266
  }
2249
2267
  getStatusTextFromSessionRoles(sessionRoles) {
2250
2268
  if (!Array.isArray(sessionRoles) || sessionRoles.length === 0) {
2251
- return (this.getTranslation('You can join this meeting') ||
2252
- 'You can join this meeting');
2269
+ return (this.getTranslation('') ||
2270
+ '');
2253
2271
  }
2254
2272
  const first = sessionRoles[0];
2255
2273
  if (typeof first === 'string') {
@@ -2260,8 +2278,8 @@ class ChatDrawerComponent {
2260
2278
  if (first && typeof first === 'object' && first.description) {
2261
2279
  return String(first.description).trim();
2262
2280
  }
2263
- return (this.getTranslation('You can join this meeting') ||
2264
- 'You can join this meeting');
2281
+ return (this.getTranslation('') ||
2282
+ '');
2265
2283
  }
2266
2284
  mapSessionsToCardData(sessions) {
2267
2285
  if (!Array.isArray(sessions))