@hivegpt/hiveai-angular 0.0.575 → 0.0.577

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 +195 -328
  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 +45 -67
  8. package/esm2015/lib/components/voice-agent/services/websocket-voice-client.service.js +34 -79
  9. package/fesm2015/hivegpt-hiveai-angular.js +142 -247
  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,98 +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
- }
881
- }
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
- });
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);
887
849
  }
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
- this.disconnect();
905
- };
906
- ws.onclose = () => {
907
- this.ws = null;
908
- if (!settled) {
909
- settled = true;
910
- clear();
911
- 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);
912
859
  }
913
- };
914
- }
915
- catch (err) {
916
- clear();
917
- 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 = () => {
918
869
  this.ws = null;
919
- reject(err instanceof Error ? err : new Error(String(err)));
920
- }
921
- });
870
+ };
871
+ }
872
+ catch (err) {
873
+ console.error('WebSocketVoiceClient: connect failed', err);
874
+ this.ws = null;
875
+ throw err;
876
+ }
922
877
  }
923
878
  /** Disconnect and cleanup. */
924
879
  disconnect() {
@@ -961,8 +916,7 @@ class DailyVoiceClientService {
961
916
  this.remoteAudioElement = null;
962
917
  /** AnalyserNode-based remote audio monitor for instant bot speaking detection. */
963
918
  this.remoteAudioContext = null;
964
- /** Poll interval id (~100ms); named historically when RAF was used. */
965
- this.remoteSpeakingPollId = null;
919
+ this.remoteSpeakingRAF = null;
966
920
  this.speakingSubject = new BehaviorSubject(false);
967
921
  this.userSpeakingSubject = new BehaviorSubject(false);
968
922
  this.micMutedSubject = new BehaviorSubject(false);
@@ -980,18 +934,15 @@ class DailyVoiceClientService {
980
934
  * Connect to Daily room. Acquires mic first for waveform, then joins with audio.
981
935
  * @param roomUrl Daily room URL (from room_created)
982
936
  * @param token Optional meeting token
983
- * @param existingStream Optional pre-acquired mic (avoids a second getUserMedia / extra prompts on some browsers)
984
937
  */
985
- connect(roomUrl, token, existingStream) {
938
+ connect(roomUrl, token) {
986
939
  return __awaiter(this, void 0, void 0, function* () {
987
940
  if (this.callObject) {
988
941
  yield this.disconnect();
989
942
  }
990
943
  try {
991
- const hasLiveTrack = !!(existingStream === null || existingStream === void 0 ? void 0 : existingStream.getAudioTracks().some((t) => t.readyState === 'live'));
992
- const stream = hasLiveTrack
993
- ? existingStream
994
- : 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 });
995
946
  const audioTrack = stream.getAudioTracks()[0];
996
947
  if (!audioTrack) {
997
948
  stream.getTracks().forEach((t) => t.stop());
@@ -1123,7 +1074,7 @@ class DailyVoiceClientService {
1123
1074
  }
1124
1075
  /**
1125
1076
  * Monitor remote audio track energy via AnalyserNode.
1126
- * Polls at ~10Hz; sufficient for speaking detection vs ~60fps RAF.
1077
+ * Polls at ~60fps and flips speakingSubject based on actual audio energy.
1127
1078
  */
1128
1079
  monitorRemoteAudio(track) {
1129
1080
  this.stopRemoteAudioMonitor();
@@ -1137,17 +1088,11 @@ class DailyVoiceClientService {
1137
1088
  const dataArray = new Uint8Array(analyser.frequencyBinCount);
1138
1089
  const THRESHOLD = 5;
1139
1090
  const SILENCE_MS = 1500;
1140
- const POLL_MS = 100;
1141
1091
  let lastSoundTime = 0;
1142
1092
  let isSpeaking = false;
1143
- this.remoteSpeakingPollId = setInterval(() => {
1144
- if (!this.remoteAudioContext) {
1145
- if (this.remoteSpeakingPollId) {
1146
- clearInterval(this.remoteSpeakingPollId);
1147
- this.remoteSpeakingPollId = null;
1148
- }
1093
+ const poll = () => {
1094
+ if (!this.remoteAudioContext)
1149
1095
  return;
1150
- }
1151
1096
  analyser.getByteFrequencyData(dataArray);
1152
1097
  let sum = 0;
1153
1098
  for (let i = 0; i < dataArray.length; i++) {
@@ -1171,16 +1116,18 @@ class DailyVoiceClientService {
1171
1116
  console.log(`[VoiceDebug] Bot audio silence detected (speaking=false) — ${new Date().toISOString()}`);
1172
1117
  this.ngZone.run(() => this.speakingSubject.next(false));
1173
1118
  }
1174
- }, POLL_MS);
1119
+ this.remoteSpeakingRAF = requestAnimationFrame(poll);
1120
+ };
1121
+ this.remoteSpeakingRAF = requestAnimationFrame(poll);
1175
1122
  }
1176
1123
  catch (err) {
1177
1124
  console.warn('DailyVoiceClient: failed to create remote audio monitor', err);
1178
1125
  }
1179
1126
  }
1180
1127
  stopRemoteAudioMonitor() {
1181
- if (this.remoteSpeakingPollId !== null) {
1182
- clearInterval(this.remoteSpeakingPollId);
1183
- this.remoteSpeakingPollId = null;
1128
+ if (this.remoteSpeakingRAF) {
1129
+ cancelAnimationFrame(this.remoteSpeakingRAF);
1130
+ this.remoteSpeakingRAF = null;
1184
1131
  }
1185
1132
  if (this.remoteAudioContext) {
1186
1133
  this.remoteAudioContext.close().catch(() => { });
@@ -1279,8 +1226,6 @@ class VoiceAgentService {
1279
1226
  this.callStartTime = 0;
1280
1227
  this.durationInterval = null;
1281
1228
  this.subscriptions = new Subscription();
1282
- /** Per-call only; cleared on disconnect / reset / new room so handlers do not stack. */
1283
- this.callSubscriptions = new Subscription();
1284
1229
  this.destroy$ = new Subject();
1285
1230
  this.callState$ = this.callStateSubject.asObservable();
1286
1231
  this.statusText$ = this.statusTextSubject.asObservable();
@@ -1292,15 +1237,6 @@ class VoiceAgentService {
1292
1237
  this.botTranscript$ = this.botTranscriptSubject.asObservable();
1293
1238
  // Waveform visualization only - do NOT use for speaking state
1294
1239
  this.subscriptions.add(this.audioAnalyzer.audioLevels$.subscribe((levels) => this.audioLevelsSubject.next(levels)));
1295
- // Transcripts: single subscription for service lifetime (avoid stacking on each connect()).
1296
- // WebSocket is disconnected between calls; no replay — new subscribers (setupVoiceTranscripts)
1297
- // only receive messages from the new WS after connect.
1298
- this.subscriptions.add(this.wsClient.userTranscript$
1299
- .pipe(takeUntil(this.destroy$))
1300
- .subscribe((t) => this.userTranscriptSubject.next(t)));
1301
- this.subscriptions.add(this.wsClient.botTranscript$
1302
- .pipe(takeUntil(this.destroy$))
1303
- .subscribe((t) => this.botTranscriptSubject.next(t)));
1304
1240
  }
1305
1241
  ngOnDestroy() {
1306
1242
  this.destroy$.next();
@@ -1311,8 +1247,6 @@ class VoiceAgentService {
1311
1247
  resetToIdle() {
1312
1248
  if (this.callStateSubject.value === 'idle')
1313
1249
  return;
1314
- this.callSubscriptions.unsubscribe();
1315
- this.callSubscriptions = new Subscription();
1316
1250
  this.stopDurationTimer();
1317
1251
  this.audioAnalyzer.stop();
1318
1252
  this.wsClient.disconnect();
@@ -1322,8 +1256,7 @@ class VoiceAgentService {
1322
1256
  this.statusTextSubject.next('');
1323
1257
  this.durationSubject.next('0:00');
1324
1258
  }
1325
- connect(apiUrl, token, botId, conversationId, apiKey, eventToken, eventId, eventUrl, domainAuthority, usersApiUrl, existingMicStream) {
1326
- var _a;
1259
+ connect(apiUrl, token, botId, conversationId, apiKey, eventToken, eventId, eventUrl, domainAuthority, usersApiUrl) {
1327
1260
  return __awaiter(this, void 0, void 0, function* () {
1328
1261
  if (this.callStateSubject.value !== 'idle') {
1329
1262
  console.warn('Call already in progress');
@@ -1332,41 +1265,26 @@ class VoiceAgentService {
1332
1265
  try {
1333
1266
  this.callStateSubject.next('connecting');
1334
1267
  this.statusTextSubject.next('Connecting...');
1335
- const tokenPromise = usersApiUrl && isPlatformBrowser(this.platformId)
1336
- ? this.platformTokenRefresh
1337
- .ensureValidAccessToken(token, usersApiUrl)
1338
- .pipe(take(1))
1339
- .toPromise()
1340
- .then((ensured) => { var _a; return (_a = ensured === null || ensured === void 0 ? void 0 : ensured.accessToken) !== null && _a !== void 0 ? _a : token; })
1341
- .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) {
1342
1283
  console.warn('[HiveGpt Voice] Token refresh before connect failed', e);
1343
- return token;
1344
- })
1345
- : Promise.resolve(token);
1346
- const prepPromise = Promise.resolve().then(() => {
1347
- const baseUrl = apiUrl.replace(/\/$/, '');
1348
- return {
1349
- postUrl: `${baseUrl}/ai/ask-voice`,
1350
- body: JSON.stringify({
1351
- bot_id: botId,
1352
- conversation_id: conversationId,
1353
- voice: 'alloy',
1354
- }),
1355
- };
1356
- });
1357
- const micPromise = (existingMicStream === null || existingMicStream === void 0 ? void 0 : existingMicStream.getAudioTracks().some((t) => t.readyState === 'live'))
1358
- ? Promise.resolve(existingMicStream)
1359
- : isPlatformBrowser(this.platformId) &&
1360
- ((_a = navigator.mediaDevices) === null || _a === void 0 ? void 0 : _a.getUserMedia)
1361
- ? navigator.mediaDevices
1362
- .getUserMedia({ audio: true })
1363
- .catch(() => undefined)
1364
- : Promise.resolve(undefined);
1365
- const [accessToken, { postUrl, body }, micStream] = yield Promise.all([
1366
- tokenPromise,
1367
- prepPromise,
1368
- micPromise,
1369
- ]);
1284
+ }
1285
+ }
1286
+ const baseUrl = apiUrl.replace(/\/$/, '');
1287
+ const postUrl = `${baseUrl}/ai/ask-voice`;
1370
1288
  const headers = {
1371
1289
  'Content-Type': 'application/json',
1372
1290
  Authorization: `Bearer ${accessToken}`,
@@ -1382,7 +1300,11 @@ class VoiceAgentService {
1382
1300
  const res = yield fetch(postUrl, {
1383
1301
  method: 'POST',
1384
1302
  headers,
1385
- body,
1303
+ body: JSON.stringify({
1304
+ bot_id: botId,
1305
+ conversation_id: conversationId,
1306
+ voice: 'alloy',
1307
+ }),
1386
1308
  });
1387
1309
  if (!res.ok) {
1388
1310
  throw new Error(`HTTP ${res.status}`);
@@ -1397,7 +1319,7 @@ class VoiceAgentService {
1397
1319
  .pipe(take(1), takeUntil(this.destroy$))
1398
1320
  .subscribe((roomUrl) => __awaiter(this, void 0, void 0, function* () {
1399
1321
  try {
1400
- yield this.onRoomCreated(roomUrl, micStream !== null && micStream !== void 0 ? micStream : undefined);
1322
+ yield this.onRoomCreated(roomUrl);
1401
1323
  }
1402
1324
  catch (err) {
1403
1325
  console.error('Daily join failed:', err);
@@ -1407,8 +1329,15 @@ class VoiceAgentService {
1407
1329
  throw err;
1408
1330
  }
1409
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)));
1410
1339
  // Connect signaling WebSocket (no audio over WS)
1411
- yield this.wsClient.connect(wsUrl);
1340
+ this.wsClient.connect(wsUrl);
1412
1341
  }
1413
1342
  catch (error) {
1414
1343
  console.error('Error connecting voice agent:', error);
@@ -1419,19 +1348,18 @@ class VoiceAgentService {
1419
1348
  }
1420
1349
  });
1421
1350
  }
1422
- onRoomCreated(roomUrl, micStream) {
1351
+ onRoomCreated(roomUrl) {
1423
1352
  return __awaiter(this, void 0, void 0, function* () {
1424
- yield this.dailyClient.connect(roomUrl, undefined, micStream);
1425
- this.callSubscriptions.unsubscribe();
1426
- this.callSubscriptions = new Subscription();
1353
+ // Connect Daily.js for WebRTC audio
1354
+ yield this.dailyClient.connect(roomUrl);
1427
1355
  // Waveform: use local mic stream from Daily client
1428
- this.callSubscriptions.add(this.dailyClient.localStream$
1356
+ this.dailyClient.localStream$
1429
1357
  .pipe(filter((s) => s != null), take(1))
1430
1358
  .subscribe((stream) => {
1431
1359
  this.audioAnalyzer.start(stream);
1432
- }));
1433
- this.callSubscriptions.add(this.dailyClient.userSpeaking$.subscribe((s) => this.isUserSpeakingSubject.next(s)));
1434
- this.callSubscriptions.add(combineLatest([
1360
+ });
1361
+ this.subscriptions.add(this.dailyClient.userSpeaking$.subscribe((s) => this.isUserSpeakingSubject.next(s)));
1362
+ this.subscriptions.add(combineLatest([
1435
1363
  this.dailyClient.speaking$,
1436
1364
  this.dailyClient.userSpeaking$,
1437
1365
  ]).subscribe(([bot, user]) => {
@@ -1451,19 +1379,16 @@ class VoiceAgentService {
1451
1379
  else if (bot) {
1452
1380
  this.callStateSubject.next('talking');
1453
1381
  }
1454
- else {
1455
- // Between bot turns: stay on listening to avoid flicker via 'connected'
1456
- this.callStateSubject.next('listening');
1382
+ else if (current === 'talking' || current === 'listening') {
1383
+ this.callStateSubject.next('connected');
1457
1384
  }
1458
1385
  }));
1459
- 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)));
1460
1387
  this.statusTextSubject.next('Connecting...');
1461
1388
  });
1462
1389
  }
1463
1390
  disconnect() {
1464
1391
  return __awaiter(this, void 0, void 0, function* () {
1465
- this.callSubscriptions.unsubscribe();
1466
- this.callSubscriptions = new Subscription();
1467
1392
  this.stopDurationTimer();
1468
1393
  this.audioAnalyzer.stop();
1469
1394
  // Daily first, then WebSocket
@@ -1514,11 +1439,10 @@ const VOICE_MODAL_CONFIG = new InjectionToken('VOICE_MODAL_CONFIG');
1514
1439
  const VOICE_MODAL_CLOSE_CALLBACK = new InjectionToken('VOICE_MODAL_CLOSE_CALLBACK');
1515
1440
 
1516
1441
  class VoiceAgentModalComponent {
1517
- constructor(voiceAgentService, audioAnalyzer, injector, platformId) {
1442
+ constructor(voiceAgentService, audioAnalyzer, injector) {
1518
1443
  this.voiceAgentService = voiceAgentService;
1519
1444
  this.audioAnalyzer = audioAnalyzer;
1520
1445
  this.injector = injector;
1521
- this.platformId = platformId;
1522
1446
  this.close = new EventEmitter();
1523
1447
  this.apiKey = '';
1524
1448
  this.eventToken = '';
@@ -1530,8 +1454,6 @@ class VoiceAgentModalComponent {
1530
1454
  this.usersApiUrl = '';
1531
1455
  this.injectedConfig = null;
1532
1456
  this.onCloseCallback = null;
1533
- /** Held until destroy; passed to Daily so we do not stop/re-acquire (avoids extra prompts on some browsers). */
1534
- this.warmMicStream = null;
1535
1457
  /** Hardcoded voice agent avatar (Nia). */
1536
1458
  this.displayAvatarUrl = 'https://www.jotform.com/uploads/mehmetkarakasli/form_files/1564593667676a8e85f23758.86945537_icon.png';
1537
1459
  this.callState = 'idle';
@@ -1547,83 +1469,58 @@ class VoiceAgentModalComponent {
1547
1469
  this.isConnecting = false;
1548
1470
  }
1549
1471
  ngOnInit() {
1550
- void this.bootstrap();
1551
- }
1552
- bootstrap() {
1553
1472
  var _a, _b, _c, _d, _e, _f, _g, _h;
1554
- return __awaiter(this, void 0, void 0, function* () {
1555
- this.injectedConfig = this.injector.get(VOICE_MODAL_CONFIG, null);
1556
- this.onCloseCallback = this.injector.get(VOICE_MODAL_CLOSE_CALLBACK, null);
1557
- if (this.injectedConfig) {
1558
- this.apiUrl = this.injectedConfig.apiUrl;
1559
- this.token = this.injectedConfig.token;
1560
- this.botId = this.injectedConfig.botId;
1561
- this.conversationId = this.injectedConfig.conversationId;
1562
- this.apiKey = (_a = this.injectedConfig.apiKey) !== null && _a !== void 0 ? _a : '';
1563
- this.eventToken = (_b = this.injectedConfig.eventToken) !== null && _b !== void 0 ? _b : '';
1564
- this.eventId = (_c = this.injectedConfig.eventId) !== null && _c !== void 0 ? _c : '';
1565
- this.eventUrl = (_d = this.injectedConfig.eventUrl) !== null && _d !== void 0 ? _d : '';
1566
- this.domainAuthority = (_e = this.injectedConfig.domainAuthority) !== null && _e !== void 0 ? _e : 'prod-lite';
1567
- this.agentName = (_f = this.injectedConfig.agentName) !== null && _f !== void 0 ? _f : this.agentName;
1568
- this.agentRole = (_g = this.injectedConfig.agentRole) !== null && _g !== void 0 ? _g : this.agentRole;
1569
- this.agentAvatar = this.injectedConfig.agentAvatar;
1570
- 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;
1571
1497
  }
1572
- // Subscribe to observables
1573
- this.subscriptions.push(this.voiceAgentService.callState$.subscribe(state => {
1574
- this.callState = state;
1575
- this.isSpeaking = state === 'talking';
1576
- if (state === 'listening' || state === 'talking') {
1577
- this.hasLeftConnectedOnce = true;
1578
- }
1579
- if (state === 'idle' || state === 'ended') {
1580
- this.hasLeftConnectedOnce = false;
1581
- }
1582
- }));
1583
- this.subscriptions.push(this.voiceAgentService.statusText$.subscribe(text => {
1584
- this.statusText = text;
1585
- }));
1586
- this.subscriptions.push(this.voiceAgentService.duration$.subscribe(duration => {
1587
- this.duration = duration;
1588
- }));
1589
- this.subscriptions.push(this.voiceAgentService.isMicMuted$.subscribe(muted => {
1590
- this.isMicMuted = muted;
1591
- }));
1592
- this.subscriptions.push(this.voiceAgentService.isUserSpeaking$.subscribe(speaking => {
1593
- this.isUserSpeaking = speaking;
1594
- }));
1595
- this.subscriptions.push(this.voiceAgentService.audioLevels$.subscribe(levels => {
1596
- this.audioLevels = levels;
1597
- }));
1598
- this.voiceAgentService.resetToIdle();
1599
- yield this.startCall();
1600
- });
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();
1601
1520
  }
1602
1521
  ngOnDestroy() {
1603
- this.subscriptions.forEach((sub) => sub.unsubscribe());
1604
- void this.disconnect().finally(() => {
1605
- var _a;
1606
- (_a = this.warmMicStream) === null || _a === void 0 ? void 0 : _a.getTracks().forEach((t) => t.stop());
1607
- this.warmMicStream = null;
1608
- });
1609
- }
1610
- /** Ensures a live mic stream for this call (re-acquire after Daily stops tracks on hang-up). */
1611
- ensureMicForCall() {
1612
- var _a;
1613
- return __awaiter(this, void 0, void 0, function* () {
1614
- if (!isPlatformBrowser(this.platformId))
1615
- return undefined;
1616
- if ((_a = this.warmMicStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks().some((t) => t.readyState === 'live')) {
1617
- return this.warmMicStream;
1618
- }
1619
- try {
1620
- this.warmMicStream = yield navigator.mediaDevices.getUserMedia({ audio: true });
1621
- return this.warmMicStream;
1622
- }
1623
- catch (_b) {
1624
- return undefined;
1625
- }
1626
- });
1522
+ this.subscriptions.forEach(sub => sub.unsubscribe());
1523
+ this.disconnect();
1627
1524
  }
1628
1525
  startCall() {
1629
1526
  return __awaiter(this, void 0, void 0, function* () {
@@ -1631,8 +1528,7 @@ class VoiceAgentModalComponent {
1631
1528
  return;
1632
1529
  this.isConnecting = true;
1633
1530
  try {
1634
- const mic = yield this.ensureMicForCall();
1635
- 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);
1636
1532
  }
1637
1533
  catch (error) {
1638
1534
  console.error('Failed to connect voice agent:', error);
@@ -1671,7 +1567,7 @@ class VoiceAgentModalComponent {
1671
1567
  /** Call Again: reset to idle then start a new call. */
1672
1568
  callAgain() {
1673
1569
  this.voiceAgentService.resetToIdle();
1674
- void this.startCall();
1570
+ this.startCall();
1675
1571
  }
1676
1572
  /** Back to Chat: close modal and disconnect. */
1677
1573
  backToChat() {
@@ -1698,8 +1594,7 @@ VoiceAgentModalComponent.decorators = [
1698
1594
  VoiceAgentModalComponent.ctorParameters = () => [
1699
1595
  { type: VoiceAgentService },
1700
1596
  { type: AudioAnalyzerService },
1701
- { type: Injector },
1702
- { type: Object, decorators: [{ type: Inject, args: [PLATFORM_ID,] }] }
1597
+ { type: Injector }
1703
1598
  ];
1704
1599
  VoiceAgentModalComponent.propDecorators = {
1705
1600
  close: [{ type: Output }],