@hivegpt/hiveai-angular 0.0.576 → 0.0.578

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.
Files changed (20) hide show
  1. package/bundles/hivegpt-hiveai-angular.umd.js +214 -356
  2. package/bundles/hivegpt-hiveai-angular.umd.js.map +1 -1
  3. package/bundles/hivegpt-hiveai-angular.umd.min.js +1 -1
  4. package/bundles/hivegpt-hiveai-angular.umd.min.js.map +1 -1
  5. package/esm2015/lib/components/voice-agent/components/voice-agent-modal/voice-agent-modal.component.js +54 -85
  6. package/esm2015/lib/components/voice-agent/services/daily-voice-client.service.js +14 -22
  7. package/esm2015/lib/components/voice-agent/services/voice-agent.service.js +60 -90
  8. package/esm2015/lib/components/voice-agent/services/websocket-voice-client.service.js +34 -81
  9. package/fesm2015/hivegpt-hiveai-angular.js +157 -272
  10. package/fesm2015/hivegpt-hiveai-angular.js.map +1 -1
  11. package/hivegpt-hiveai-angular.metadata.json +1 -1
  12. package/lib/components/voice-agent/components/voice-agent-modal/voice-agent-modal.component.d.ts +1 -7
  13. package/lib/components/voice-agent/components/voice-agent-modal/voice-agent-modal.component.d.ts.map +1 -1
  14. package/lib/components/voice-agent/services/daily-voice-client.service.d.ts +3 -5
  15. package/lib/components/voice-agent/services/daily-voice-client.service.d.ts.map +1 -1
  16. package/lib/components/voice-agent/services/voice-agent.service.d.ts +1 -3
  17. package/lib/components/voice-agent/services/voice-agent.service.d.ts.map +1 -1
  18. package/lib/components/voice-agent/services/websocket-voice-client.service.d.ts +12 -5
  19. package/lib/components/voice-agent/services/websocket-voice-client.service.d.ts.map +1 -1
  20. package/package.json +1 -1
@@ -813,7 +813,6 @@ AudioAnalyzerService.decorators = [
813
813
  * - Emit roomCreated$, userTranscript$, botTranscript$
814
814
  * - NO audio logic, NO mic logic. Audio is handled by Daily.js (WebRTC).
815
815
  */
816
- const WS_CONNECT_TIMEOUT_MS = 10000;
817
816
  class WebSocketVoiceClientService {
818
817
  constructor() {
819
818
  this.ws = null;
@@ -827,100 +826,54 @@ class WebSocketVoiceClientService {
827
826
  /** Emits bot transcript updates. */
828
827
  this.botTranscript$ = this.botTranscriptSubject.asObservable();
829
828
  }
830
- /**
831
- * Connect to signaling WebSocket. No audio over this connection.
832
- * Resolves when the socket is open; rejects if the connection fails.
833
- */
829
+ /** Connect to signaling WebSocket. No audio over this connection. */
834
830
  connect(wsUrl) {
835
831
  var _a;
836
832
  if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN) {
837
- return Promise.resolve();
833
+ return;
838
834
  }
839
835
  if (this.ws) {
840
836
  this.ws.close();
841
837
  this.ws = null;
842
838
  }
843
- return new Promise((resolve, reject) => {
844
- let settled = false;
845
- const timeout = setTimeout(() => {
839
+ try {
840
+ this.ws = new WebSocket(wsUrl);
841
+ this.ws.onmessage = (event) => {
846
842
  var _a;
847
- if (settled)
848
- return;
849
- settled = true;
850
843
  try {
851
- (_a = this.ws) === null || _a === void 0 ? void 0 : _a.close();
852
- }
853
- catch (_b) {
854
- /* ignore */
855
- }
856
- this.ws = null;
857
- reject(new Error('WebSocket connection timed out'));
858
- }, WS_CONNECT_TIMEOUT_MS);
859
- const clear = () => {
860
- clearTimeout(timeout);
861
- };
862
- try {
863
- const ws = new WebSocket(wsUrl);
864
- this.ws = ws;
865
- ws.onopen = () => {
866
- if (settled)
867
- return;
868
- settled = true;
869
- clear();
870
- resolve();
871
- };
872
- ws.onmessage = (event) => {
873
- var _a;
874
- try {
875
- const msg = JSON.parse(event.data);
876
- if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'room_created') {
877
- const roomUrl = ((_a = msg.room_url) !== null && _a !== void 0 ? _a : msg.roomUrl);
878
- if (typeof roomUrl === 'string') {
879
- this.roomCreatedSubject.next(roomUrl);
880
- }
844
+ const msg = JSON.parse(event.data);
845
+ if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'room_created') {
846
+ const roomUrl = ((_a = msg.room_url) !== null && _a !== void 0 ? _a : msg.roomUrl);
847
+ if (typeof roomUrl === 'string') {
848
+ this.roomCreatedSubject.next(roomUrl);
881
849
  }
882
- else if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'user_transcript' && typeof msg.text === 'string') {
883
- this.userTranscriptSubject.next({
884
- text: msg.text,
885
- final: msg.final === true,
886
- });
887
- }
888
- else if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'bot_transcript' && typeof msg.text === 'string') {
889
- this.botTranscriptSubject.next(msg.text);
890
- }
891
- }
892
- catch (_b) {
893
- // Ignore non-JSON or unknown messages
894
850
  }
895
- };
896
- ws.onerror = () => {
897
- if (!settled) {
898
- settled = true;
899
- clear();
900
- this.disconnect();
901
- reject(new Error('WebSocket connection failed'));
902
- return;
851
+ else if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'user_transcript' && typeof msg.text === 'string') {
852
+ this.userTranscriptSubject.next({
853
+ text: msg.text,
854
+ final: msg.final === true,
855
+ });
903
856
  }
904
- // After onopen, some environments fire onerror spuriously; closing here can
905
- // kill the socket before room_created is delivered. Let onclose clean up.
906
- console.warn('WebSocketVoiceClient: onerror after open (not forcing disconnect)');
907
- };
908
- ws.onclose = () => {
909
- this.ws = null;
910
- if (!settled) {
911
- settled = true;
912
- clear();
913
- reject(new Error('WebSocket connection failed'));
857
+ else if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'bot_transcript' && typeof msg.text === 'string') {
858
+ this.botTranscriptSubject.next(msg.text);
914
859
  }
915
- };
916
- }
917
- catch (err) {
918
- clear();
919
- console.error('WebSocketVoiceClient: connect failed', err);
860
+ }
861
+ catch (_b) {
862
+ // Ignore non-JSON or unknown messages
863
+ }
864
+ };
865
+ this.ws.onerror = () => {
866
+ this.disconnect();
867
+ };
868
+ this.ws.onclose = () => {
920
869
  this.ws = null;
921
- reject(err instanceof Error ? err : new Error(String(err)));
922
- }
923
- });
870
+ };
871
+ }
872
+ catch (err) {
873
+ console.error('WebSocketVoiceClient: connect failed', err);
874
+ this.ws = null;
875
+ throw err;
876
+ }
924
877
  }
925
878
  /** Disconnect and cleanup. */
926
879
  disconnect() {
@@ -963,8 +916,7 @@ class DailyVoiceClientService {
963
916
  this.remoteAudioElement = null;
964
917
  /** AnalyserNode-based remote audio monitor for instant bot speaking detection. */
965
918
  this.remoteAudioContext = null;
966
- /** Poll interval id (~100ms); named historically when RAF was used. */
967
- this.remoteSpeakingPollId = null;
919
+ this.remoteSpeakingRAF = null;
968
920
  this.speakingSubject = new BehaviorSubject(false);
969
921
  this.userSpeakingSubject = new BehaviorSubject(false);
970
922
  this.micMutedSubject = new BehaviorSubject(false);
@@ -982,18 +934,15 @@ class DailyVoiceClientService {
982
934
  * Connect to Daily room. Acquires mic first for waveform, then joins with audio.
983
935
  * @param roomUrl Daily room URL (from room_created)
984
936
  * @param token Optional meeting token
985
- * @param existingStream Optional pre-acquired mic (avoids a second getUserMedia / extra prompts on some browsers)
986
937
  */
987
- connect(roomUrl, token, existingStream) {
938
+ connect(roomUrl, token) {
988
939
  return __awaiter(this, void 0, void 0, function* () {
989
940
  if (this.callObject) {
990
941
  yield this.disconnect();
991
942
  }
992
943
  try {
993
- const hasLiveTrack = !!(existingStream === null || existingStream === void 0 ? void 0 : existingStream.getAudioTracks().some((t) => t.readyState === 'live'));
994
- const stream = hasLiveTrack
995
- ? existingStream
996
- : yield navigator.mediaDevices.getUserMedia({ audio: true });
944
+ // Get mic stream for both Daily and waveform (single capture)
945
+ const stream = yield navigator.mediaDevices.getUserMedia({ audio: true });
997
946
  const audioTrack = stream.getAudioTracks()[0];
998
947
  if (!audioTrack) {
999
948
  stream.getTracks().forEach((t) => t.stop());
@@ -1125,7 +1074,7 @@ class DailyVoiceClientService {
1125
1074
  }
1126
1075
  /**
1127
1076
  * Monitor remote audio track energy via AnalyserNode.
1128
- * Polls at ~10Hz; sufficient for speaking detection vs ~60fps RAF.
1077
+ * Polls at ~60fps and flips speakingSubject based on actual audio energy.
1129
1078
  */
1130
1079
  monitorRemoteAudio(track) {
1131
1080
  this.stopRemoteAudioMonitor();
@@ -1139,17 +1088,11 @@ class DailyVoiceClientService {
1139
1088
  const dataArray = new Uint8Array(analyser.frequencyBinCount);
1140
1089
  const THRESHOLD = 5;
1141
1090
  const SILENCE_MS = 1500;
1142
- const POLL_MS = 100;
1143
1091
  let lastSoundTime = 0;
1144
1092
  let isSpeaking = false;
1145
- this.remoteSpeakingPollId = setInterval(() => {
1146
- if (!this.remoteAudioContext) {
1147
- if (this.remoteSpeakingPollId) {
1148
- clearInterval(this.remoteSpeakingPollId);
1149
- this.remoteSpeakingPollId = null;
1150
- }
1093
+ const poll = () => {
1094
+ if (!this.remoteAudioContext)
1151
1095
  return;
1152
- }
1153
1096
  analyser.getByteFrequencyData(dataArray);
1154
1097
  let sum = 0;
1155
1098
  for (let i = 0; i < dataArray.length; i++) {
@@ -1173,16 +1116,18 @@ class DailyVoiceClientService {
1173
1116
  console.log(`[VoiceDebug] Bot audio silence detected (speaking=false) — ${new Date().toISOString()}`);
1174
1117
  this.ngZone.run(() => this.speakingSubject.next(false));
1175
1118
  }
1176
- }, POLL_MS);
1119
+ this.remoteSpeakingRAF = requestAnimationFrame(poll);
1120
+ };
1121
+ this.remoteSpeakingRAF = requestAnimationFrame(poll);
1177
1122
  }
1178
1123
  catch (err) {
1179
1124
  console.warn('DailyVoiceClient: failed to create remote audio monitor', err);
1180
1125
  }
1181
1126
  }
1182
1127
  stopRemoteAudioMonitor() {
1183
- if (this.remoteSpeakingPollId !== null) {
1184
- clearInterval(this.remoteSpeakingPollId);
1185
- this.remoteSpeakingPollId = null;
1128
+ if (this.remoteSpeakingRAF) {
1129
+ cancelAnimationFrame(this.remoteSpeakingRAF);
1130
+ this.remoteSpeakingRAF = null;
1186
1131
  }
1187
1132
  if (this.remoteAudioContext) {
1188
1133
  this.remoteAudioContext.close().catch(() => { });
@@ -1281,8 +1226,6 @@ class VoiceAgentService {
1281
1226
  this.callStartTime = 0;
1282
1227
  this.durationInterval = null;
1283
1228
  this.subscriptions = new Subscription();
1284
- /** Per-call only; cleared on disconnect / reset / new room so handlers do not stack. */
1285
- this.callSubscriptions = new Subscription();
1286
1229
  this.destroy$ = new Subject();
1287
1230
  this.callState$ = this.callStateSubject.asObservable();
1288
1231
  this.statusText$ = this.statusTextSubject.asObservable();
@@ -1294,15 +1237,6 @@ class VoiceAgentService {
1294
1237
  this.botTranscript$ = this.botTranscriptSubject.asObservable();
1295
1238
  // Waveform visualization only - do NOT use for speaking state
1296
1239
  this.subscriptions.add(this.audioAnalyzer.audioLevels$.subscribe((levels) => this.audioLevelsSubject.next(levels)));
1297
- // Transcripts: single subscription for service lifetime (avoid stacking on each connect()).
1298
- // WebSocket is disconnected between calls; no replay — new subscribers (setupVoiceTranscripts)
1299
- // only receive messages from the new WS after connect.
1300
- this.subscriptions.add(this.wsClient.userTranscript$
1301
- .pipe(takeUntil(this.destroy$))
1302
- .subscribe((t) => this.userTranscriptSubject.next(t)));
1303
- this.subscriptions.add(this.wsClient.botTranscript$
1304
- .pipe(takeUntil(this.destroy$))
1305
- .subscribe((t) => this.botTranscriptSubject.next(t)));
1306
1240
  }
1307
1241
  ngOnDestroy() {
1308
1242
  this.destroy$.next();
@@ -1313,8 +1247,6 @@ class VoiceAgentService {
1313
1247
  resetToIdle() {
1314
1248
  if (this.callStateSubject.value === 'idle')
1315
1249
  return;
1316
- this.callSubscriptions.unsubscribe();
1317
- this.callSubscriptions = new Subscription();
1318
1250
  this.stopDurationTimer();
1319
1251
  this.audioAnalyzer.stop();
1320
1252
  this.wsClient.disconnect();
@@ -1324,8 +1256,7 @@ class VoiceAgentService {
1324
1256
  this.statusTextSubject.next('');
1325
1257
  this.durationSubject.next('0:00');
1326
1258
  }
1327
- connect(apiUrl, token, botId, conversationId, apiKey, eventToken, eventId, eventUrl, domainAuthority, usersApiUrl, existingMicStream) {
1328
- var _a;
1259
+ connect(apiUrl, token, botId, conversationId, apiKey, eventToken, eventId, eventUrl, domainAuthority, usersApiUrl) {
1329
1260
  return __awaiter(this, void 0, void 0, function* () {
1330
1261
  if (this.callStateSubject.value !== 'idle') {
1331
1262
  console.warn('Call already in progress');
@@ -1334,41 +1265,26 @@ class VoiceAgentService {
1334
1265
  try {
1335
1266
  this.callStateSubject.next('connecting');
1336
1267
  this.statusTextSubject.next('Connecting...');
1337
- const tokenPromise = usersApiUrl && isPlatformBrowser(this.platformId)
1338
- ? this.platformTokenRefresh
1339
- .ensureValidAccessToken(token, usersApiUrl)
1340
- .pipe(take(1))
1341
- .toPromise()
1342
- .then((ensured) => { var _a; return (_a = ensured === null || ensured === void 0 ? void 0 : ensured.accessToken) !== null && _a !== void 0 ? _a : token; })
1343
- .catch((e) => {
1268
+ let accessToken = token;
1269
+ // Align with chat drawer token handling: always delegate to
1270
+ // PlatformTokenRefreshService when we have a usersApiUrl, so it can
1271
+ // fall back to stored tokens even if the caller passed an empty token.
1272
+ if (usersApiUrl && isPlatformBrowser(this.platformId)) {
1273
+ try {
1274
+ const ensured = yield this.platformTokenRefresh
1275
+ .ensureValidAccessToken(token, usersApiUrl)
1276
+ .pipe(take(1))
1277
+ .toPromise();
1278
+ if (ensured === null || ensured === void 0 ? void 0 : ensured.accessToken) {
1279
+ accessToken = ensured.accessToken;
1280
+ }
1281
+ }
1282
+ catch (e) {
1344
1283
  console.warn('[HiveGpt Voice] Token refresh before connect failed', e);
1345
- return token;
1346
- })
1347
- : Promise.resolve(token);
1348
- const prepPromise = Promise.resolve().then(() => {
1349
- const baseUrl = apiUrl.replace(/\/$/, '');
1350
- return {
1351
- postUrl: `${baseUrl}/ai/ask-voice`,
1352
- body: JSON.stringify({
1353
- bot_id: botId,
1354
- conversation_id: conversationId,
1355
- voice: 'alloy',
1356
- }),
1357
- };
1358
- });
1359
- const micPromise = (existingMicStream === null || existingMicStream === void 0 ? void 0 : existingMicStream.getAudioTracks().some((t) => t.readyState === 'live'))
1360
- ? Promise.resolve(existingMicStream)
1361
- : isPlatformBrowser(this.platformId) &&
1362
- ((_a = navigator.mediaDevices) === null || _a === void 0 ? void 0 : _a.getUserMedia)
1363
- ? navigator.mediaDevices
1364
- .getUserMedia({ audio: true })
1365
- .catch(() => undefined)
1366
- : Promise.resolve(undefined);
1367
- const [accessToken, { postUrl, body }, micStream] = yield Promise.all([
1368
- tokenPromise,
1369
- prepPromise,
1370
- micPromise,
1371
- ]);
1284
+ }
1285
+ }
1286
+ const baseUrl = apiUrl.replace(/\/$/, '');
1287
+ const postUrl = `${baseUrl}/ai/ask-voice`;
1372
1288
  const headers = {
1373
1289
  'Content-Type': 'application/json',
1374
1290
  Authorization: `Bearer ${accessToken}`,
@@ -1384,7 +1300,11 @@ class VoiceAgentService {
1384
1300
  const res = yield fetch(postUrl, {
1385
1301
  method: 'POST',
1386
1302
  headers,
1387
- body,
1303
+ body: JSON.stringify({
1304
+ bot_id: botId,
1305
+ conversation_id: conversationId,
1306
+ voice: 'alloy',
1307
+ }),
1388
1308
  });
1389
1309
  if (!res.ok) {
1390
1310
  throw new Error(`HTTP ${res.status}`);
@@ -1394,31 +1314,30 @@ class VoiceAgentService {
1394
1314
  if (!wsUrl || typeof wsUrl !== 'string') {
1395
1315
  throw new Error('No ws_url in response');
1396
1316
  }
1397
- // Subscribe before connect so the first room_created is never missed.
1398
- // Await until Daily join completes — callers must not treat WS "open" as call ready.
1399
- let roomCreatedSub;
1400
- const roomJoined = new Promise((resolve, reject) => {
1401
- roomCreatedSub = this.wsClient.roomCreated$
1402
- .pipe(take(1), takeUntil(this.destroy$))
1403
- .subscribe((roomUrl) => __awaiter(this, void 0, void 0, function* () {
1404
- try {
1405
- yield this.onRoomCreated(roomUrl, micStream !== null && micStream !== void 0 ? micStream : undefined);
1406
- resolve();
1407
- }
1408
- catch (err) {
1409
- console.error('Daily join failed:', err);
1410
- reject(err);
1411
- }
1412
- }), (err) => reject(err));
1413
- });
1414
- try {
1415
- yield this.wsClient.connect(wsUrl);
1416
- yield roomJoined;
1417
- }
1418
- catch (e) {
1419
- roomCreatedSub === null || roomCreatedSub === void 0 ? void 0 : roomCreatedSub.unsubscribe();
1420
- throw e;
1421
- }
1317
+ // Subscribe to room_created BEFORE connecting to avoid race
1318
+ this.wsClient.roomCreated$
1319
+ .pipe(take(1), takeUntil(this.destroy$))
1320
+ .subscribe((roomUrl) => __awaiter(this, void 0, void 0, function* () {
1321
+ try {
1322
+ yield this.onRoomCreated(roomUrl);
1323
+ }
1324
+ catch (err) {
1325
+ console.error('Daily join failed:', err);
1326
+ this.callStateSubject.next('ended');
1327
+ this.statusTextSubject.next('Connection failed');
1328
+ yield this.disconnect();
1329
+ throw err;
1330
+ }
1331
+ }));
1332
+ // Forward transcripts from WebSocket
1333
+ this.subscriptions.add(this.wsClient.userTranscript$
1334
+ .pipe(takeUntil(this.destroy$))
1335
+ .subscribe((t) => this.userTranscriptSubject.next(t)));
1336
+ this.subscriptions.add(this.wsClient.botTranscript$
1337
+ .pipe(takeUntil(this.destroy$))
1338
+ .subscribe((t) => this.botTranscriptSubject.next(t)));
1339
+ // Connect signaling WebSocket (no audio over WS)
1340
+ this.wsClient.connect(wsUrl);
1422
1341
  }
1423
1342
  catch (error) {
1424
1343
  console.error('Error connecting voice agent:', error);
@@ -1429,19 +1348,18 @@ class VoiceAgentService {
1429
1348
  }
1430
1349
  });
1431
1350
  }
1432
- onRoomCreated(roomUrl, micStream) {
1351
+ onRoomCreated(roomUrl) {
1433
1352
  return __awaiter(this, void 0, void 0, function* () {
1434
- yield this.dailyClient.connect(roomUrl, undefined, micStream);
1435
- this.callSubscriptions.unsubscribe();
1436
- this.callSubscriptions = new Subscription();
1353
+ // Connect Daily.js for WebRTC audio
1354
+ yield this.dailyClient.connect(roomUrl);
1437
1355
  // Waveform: use local mic stream from Daily client
1438
- this.callSubscriptions.add(this.dailyClient.localStream$
1356
+ this.dailyClient.localStream$
1439
1357
  .pipe(filter((s) => s != null), take(1))
1440
1358
  .subscribe((stream) => {
1441
1359
  this.audioAnalyzer.start(stream);
1442
- }));
1443
- this.callSubscriptions.add(this.dailyClient.userSpeaking$.subscribe((s) => this.isUserSpeakingSubject.next(s)));
1444
- this.callSubscriptions.add(combineLatest([
1360
+ });
1361
+ this.subscriptions.add(this.dailyClient.userSpeaking$.subscribe((s) => this.isUserSpeakingSubject.next(s)));
1362
+ this.subscriptions.add(combineLatest([
1445
1363
  this.dailyClient.speaking$,
1446
1364
  this.dailyClient.userSpeaking$,
1447
1365
  ]).subscribe(([bot, user]) => {
@@ -1461,19 +1379,16 @@ class VoiceAgentService {
1461
1379
  else if (bot) {
1462
1380
  this.callStateSubject.next('talking');
1463
1381
  }
1464
- else {
1465
- // Between bot turns: stay on listening to avoid flicker via 'connected'
1466
- this.callStateSubject.next('listening');
1382
+ else if (current === 'talking' || current === 'listening') {
1383
+ this.callStateSubject.next('connected');
1467
1384
  }
1468
1385
  }));
1469
- this.callSubscriptions.add(this.dailyClient.micMuted$.subscribe((muted) => this.isMicMutedSubject.next(muted)));
1386
+ this.subscriptions.add(this.dailyClient.micMuted$.subscribe((muted) => this.isMicMutedSubject.next(muted)));
1470
1387
  this.statusTextSubject.next('Connecting...');
1471
1388
  });
1472
1389
  }
1473
1390
  disconnect() {
1474
1391
  return __awaiter(this, void 0, void 0, function* () {
1475
- this.callSubscriptions.unsubscribe();
1476
- this.callSubscriptions = new Subscription();
1477
1392
  this.stopDurationTimer();
1478
1393
  this.audioAnalyzer.stop();
1479
1394
  // Daily first, then WebSocket
@@ -1524,11 +1439,10 @@ const VOICE_MODAL_CONFIG = new InjectionToken('VOICE_MODAL_CONFIG');
1524
1439
  const VOICE_MODAL_CLOSE_CALLBACK = new InjectionToken('VOICE_MODAL_CLOSE_CALLBACK');
1525
1440
 
1526
1441
  class VoiceAgentModalComponent {
1527
- constructor(voiceAgentService, audioAnalyzer, injector, platformId) {
1442
+ constructor(voiceAgentService, audioAnalyzer, injector) {
1528
1443
  this.voiceAgentService = voiceAgentService;
1529
1444
  this.audioAnalyzer = audioAnalyzer;
1530
1445
  this.injector = injector;
1531
- this.platformId = platformId;
1532
1446
  this.close = new EventEmitter();
1533
1447
  this.apiKey = '';
1534
1448
  this.eventToken = '';
@@ -1540,8 +1454,6 @@ class VoiceAgentModalComponent {
1540
1454
  this.usersApiUrl = '';
1541
1455
  this.injectedConfig = null;
1542
1456
  this.onCloseCallback = null;
1543
- /** Held until destroy; passed to Daily so we do not stop/re-acquire (avoids extra prompts on some browsers). */
1544
- this.warmMicStream = null;
1545
1457
  /** Hardcoded voice agent avatar (Nia). */
1546
1458
  this.displayAvatarUrl = 'https://www.jotform.com/uploads/mehmetkarakasli/form_files/1564593667676a8e85f23758.86945537_icon.png';
1547
1459
  this.callState = 'idle';
@@ -1557,83 +1469,58 @@ class VoiceAgentModalComponent {
1557
1469
  this.isConnecting = false;
1558
1470
  }
1559
1471
  ngOnInit() {
1560
- void this.bootstrap();
1561
- }
1562
- bootstrap() {
1563
1472
  var _a, _b, _c, _d, _e, _f, _g, _h;
1564
- return __awaiter(this, void 0, void 0, function* () {
1565
- this.injectedConfig = this.injector.get(VOICE_MODAL_CONFIG, null);
1566
- this.onCloseCallback = this.injector.get(VOICE_MODAL_CLOSE_CALLBACK, null);
1567
- if (this.injectedConfig) {
1568
- this.apiUrl = this.injectedConfig.apiUrl;
1569
- this.token = this.injectedConfig.token;
1570
- this.botId = this.injectedConfig.botId;
1571
- this.conversationId = this.injectedConfig.conversationId;
1572
- this.apiKey = (_a = this.injectedConfig.apiKey) !== null && _a !== void 0 ? _a : '';
1573
- this.eventToken = (_b = this.injectedConfig.eventToken) !== null && _b !== void 0 ? _b : '';
1574
- this.eventId = (_c = this.injectedConfig.eventId) !== null && _c !== void 0 ? _c : '';
1575
- this.eventUrl = (_d = this.injectedConfig.eventUrl) !== null && _d !== void 0 ? _d : '';
1576
- this.domainAuthority = (_e = this.injectedConfig.domainAuthority) !== null && _e !== void 0 ? _e : 'prod-lite';
1577
- this.agentName = (_f = this.injectedConfig.agentName) !== null && _f !== void 0 ? _f : this.agentName;
1578
- this.agentRole = (_g = this.injectedConfig.agentRole) !== null && _g !== void 0 ? _g : this.agentRole;
1579
- this.agentAvatar = this.injectedConfig.agentAvatar;
1580
- this.usersApiUrl = (_h = this.injectedConfig.usersApiUrl) !== null && _h !== void 0 ? _h : this.usersApiUrl;
1473
+ // When opened via Overlay, config is provided by injection
1474
+ this.injectedConfig = this.injector.get(VOICE_MODAL_CONFIG, null);
1475
+ this.onCloseCallback = this.injector.get(VOICE_MODAL_CLOSE_CALLBACK, null);
1476
+ if (this.injectedConfig) {
1477
+ this.apiUrl = this.injectedConfig.apiUrl;
1478
+ this.token = this.injectedConfig.token;
1479
+ this.botId = this.injectedConfig.botId;
1480
+ this.conversationId = this.injectedConfig.conversationId;
1481
+ this.apiKey = (_a = this.injectedConfig.apiKey) !== null && _a !== void 0 ? _a : '';
1482
+ this.eventToken = (_b = this.injectedConfig.eventToken) !== null && _b !== void 0 ? _b : '';
1483
+ this.eventId = (_c = this.injectedConfig.eventId) !== null && _c !== void 0 ? _c : '';
1484
+ this.eventUrl = (_d = this.injectedConfig.eventUrl) !== null && _d !== void 0 ? _d : '';
1485
+ this.domainAuthority = (_e = this.injectedConfig.domainAuthority) !== null && _e !== void 0 ? _e : 'prod-lite';
1486
+ this.agentName = (_f = this.injectedConfig.agentName) !== null && _f !== void 0 ? _f : this.agentName;
1487
+ this.agentRole = (_g = this.injectedConfig.agentRole) !== null && _g !== void 0 ? _g : this.agentRole;
1488
+ this.agentAvatar = this.injectedConfig.agentAvatar;
1489
+ this.usersApiUrl = (_h = this.injectedConfig.usersApiUrl) !== null && _h !== void 0 ? _h : this.usersApiUrl;
1490
+ }
1491
+ // Subscribe to observables
1492
+ this.subscriptions.push(this.voiceAgentService.callState$.subscribe(state => {
1493
+ this.callState = state;
1494
+ this.isSpeaking = state === 'talking';
1495
+ if (state === 'listening' || state === 'talking') {
1496
+ this.hasLeftConnectedOnce = true;
1581
1497
  }
1582
- // Subscribe to observables
1583
- this.subscriptions.push(this.voiceAgentService.callState$.subscribe(state => {
1584
- this.callState = state;
1585
- this.isSpeaking = state === 'talking';
1586
- if (state === 'listening' || state === 'talking') {
1587
- this.hasLeftConnectedOnce = true;
1588
- }
1589
- if (state === 'idle' || state === 'ended') {
1590
- this.hasLeftConnectedOnce = false;
1591
- }
1592
- }));
1593
- this.subscriptions.push(this.voiceAgentService.statusText$.subscribe(text => {
1594
- this.statusText = text;
1595
- }));
1596
- this.subscriptions.push(this.voiceAgentService.duration$.subscribe(duration => {
1597
- this.duration = duration;
1598
- }));
1599
- this.subscriptions.push(this.voiceAgentService.isMicMuted$.subscribe(muted => {
1600
- this.isMicMuted = muted;
1601
- }));
1602
- this.subscriptions.push(this.voiceAgentService.isUserSpeaking$.subscribe(speaking => {
1603
- this.isUserSpeaking = speaking;
1604
- }));
1605
- this.subscriptions.push(this.voiceAgentService.audioLevels$.subscribe(levels => {
1606
- this.audioLevels = levels;
1607
- }));
1608
- this.voiceAgentService.resetToIdle();
1609
- yield this.startCall();
1610
- });
1498
+ if (state === 'idle' || state === 'ended') {
1499
+ this.hasLeftConnectedOnce = false;
1500
+ }
1501
+ }));
1502
+ this.subscriptions.push(this.voiceAgentService.statusText$.subscribe(text => {
1503
+ this.statusText = text;
1504
+ }));
1505
+ this.subscriptions.push(this.voiceAgentService.duration$.subscribe(duration => {
1506
+ this.duration = duration;
1507
+ }));
1508
+ this.subscriptions.push(this.voiceAgentService.isMicMuted$.subscribe(muted => {
1509
+ this.isMicMuted = muted;
1510
+ }));
1511
+ this.subscriptions.push(this.voiceAgentService.isUserSpeaking$.subscribe(speaking => {
1512
+ this.isUserSpeaking = speaking;
1513
+ }));
1514
+ this.subscriptions.push(this.voiceAgentService.audioLevels$.subscribe(levels => {
1515
+ this.audioLevels = levels;
1516
+ }));
1517
+ // Modal opens in idle state, then immediately starts connecting.
1518
+ this.voiceAgentService.resetToIdle();
1519
+ void this.startCall();
1611
1520
  }
1612
1521
  ngOnDestroy() {
1613
- this.subscriptions.forEach((sub) => sub.unsubscribe());
1614
- void this.disconnect().finally(() => {
1615
- var _a;
1616
- (_a = this.warmMicStream) === null || _a === void 0 ? void 0 : _a.getTracks().forEach((t) => t.stop());
1617
- this.warmMicStream = null;
1618
- });
1619
- }
1620
- /** Ensures a live mic stream for this call (re-acquire after Daily stops tracks on hang-up). */
1621
- ensureMicForCall() {
1622
- var _a;
1623
- return __awaiter(this, void 0, void 0, function* () {
1624
- if (!isPlatformBrowser(this.platformId))
1625
- return undefined;
1626
- if ((_a = this.warmMicStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks().some((t) => t.readyState === 'live')) {
1627
- return this.warmMicStream;
1628
- }
1629
- try {
1630
- this.warmMicStream = yield navigator.mediaDevices.getUserMedia({ audio: true });
1631
- return this.warmMicStream;
1632
- }
1633
- catch (_b) {
1634
- return undefined;
1635
- }
1636
- });
1522
+ this.subscriptions.forEach(sub => sub.unsubscribe());
1523
+ this.disconnect();
1637
1524
  }
1638
1525
  startCall() {
1639
1526
  return __awaiter(this, void 0, void 0, function* () {
@@ -1641,8 +1528,7 @@ class VoiceAgentModalComponent {
1641
1528
  return;
1642
1529
  this.isConnecting = true;
1643
1530
  try {
1644
- const mic = yield this.ensureMicForCall();
1645
- yield this.voiceAgentService.connect(this.apiUrl, this.token, this.botId, this.conversationId, this.apiKey, this.eventToken, this.eventId, this.eventUrl, this.domainAuthority, this.usersApiUrl || undefined, mic);
1531
+ yield this.voiceAgentService.connect(this.apiUrl, this.token, this.botId, this.conversationId, this.apiKey, this.eventToken, this.eventId, this.eventUrl, this.domainAuthority, this.usersApiUrl || undefined);
1646
1532
  }
1647
1533
  catch (error) {
1648
1534
  console.error('Failed to connect voice agent:', error);
@@ -1681,7 +1567,7 @@ class VoiceAgentModalComponent {
1681
1567
  /** Call Again: reset to idle then start a new call. */
1682
1568
  callAgain() {
1683
1569
  this.voiceAgentService.resetToIdle();
1684
- void this.startCall();
1570
+ this.startCall();
1685
1571
  }
1686
1572
  /** Back to Chat: close modal and disconnect. */
1687
1573
  backToChat() {
@@ -1708,8 +1594,7 @@ VoiceAgentModalComponent.decorators = [
1708
1594
  VoiceAgentModalComponent.ctorParameters = () => [
1709
1595
  { type: VoiceAgentService },
1710
1596
  { type: AudioAnalyzerService },
1711
- { type: Injector },
1712
- { type: Object, decorators: [{ type: Inject, args: [PLATFORM_ID,] }] }
1597
+ { type: Injector }
1713
1598
  ];
1714
1599
  VoiceAgentModalComponent.propDecorators = {
1715
1600
  close: [{ type: Output }],