@hivegpt/hiveai-angular 0.0.574 → 0.0.575

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 (21) hide show
  1. package/bundles/hivegpt-hiveai-angular.umd.js +525 -270
  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/chat-drawer/chat-drawer.component.js +6 -6
  6. package/esm2015/lib/components/voice-agent/components/voice-agent-modal/voice-agent-modal.component.js +85 -54
  7. package/esm2015/lib/components/voice-agent/services/daily-voice-client.service.js +153 -63
  8. package/esm2015/lib/components/voice-agent/services/voice-agent.service.js +148 -84
  9. package/esm2015/lib/components/voice-agent/services/websocket-voice-client.service.js +79 -34
  10. package/fesm2015/hivegpt-hiveai-angular.js +464 -235
  11. package/fesm2015/hivegpt-hiveai-angular.js.map +1 -1
  12. package/hivegpt-hiveai-angular.metadata.json +1 -1
  13. package/lib/components/voice-agent/components/voice-agent-modal/voice-agent-modal.component.d.ts +7 -1
  14. package/lib/components/voice-agent/components/voice-agent-modal/voice-agent-modal.component.d.ts.map +1 -1
  15. package/lib/components/voice-agent/services/daily-voice-client.service.d.ts +37 -3
  16. package/lib/components/voice-agent/services/daily-voice-client.service.d.ts.map +1 -1
  17. package/lib/components/voice-agent/services/voice-agent.service.d.ts +19 -6
  18. package/lib/components/voice-agent/services/voice-agent.service.d.ts.map +1 -1
  19. package/lib/components/voice-agent/services/websocket-voice-client.service.d.ts +5 -12
  20. package/lib/components/voice-agent/services/websocket-voice-client.service.d.ts.map +1 -1
  21. package/package.json +1 -1
@@ -1349,6 +1349,7 @@
1349
1349
  * - Emit roomCreated$, userTranscript$, botTranscript$
1350
1350
  * - NO audio logic, NO mic logic. Audio is handled by Daily.js (WebRTC).
1351
1351
  */
1352
+ var WS_CONNECT_TIMEOUT_MS = 10000;
1352
1353
  var WebSocketVoiceClientService = /** @class */ (function () {
1353
1354
  function WebSocketVoiceClientService() {
1354
1355
  this.ws = null;
@@ -1362,55 +1363,99 @@
1362
1363
  /** Emits bot transcript updates. */
1363
1364
  this.botTranscript$ = this.botTranscriptSubject.asObservable();
1364
1365
  }
1365
- /** Connect to signaling WebSocket. No audio over this connection. */
1366
+ /**
1367
+ * Connect to signaling WebSocket. No audio over this connection.
1368
+ * Resolves when the socket is open; rejects if the connection fails.
1369
+ */
1366
1370
  WebSocketVoiceClientService.prototype.connect = function (wsUrl) {
1367
1371
  var _this = this;
1368
1372
  var _a;
1369
1373
  if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN) {
1370
- return;
1374
+ return Promise.resolve();
1371
1375
  }
1372
1376
  if (this.ws) {
1373
1377
  this.ws.close();
1374
1378
  this.ws = null;
1375
1379
  }
1376
- try {
1377
- this.ws = new WebSocket(wsUrl);
1378
- this.ws.onmessage = function (event) {
1380
+ return new Promise(function (resolve, reject) {
1381
+ var settled = false;
1382
+ var timeout = setTimeout(function () {
1379
1383
  var _a;
1384
+ if (settled)
1385
+ return;
1386
+ settled = true;
1380
1387
  try {
1381
- var msg = JSON.parse(event.data);
1382
- if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'room_created') {
1383
- var roomUrl = ((_a = msg.room_url) !== null && _a !== void 0 ? _a : msg.roomUrl);
1384
- if (typeof roomUrl === 'string') {
1385
- _this.roomCreatedSubject.next(roomUrl);
1386
- }
1387
- }
1388
- else if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'user_transcript' && typeof msg.text === 'string') {
1389
- _this.userTranscriptSubject.next({
1390
- text: msg.text,
1391
- final: msg.final === true,
1392
- });
1393
- }
1394
- else if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'bot_transcript' && typeof msg.text === 'string') {
1395
- _this.botTranscriptSubject.next(msg.text);
1396
- }
1388
+ (_a = _this.ws) === null || _a === void 0 ? void 0 : _a.close();
1397
1389
  }
1398
1390
  catch (_b) {
1399
- // Ignore non-JSON or unknown messages
1391
+ /* ignore */
1400
1392
  }
1401
- };
1402
- this.ws.onerror = function () {
1403
- _this.disconnect();
1404
- };
1405
- this.ws.onclose = function () {
1406
1393
  _this.ws = null;
1394
+ reject(new Error('WebSocket connection timed out'));
1395
+ }, WS_CONNECT_TIMEOUT_MS);
1396
+ var clear = function () {
1397
+ clearTimeout(timeout);
1407
1398
  };
1408
- }
1409
- catch (err) {
1410
- console.error('WebSocketVoiceClient: connect failed', err);
1411
- this.ws = null;
1412
- throw err;
1413
- }
1399
+ try {
1400
+ var ws = new WebSocket(wsUrl);
1401
+ _this.ws = ws;
1402
+ ws.onopen = function () {
1403
+ if (settled)
1404
+ return;
1405
+ settled = true;
1406
+ clear();
1407
+ resolve();
1408
+ };
1409
+ ws.onmessage = function (event) {
1410
+ var _a;
1411
+ try {
1412
+ var msg = JSON.parse(event.data);
1413
+ if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'room_created') {
1414
+ var roomUrl = ((_a = msg.room_url) !== null && _a !== void 0 ? _a : msg.roomUrl);
1415
+ if (typeof roomUrl === 'string') {
1416
+ _this.roomCreatedSubject.next(roomUrl);
1417
+ }
1418
+ }
1419
+ else if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'user_transcript' && typeof msg.text === 'string') {
1420
+ _this.userTranscriptSubject.next({
1421
+ text: msg.text,
1422
+ final: msg.final === true,
1423
+ });
1424
+ }
1425
+ else if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'bot_transcript' && typeof msg.text === 'string') {
1426
+ _this.botTranscriptSubject.next(msg.text);
1427
+ }
1428
+ }
1429
+ catch (_b) {
1430
+ // Ignore non-JSON or unknown messages
1431
+ }
1432
+ };
1433
+ ws.onerror = function () {
1434
+ if (!settled) {
1435
+ settled = true;
1436
+ clear();
1437
+ _this.disconnect();
1438
+ reject(new Error('WebSocket connection failed'));
1439
+ return;
1440
+ }
1441
+ _this.disconnect();
1442
+ };
1443
+ ws.onclose = function () {
1444
+ _this.ws = null;
1445
+ if (!settled) {
1446
+ settled = true;
1447
+ clear();
1448
+ reject(new Error('WebSocket connection failed'));
1449
+ }
1450
+ };
1451
+ }
1452
+ catch (err) {
1453
+ clear();
1454
+ console.error('WebSocketVoiceClient: connect failed', err);
1455
+ _this.ws = null;
1456
+ reject(err instanceof Error ? err : new Error(String(err)));
1457
+ }
1458
+ });
1414
1459
  };
1415
1460
  /** Disconnect and cleanup. */
1416
1461
  WebSocketVoiceClientService.prototype.disconnect = function () {
@@ -1437,27 +1482,51 @@
1437
1482
  },] }
1438
1483
  ];
1439
1484
 
1485
+ /**
1486
+ * Daily.js WebRTC client for voice agent audio.
1487
+ * Responsibilities:
1488
+ * - Create and manage Daily CallObject
1489
+ * - Join Daily room using room_url
1490
+ * - Handle mic capture + speaker playback
1491
+ * - Bot speaking detection via AnalyserNode on remote track (instant)
1492
+ * - User speaking detection via active-speaker-change
1493
+ * - Expose speaking$ (bot speaking), userSpeaking$ (user speaking), micMuted$
1494
+ * - Expose localStream$ for waveform visualization (AudioAnalyzerService)
1495
+ */
1440
1496
  var DailyVoiceClientService = /** @class */ (function () {
1441
1497
  function DailyVoiceClientService(ngZone) {
1442
1498
  this.ngZone = ngZone;
1443
1499
  this.callObject = null;
1444
1500
  this.localStream = null;
1445
1501
  this.localSessionId = null;
1502
+ /** Explicit playback of remote (bot) audio; required in some browsers. */
1446
1503
  this.remoteAudioElement = null;
1504
+ /** AnalyserNode-based remote audio monitor for instant bot speaking detection. */
1447
1505
  this.remoteAudioContext = null;
1448
- this.remoteSpeakingRAF = null;
1506
+ /** Poll interval id (~100ms); named historically when RAF was used. */
1507
+ this.remoteSpeakingPollId = null;
1449
1508
  this.speakingSubject = new rxjs.BehaviorSubject(false);
1450
1509
  this.userSpeakingSubject = new rxjs.BehaviorSubject(false);
1451
- this.micMutedSubject = new rxjs.BehaviorSubject(true); // 🔴 default muted
1510
+ this.micMutedSubject = new rxjs.BehaviorSubject(false);
1452
1511
  this.localStreamSubject = new rxjs.BehaviorSubject(null);
1512
+ /** True when bot (remote participant) is the active speaker. */
1453
1513
  this.speaking$ = this.speakingSubject.asObservable();
1514
+ /** True when user (local participant) is the active speaker. */
1454
1515
  this.userSpeaking$ = this.userSpeakingSubject.asObservable();
1516
+ /** True when mic is muted. */
1455
1517
  this.micMuted$ = this.micMutedSubject.asObservable();
1518
+ /** Emits local mic stream for waveform visualization. */
1456
1519
  this.localStream$ = this.localStreamSubject.asObservable();
1457
1520
  }
1458
- DailyVoiceClientService.prototype.connect = function (roomUrl, token) {
1521
+ /**
1522
+ * Connect to Daily room. Acquires mic first for waveform, then joins with audio.
1523
+ * @param roomUrl Daily room URL (from room_created)
1524
+ * @param token Optional meeting token
1525
+ * @param existingStream Optional pre-acquired mic (avoids a second getUserMedia / extra prompts on some browsers)
1526
+ */
1527
+ DailyVoiceClientService.prototype.connect = function (roomUrl, token, existingStream) {
1459
1528
  return __awaiter(this, void 0, void 0, function () {
1460
- var stream, audioTrack, callObject, joinOptions, participants, err_1;
1529
+ var hasLiveTrack, stream, _e, audioTrack, callObject, joinOptions, participants, err_1;
1461
1530
  return __generator(this, function (_f) {
1462
1531
  switch (_f.label) {
1463
1532
  case 0:
@@ -1467,13 +1536,22 @@
1467
1536
  _f.sent();
1468
1537
  _f.label = 2;
1469
1538
  case 2:
1470
- _f.trys.push([2, 5, , 6]);
1471
- return [4 /*yield*/, navigator.mediaDevices.getUserMedia({ audio: true })];
1472
- case 3:
1473
- stream = _f.sent();
1539
+ _f.trys.push([2, 7, , 8]);
1540
+ hasLiveTrack = !!(existingStream === null || existingStream === void 0 ? void 0 : existingStream.getAudioTracks().some(function (t) { return t.readyState === 'live'; }));
1541
+ if (!hasLiveTrack) return [3 /*break*/, 3];
1542
+ _e = existingStream;
1543
+ return [3 /*break*/, 5];
1544
+ case 3: return [4 /*yield*/, navigator.mediaDevices.getUserMedia({ audio: true })];
1545
+ case 4:
1546
+ _e = _f.sent();
1547
+ _f.label = 5;
1548
+ case 5:
1549
+ stream = _e;
1474
1550
  audioTrack = stream.getAudioTracks()[0];
1475
- if (!audioTrack)
1551
+ if (!audioTrack) {
1552
+ stream.getTracks().forEach(function (t) { return t.stop(); });
1476
1553
  throw new Error('No audio track');
1554
+ }
1477
1555
  this.localStream = stream;
1478
1556
  this.localStreamSubject.next(stream);
1479
1557
  callObject = Daily__default["default"].createCallObject({
@@ -1482,36 +1560,34 @@
1482
1560
  });
1483
1561
  this.callObject = callObject;
1484
1562
  this.setupEventHandlers(callObject);
1485
- // 🔴 Ensure mic is OFF before join
1486
- callObject.setLocalAudio(false);
1487
- this.micMutedSubject.next(true);
1488
1563
  joinOptions = { url: roomUrl };
1489
- if (typeof token === 'string' && token.trim()) {
1564
+ if (typeof token === 'string' && token.trim() !== '') {
1490
1565
  joinOptions.token = token;
1491
1566
  }
1492
1567
  return [4 /*yield*/, callObject.join(joinOptions)];
1493
- case 4:
1568
+ case 6:
1494
1569
  _f.sent();
1495
- console.log("[VoiceDebug] Joined room \u2014 " + new Date().toISOString());
1570
+ console.log("[VoiceDebug] Room connected (Daily join complete) \u2014 " + new Date().toISOString());
1496
1571
  participants = callObject.participants();
1497
1572
  if (participants === null || participants === void 0 ? void 0 : participants.local) {
1498
1573
  this.localSessionId = participants.local.session_id;
1499
1574
  }
1500
- // 🔴 Force sync again (Daily sometimes overrides)
1501
- callObject.setLocalAudio(false);
1502
- this.micMutedSubject.next(true);
1503
- return [3 /*break*/, 6];
1504
- case 5:
1575
+ // Initial mute state: Daily starts with audio on
1576
+ this.micMutedSubject.next(!callObject.localAudio());
1577
+ return [3 /*break*/, 8];
1578
+ case 7:
1505
1579
  err_1 = _f.sent();
1506
1580
  this.cleanup();
1507
1581
  throw err_1;
1508
- case 6: return [2 /*return*/];
1582
+ case 8: return [2 /*return*/];
1509
1583
  }
1510
1584
  });
1511
1585
  });
1512
1586
  };
1513
1587
  DailyVoiceClientService.prototype.setupEventHandlers = function (call) {
1514
1588
  var _this = this;
1589
+ // active-speaker-change: used ONLY for user speaking detection.
1590
+ // Bot speaking is detected by our own AnalyserNode (instant, no debounce).
1515
1591
  call.on('active-speaker-change', function (event) {
1516
1592
  _this.ngZone.run(function () {
1517
1593
  var _a;
@@ -1520,20 +1596,23 @@
1520
1596
  _this.userSpeakingSubject.next(false);
1521
1597
  return;
1522
1598
  }
1523
- _this.userSpeakingSubject.next(peerId === _this.localSessionId);
1599
+ var isLocal = peerId === _this.localSessionId;
1600
+ _this.userSpeakingSubject.next(isLocal);
1524
1601
  });
1525
1602
  });
1603
+ // track-started / track-stopped: set up remote audio playback + AnalyserNode monitor.
1526
1604
  call.on('track-started', function (event) {
1527
1605
  _this.ngZone.run(function () {
1528
- var _a, _b, _c, _d, _e;
1606
+ var _a, _b, _c, _d;
1529
1607
  var p = event === null || event === void 0 ? void 0 : event.participant;
1530
1608
  var type = (_a = event === null || event === void 0 ? void 0 : event.type) !== null && _a !== void 0 ? _a : (_b = event === null || event === void 0 ? void 0 : event.track) === null || _b === void 0 ? void 0 : _b.kind;
1609
+ var track = event === null || event === void 0 ? void 0 : event.track;
1531
1610
  if (p && !p.local && type === 'audio') {
1532
- var track = (_c = event.track) !== null && _c !== void 0 ? _c : (_e = (_d = p === null || p === void 0 ? void 0 : p.tracks) === null || _d === void 0 ? void 0 : _d.audio) === null || _e === void 0 ? void 0 : _e.track;
1533
- if (track) {
1534
- console.log('[VoiceDebug] Remote audio track received');
1535
- _this.playRemoteTrack(track);
1536
- _this.monitorRemoteAudio(track);
1611
+ console.log("[VoiceDebug] Got audio track from backend (track-started) \u2014 readyState=" + (track === null || track === void 0 ? void 0 : track.readyState) + ", muted=" + (track === null || track === void 0 ? void 0 : track.muted) + " \u2014 " + new Date().toISOString());
1612
+ var audioTrack = track !== null && track !== void 0 ? track : (_d = (_c = p.tracks) === null || _c === void 0 ? void 0 : _c.audio) === null || _d === void 0 ? void 0 : _d.track;
1613
+ if (audioTrack && typeof audioTrack === 'object') {
1614
+ _this.playRemoteTrack(audioTrack);
1615
+ _this.monitorRemoteAudio(audioTrack);
1537
1616
  }
1538
1617
  }
1539
1618
  });
@@ -1552,27 +1631,57 @@
1552
1631
  call.on('left-meeting', function () {
1553
1632
  _this.ngZone.run(function () { return _this.cleanup(); });
1554
1633
  });
1555
- call.on('error', function (e) {
1556
- console.error('Daily error:', e);
1557
- _this.cleanup();
1634
+ call.on('error', function (event) {
1635
+ _this.ngZone.run(function () {
1636
+ var _a;
1637
+ console.error('DailyVoiceClient: Daily error', (_a = event === null || event === void 0 ? void 0 : event.errorMsg) !== null && _a !== void 0 ? _a : event);
1638
+ _this.cleanup();
1639
+ });
1558
1640
  });
1559
1641
  };
1642
+ /**
1643
+ * Play remote (bot) audio track via a dedicated audio element.
1644
+ * Required in many browsers where Daily's internal playback does not output to speakers.
1645
+ */
1560
1646
  DailyVoiceClientService.prototype.playRemoteTrack = function (track) {
1561
1647
  this.stopRemoteAudio();
1562
1648
  try {
1649
+ console.log("[VoiceDebug] playRemoteTrack called \u2014 track.readyState=" + track.readyState + ", track.muted=" + track.muted + " \u2014 " + new Date().toISOString());
1650
+ track.onunmute = function () {
1651
+ console.log("[VoiceDebug] Remote audio track UNMUTED (audio data arriving) \u2014 " + new Date().toISOString());
1652
+ };
1563
1653
  var stream = new MediaStream([track]);
1564
1654
  var audio = new Audio();
1565
1655
  audio.autoplay = true;
1566
1656
  audio.srcObject = stream;
1567
1657
  this.remoteAudioElement = audio;
1568
- audio.play().catch(function () {
1569
- console.warn('Autoplay blocked');
1570
- });
1658
+ audio.onplaying = function () {
1659
+ console.log("[VoiceDebug] Audio element PLAYING (browser started playback) \u2014 " + new Date().toISOString());
1660
+ };
1661
+ var firstTimeUpdate_1 = true;
1662
+ audio.ontimeupdate = function () {
1663
+ if (firstTimeUpdate_1) {
1664
+ firstTimeUpdate_1 = false;
1665
+ console.log("[VoiceDebug] Audio element first TIMEUPDATE (actual audio output) \u2014 " + new Date().toISOString());
1666
+ }
1667
+ };
1668
+ var p = audio.play();
1669
+ if (p && typeof p.then === 'function') {
1670
+ p.then(function () {
1671
+ console.log("[VoiceDebug] audio.play() resolved \u2014 " + new Date().toISOString());
1672
+ }).catch(function (err) {
1673
+ console.warn('DailyVoiceClient: remote audio play failed (may need user gesture)', err);
1674
+ });
1675
+ }
1571
1676
  }
1572
1677
  catch (err) {
1573
- console.warn('Audio playback error', err);
1678
+ console.warn('DailyVoiceClient: failed to create remote audio element', err);
1574
1679
  }
1575
1680
  };
1681
+ /**
1682
+ * Monitor remote audio track energy via AnalyserNode.
1683
+ * Polls at ~10Hz; sufficient for speaking detection vs ~60fps RAF.
1684
+ */
1576
1685
  DailyVoiceClientService.prototype.monitorRemoteAudio = function (track) {
1577
1686
  var _this = this;
1578
1687
  this.stopRemoteAudioMonitor();
@@ -1583,75 +1692,96 @@
1583
1692
  analyser_1.fftSize = 256;
1584
1693
  source.connect(analyser_1);
1585
1694
  this.remoteAudioContext = ctx;
1586
- var data_1 = new Uint8Array(analyser_1.frequencyBinCount);
1587
- var speaking_1 = false;
1588
- var lastSound_1 = 0;
1589
- var loop_1 = function () {
1590
- if (!_this.remoteAudioContext)
1695
+ var dataArray_1 = new Uint8Array(analyser_1.frequencyBinCount);
1696
+ var THRESHOLD_1 = 5;
1697
+ var SILENCE_MS_1 = 1500;
1698
+ var POLL_MS = 100;
1699
+ var lastSoundTime_1 = 0;
1700
+ var isSpeaking_1 = false;
1701
+ this.remoteSpeakingPollId = setInterval(function () {
1702
+ if (!_this.remoteAudioContext) {
1703
+ if (_this.remoteSpeakingPollId) {
1704
+ clearInterval(_this.remoteSpeakingPollId);
1705
+ _this.remoteSpeakingPollId = null;
1706
+ }
1591
1707
  return;
1592
- analyser_1.getByteFrequencyData(data_1);
1593
- var avg = data_1.reduce(function (a, b) { return a + b; }, 0) / data_1.length;
1708
+ }
1709
+ analyser_1.getByteFrequencyData(dataArray_1);
1710
+ var sum = 0;
1711
+ for (var i = 0; i < dataArray_1.length; i++) {
1712
+ sum += dataArray_1[i];
1713
+ }
1714
+ var avg = sum / dataArray_1.length;
1594
1715
  var now = Date.now();
1595
- if (avg > 5) {
1596
- lastSound_1 = now;
1597
- if (!speaking_1) {
1598
- speaking_1 = true;
1716
+ if (avg > THRESHOLD_1) {
1717
+ lastSoundTime_1 = now;
1718
+ if (!isSpeaking_1) {
1719
+ isSpeaking_1 = true;
1720
+ console.log("[VoiceDebug] Bot audio energy detected (speaking=true) \u2014 avg=" + avg.toFixed(1) + " \u2014 " + new Date().toISOString());
1599
1721
  _this.ngZone.run(function () {
1600
1722
  _this.userSpeakingSubject.next(false);
1601
1723
  _this.speakingSubject.next(true);
1602
1724
  });
1603
1725
  }
1604
1726
  }
1605
- else if (speaking_1 && now - lastSound_1 > 1500) {
1606
- speaking_1 = false;
1727
+ else if (isSpeaking_1 && now - lastSoundTime_1 > SILENCE_MS_1) {
1728
+ isSpeaking_1 = false;
1729
+ console.log("[VoiceDebug] Bot audio silence detected (speaking=false) \u2014 " + new Date().toISOString());
1607
1730
  _this.ngZone.run(function () { return _this.speakingSubject.next(false); });
1608
1731
  }
1609
- _this.remoteSpeakingRAF = requestAnimationFrame(loop_1);
1610
- };
1611
- this.remoteSpeakingRAF = requestAnimationFrame(loop_1);
1732
+ }, POLL_MS);
1733
+ }
1734
+ catch (err) {
1735
+ console.warn('DailyVoiceClient: failed to create remote audio monitor', err);
1612
1736
  }
1613
- catch (_a) { }
1614
1737
  };
1615
1738
  DailyVoiceClientService.prototype.stopRemoteAudioMonitor = function () {
1616
- var _a;
1617
- if (this.remoteSpeakingRAF) {
1618
- cancelAnimationFrame(this.remoteSpeakingRAF);
1619
- this.remoteSpeakingRAF = null;
1739
+ if (this.remoteSpeakingPollId !== null) {
1740
+ clearInterval(this.remoteSpeakingPollId);
1741
+ this.remoteSpeakingPollId = null;
1742
+ }
1743
+ if (this.remoteAudioContext) {
1744
+ this.remoteAudioContext.close().catch(function () { });
1745
+ this.remoteAudioContext = null;
1620
1746
  }
1621
- (_a = this.remoteAudioContext) === null || _a === void 0 ? void 0 : _a.close().catch(function () { });
1622
- this.remoteAudioContext = null;
1623
1747
  };
1624
1748
  DailyVoiceClientService.prototype.stopRemoteAudio = function () {
1625
1749
  if (this.remoteAudioElement) {
1626
- this.remoteAudioElement.pause();
1627
- this.remoteAudioElement.srcObject = null;
1750
+ try {
1751
+ this.remoteAudioElement.pause();
1752
+ this.remoteAudioElement.srcObject = null;
1753
+ }
1754
+ catch (_) { }
1628
1755
  this.remoteAudioElement = null;
1629
1756
  }
1630
1757
  };
1758
+ /** Set mic muted state. */
1631
1759
  DailyVoiceClientService.prototype.setMuted = function (muted) {
1632
1760
  if (!this.callObject)
1633
1761
  return;
1634
1762
  this.callObject.setLocalAudio(!muted);
1635
1763
  this.micMutedSubject.next(muted);
1636
- console.log("[VoiceDebug] Mic " + (muted ? 'MUTED' : 'UNMUTED'));
1637
1764
  };
1765
+ /** Disconnect and cleanup. */
1638
1766
  DailyVoiceClientService.prototype.disconnect = function () {
1639
1767
  return __awaiter(this, void 0, void 0, function () {
1640
- var _a_1;
1641
- return __generator(this, function (_f) {
1642
- switch (_f.label) {
1768
+ var e_1;
1769
+ return __generator(this, function (_e) {
1770
+ switch (_e.label) {
1643
1771
  case 0:
1644
- if (!this.callObject)
1645
- return [2 /*return*/, this.cleanup()];
1646
- _f.label = 1;
1772
+ if (!this.callObject) {
1773
+ this.cleanup();
1774
+ return [2 /*return*/];
1775
+ }
1776
+ _e.label = 1;
1647
1777
  case 1:
1648
- _f.trys.push([1, 3, , 4]);
1778
+ _e.trys.push([1, 3, , 4]);
1649
1779
  return [4 /*yield*/, this.callObject.leave()];
1650
1780
  case 2:
1651
- _f.sent();
1781
+ _e.sent();
1652
1782
  return [3 /*break*/, 4];
1653
1783
  case 3:
1654
- _a_1 = _f.sent();
1784
+ e_1 = _e.sent();
1655
1785
  return [3 /*break*/, 4];
1656
1786
  case 4:
1657
1787
  this.cleanup();
@@ -1661,17 +1791,21 @@
1661
1791
  });
1662
1792
  };
1663
1793
  DailyVoiceClientService.prototype.cleanup = function () {
1664
- var _a, _b;
1665
1794
  this.stopRemoteAudioMonitor();
1666
1795
  this.stopRemoteAudio();
1667
- (_a = this.callObject) === null || _a === void 0 ? void 0 : _a.destroy().catch(function () { });
1668
- this.callObject = null;
1669
- (_b = this.localStream) === null || _b === void 0 ? void 0 : _b.getTracks().forEach(function (t) { return t.stop(); });
1670
- this.localStream = null;
1796
+ if (this.callObject) {
1797
+ this.callObject.destroy().catch(function () { });
1798
+ this.callObject = null;
1799
+ }
1800
+ if (this.localStream) {
1801
+ this.localStream.getTracks().forEach(function (t) { return t.stop(); });
1802
+ this.localStream = null;
1803
+ }
1671
1804
  this.localSessionId = null;
1672
1805
  this.speakingSubject.next(false);
1673
1806
  this.userSpeakingSubject.next(false);
1674
1807
  this.localStreamSubject.next(null);
1808
+ // Keep last micMuted state; will reset on next connect
1675
1809
  };
1676
1810
  return DailyVoiceClientService;
1677
1811
  }());
@@ -1685,8 +1819,21 @@
1685
1819
  { type: i0.NgZone }
1686
1820
  ]; };
1687
1821
 
1822
+ /**
1823
+ * Voice agent orchestrator. Coordinates WebSocket (signaling) and Daily.js (WebRTC audio).
1824
+ *
1825
+ * CRITICAL: This service must NEVER use Socket.IO or ngx-socket-io. Voice flow uses only:
1826
+ * - Native WebSocket (WebSocketVoiceClientService) for signaling (room_created, transcripts)
1827
+ * - Daily.js (DailyVoiceClientService) for WebRTC audio. Audio does NOT flow over WebSocket.
1828
+ *
1829
+ * - Maintains callState, statusText, duration, isMicMuted, isUserSpeaking, audioLevels
1830
+ * - Uses WebSocket for room_created and transcripts only (no audio)
1831
+ * - Uses Daily.js for all audio, mic, and real-time speaking detection
1832
+ */
1688
1833
  var VoiceAgentService = /** @class */ (function () {
1689
- function VoiceAgentService(audioAnalyzer, wsClient, dailyClient, platformTokenRefresh, platformId) {
1834
+ function VoiceAgentService(audioAnalyzer, wsClient, dailyClient, platformTokenRefresh,
1835
+ /** `Object` not `object` — ngc metadata collection rejects the `object` type in DI params. */
1836
+ platformId) {
1690
1837
  var _this = this;
1691
1838
  this.audioAnalyzer = audioAnalyzer;
1692
1839
  this.wsClient = wsClient;
@@ -1696,7 +1843,7 @@
1696
1843
  this.callStateSubject = new rxjs.BehaviorSubject('idle');
1697
1844
  this.statusTextSubject = new rxjs.BehaviorSubject('');
1698
1845
  this.durationSubject = new rxjs.BehaviorSubject('00:00');
1699
- this.isMicMutedSubject = new rxjs.BehaviorSubject(true);
1846
+ this.isMicMutedSubject = new rxjs.BehaviorSubject(false);
1700
1847
  this.isUserSpeakingSubject = new rxjs.BehaviorSubject(false);
1701
1848
  this.audioLevelsSubject = new rxjs.BehaviorSubject([]);
1702
1849
  this.userTranscriptSubject = new rxjs.Subject();
@@ -1704,6 +1851,8 @@
1704
1851
  this.callStartTime = 0;
1705
1852
  this.durationInterval = null;
1706
1853
  this.subscriptions = new rxjs.Subscription();
1854
+ /** Per-call only; cleared on disconnect / reset / new room so handlers do not stack. */
1855
+ this.callSubscriptions = new rxjs.Subscription();
1707
1856
  this.destroy$ = new rxjs.Subject();
1708
1857
  this.callState$ = this.callStateSubject.asObservable();
1709
1858
  this.statusText$ = this.statusTextSubject.asObservable();
@@ -1713,151 +1862,192 @@
1713
1862
  this.audioLevels$ = this.audioLevelsSubject.asObservable();
1714
1863
  this.userTranscript$ = this.userTranscriptSubject.asObservable();
1715
1864
  this.botTranscript$ = this.botTranscriptSubject.asObservable();
1865
+ // Waveform visualization only - do NOT use for speaking state
1716
1866
  this.subscriptions.add(this.audioAnalyzer.audioLevels$.subscribe(function (levels) { return _this.audioLevelsSubject.next(levels); }));
1867
+ // Transcripts: single subscription for service lifetime (avoid stacking on each connect()).
1868
+ // WebSocket is disconnected between calls; no replay — new subscribers (setupVoiceTranscripts)
1869
+ // only receive messages from the new WS after connect.
1870
+ this.subscriptions.add(this.wsClient.userTranscript$
1871
+ .pipe(operators.takeUntil(this.destroy$))
1872
+ .subscribe(function (t) { return _this.userTranscriptSubject.next(t); }));
1873
+ this.subscriptions.add(this.wsClient.botTranscript$
1874
+ .pipe(operators.takeUntil(this.destroy$))
1875
+ .subscribe(function (t) { return _this.botTranscriptSubject.next(t); }));
1717
1876
  }
1718
1877
  VoiceAgentService.prototype.ngOnDestroy = function () {
1719
1878
  this.destroy$.next();
1720
1879
  this.subscriptions.unsubscribe();
1721
1880
  this.disconnect();
1722
1881
  };
1723
- /**
1724
- * Tear down transports and reset UI state so a new `connect()` can run.
1725
- * `connect()` only proceeds from `idle`; use this after `ended` or when reopening the modal.
1726
- */
1882
+ /** Reset to idle state (e.g. when modal opens so user can click Start Call). */
1727
1883
  VoiceAgentService.prototype.resetToIdle = function () {
1884
+ if (this.callStateSubject.value === 'idle')
1885
+ return;
1886
+ this.callSubscriptions.unsubscribe();
1887
+ this.callSubscriptions = new rxjs.Subscription();
1728
1888
  this.stopDurationTimer();
1729
1889
  this.audioAnalyzer.stop();
1730
1890
  this.wsClient.disconnect();
1891
+ // Fire-and-forget: Daily disconnect is async; connect() will await if needed
1731
1892
  void this.dailyClient.disconnect();
1732
1893
  this.callStateSubject.next('idle');
1733
1894
  this.statusTextSubject.next('');
1734
- this.durationSubject.next('00:00');
1735
- this.isMicMutedSubject.next(true);
1736
- this.isUserSpeakingSubject.next(false);
1737
- this.audioLevelsSubject.next([]);
1895
+ this.durationSubject.next('0:00');
1738
1896
  };
1739
- VoiceAgentService.prototype.connect = function (apiUrl, token, botId, conversationId, apiKey, eventToken, eventId, eventUrl, domainAuthority, usersApiUrl) {
1897
+ VoiceAgentService.prototype.connect = function (apiUrl, token, botId, conversationId, apiKey, eventToken, eventId, eventUrl, domainAuthority, usersApiUrl, existingMicStream) {
1898
+ var _a;
1740
1899
  return __awaiter(this, void 0, void 0, function () {
1741
- var accessToken, ensured, _a_1, base, postUrl, eventIdHeader, res, json, wsUrl, e_1;
1900
+ var tokenPromise, prepPromise, micPromise, _b, accessToken, _c, postUrl, body, micStream_1, headers, res, json, wsUrl, error_1;
1742
1901
  var _this = this;
1743
- return __generator(this, function (_b) {
1744
- switch (_b.label) {
1902
+ return __generator(this, function (_d) {
1903
+ switch (_d.label) {
1745
1904
  case 0:
1746
- if (this.callStateSubject.value !== 'idle')
1905
+ if (this.callStateSubject.value !== 'idle') {
1906
+ console.warn('Call already in progress');
1747
1907
  return [2 /*return*/];
1748
- _b.label = 1;
1908
+ }
1909
+ _d.label = 1;
1749
1910
  case 1:
1750
- _b.trys.push([1, 8, , 9]);
1911
+ _d.trys.push([1, 6, , 8]);
1751
1912
  this.callStateSubject.next('connecting');
1752
1913
  this.statusTextSubject.next('Connecting...');
1753
- accessToken = token;
1754
- if (!(usersApiUrl && common.isPlatformBrowser(this.platformId))) return [3 /*break*/, 5];
1755
- _b.label = 2;
1756
- case 2:
1757
- _b.trys.push([2, 4, , 5]);
1758
- return [4 /*yield*/, this.platformTokenRefresh
1914
+ tokenPromise = usersApiUrl && common.isPlatformBrowser(this.platformId)
1915
+ ? this.platformTokenRefresh
1759
1916
  .ensureValidAccessToken(token, usersApiUrl)
1760
1917
  .pipe(operators.take(1))
1761
- .toPromise()];
1762
- case 3:
1763
- ensured = _b.sent();
1764
- if (ensured === null || ensured === void 0 ? void 0 : ensured.accessToken) {
1765
- accessToken = ensured.accessToken;
1766
- }
1767
- return [3 /*break*/, 5];
1768
- case 4:
1769
- _a_1 = _b.sent();
1770
- return [3 /*break*/, 5];
1771
- case 5:
1772
- base = (apiUrl || '').replace(/\/+$/, '');
1773
- postUrl = base + "/ai/ask-voice";
1774
- eventIdHeader = (eventId && String(eventId).trim()) || '';
1775
- return [4 /*yield*/, fetch(postUrl, {
1776
- method: 'POST',
1777
- headers: {
1778
- 'Content-Type': 'application/json',
1779
- Authorization: "Bearer " + accessToken,
1780
- 'domain-authority': domainAuthority,
1781
- eventtoken: eventToken,
1782
- eventurl: eventUrl,
1783
- 'hive-bot-id': botId,
1784
- 'x-api-key': apiKey,
1785
- eventId: eventIdHeader,
1786
- },
1918
+ .toPromise()
1919
+ .then(function (ensured) { var _a; return (_a = ensured === null || ensured === void 0 ? void 0 : ensured.accessToken) !== null && _a !== void 0 ? _a : token; })
1920
+ .catch(function (e) {
1921
+ console.warn('[HiveGpt Voice] Token refresh before connect failed', e);
1922
+ return token;
1923
+ })
1924
+ : Promise.resolve(token);
1925
+ prepPromise = Promise.resolve().then(function () {
1926
+ var baseUrl = apiUrl.replace(/\/$/, '');
1927
+ return {
1928
+ postUrl: baseUrl + "/ai/ask-voice",
1787
1929
  body: JSON.stringify({
1788
1930
  bot_id: botId,
1789
1931
  conversation_id: conversationId,
1790
1932
  voice: 'alloy',
1791
1933
  }),
1934
+ };
1935
+ });
1936
+ micPromise = (existingMicStream === null || existingMicStream === void 0 ? void 0 : existingMicStream.getAudioTracks().some(function (t) { return t.readyState === 'live'; }))
1937
+ ? Promise.resolve(existingMicStream)
1938
+ : common.isPlatformBrowser(this.platformId) &&
1939
+ ((_a = navigator.mediaDevices) === null || _a === void 0 ? void 0 : _a.getUserMedia)
1940
+ ? navigator.mediaDevices
1941
+ .getUserMedia({ audio: true })
1942
+ .catch(function () { return undefined; })
1943
+ : Promise.resolve(undefined);
1944
+ return [4 /*yield*/, Promise.all([
1945
+ tokenPromise,
1946
+ prepPromise,
1947
+ micPromise,
1948
+ ])];
1949
+ case 2:
1950
+ _b = __read.apply(void 0, [_d.sent(), 3]), accessToken = _b[0], _c = _b[1], postUrl = _c.postUrl, body = _c.body, micStream_1 = _b[2];
1951
+ headers = {
1952
+ 'Content-Type': 'application/json',
1953
+ Authorization: "Bearer " + accessToken,
1954
+ 'x-api-key': apiKey,
1955
+ 'hive-bot-id': botId,
1956
+ 'domain-authority': domainAuthority,
1957
+ eventUrl: eventUrl,
1958
+ eventId: eventId,
1959
+ eventToken: eventToken,
1960
+ 'ngrok-skip-browser-warning': 'true',
1961
+ };
1962
+ return [4 /*yield*/, fetch(postUrl, {
1963
+ method: 'POST',
1964
+ headers: headers,
1965
+ body: body,
1792
1966
  })];
1793
- case 6:
1794
- res = _b.sent();
1967
+ case 3:
1968
+ res = _d.sent();
1969
+ if (!res.ok) {
1970
+ throw new Error("HTTP " + res.status);
1971
+ }
1795
1972
  return [4 /*yield*/, res.json()];
1796
- case 7:
1797
- json = _b.sent();
1973
+ case 4:
1974
+ json = _d.sent();
1798
1975
  wsUrl = json === null || json === void 0 ? void 0 : json.rn_ws_url;
1976
+ if (!wsUrl || typeof wsUrl !== 'string') {
1977
+ throw new Error('No ws_url in response');
1978
+ }
1979
+ // Subscribe to room_created BEFORE connecting to avoid race
1799
1980
  this.wsClient.roomCreated$
1800
1981
  .pipe(operators.take(1), operators.takeUntil(this.destroy$))
1801
1982
  .subscribe(function (roomUrl) { return __awaiter(_this, void 0, void 0, function () {
1983
+ var err_1;
1802
1984
  return __generator(this, function (_b) {
1803
1985
  switch (_b.label) {
1804
- case 0: return [4 /*yield*/, this.onRoomCreated(roomUrl)];
1986
+ case 0:
1987
+ _b.trys.push([0, 2, , 4]);
1988
+ return [4 /*yield*/, this.onRoomCreated(roomUrl, micStream_1 !== null && micStream_1 !== void 0 ? micStream_1 : undefined)];
1805
1989
  case 1:
1806
1990
  _b.sent();
1807
- return [2 /*return*/];
1991
+ return [3 /*break*/, 4];
1992
+ case 2:
1993
+ err_1 = _b.sent();
1994
+ console.error('Daily join failed:', err_1);
1995
+ this.callStateSubject.next('ended');
1996
+ this.statusTextSubject.next('Connection failed');
1997
+ return [4 /*yield*/, this.disconnect()];
1998
+ case 3:
1999
+ _b.sent();
2000
+ throw err_1;
2001
+ case 4: return [2 /*return*/];
1808
2002
  }
1809
2003
  });
1810
2004
  }); });
1811
- this.subscriptions.add(this.wsClient.userTranscript$.subscribe(function (t) { return _this.userTranscriptSubject.next(t); }));
1812
- this.subscriptions.add(this.wsClient.botTranscript$.subscribe(function (t) { return _this.botTranscriptSubject.next(t); }));
1813
- this.wsClient.connect(wsUrl);
1814
- return [3 /*break*/, 9];
1815
- case 8:
1816
- e_1 = _b.sent();
2005
+ // Connect signaling WebSocket (no audio over WS)
2006
+ return [4 /*yield*/, this.wsClient.connect(wsUrl)];
2007
+ case 5:
2008
+ // Connect signaling WebSocket (no audio over WS)
2009
+ _d.sent();
2010
+ return [3 /*break*/, 8];
2011
+ case 6:
2012
+ error_1 = _d.sent();
2013
+ console.error('Error connecting voice agent:', error_1);
1817
2014
  this.callStateSubject.next('ended');
1818
- return [3 /*break*/, 9];
1819
- case 9: return [2 /*return*/];
2015
+ return [4 /*yield*/, this.disconnect()];
2016
+ case 7:
2017
+ _d.sent();
2018
+ this.statusTextSubject.next('Connection failed');
2019
+ throw error_1;
2020
+ case 8: return [2 /*return*/];
1820
2021
  }
1821
2022
  });
1822
2023
  });
1823
2024
  };
1824
- VoiceAgentService.prototype.onRoomCreated = function (roomUrl) {
2025
+ VoiceAgentService.prototype.onRoomCreated = function (roomUrl, micStream) {
1825
2026
  return __awaiter(this, void 0, void 0, function () {
1826
- var handled;
1827
2027
  var _this = this;
1828
2028
  return __generator(this, function (_b) {
1829
2029
  switch (_b.label) {
1830
- case 0: return [4 /*yield*/, this.dailyClient.connect(roomUrl)];
2030
+ case 0: return [4 /*yield*/, this.dailyClient.connect(roomUrl, undefined, micStream)];
1831
2031
  case 1:
1832
2032
  _b.sent();
1833
- // 🔴 Start MUTED
1834
- this.dailyClient.setMuted(true);
1835
- this.isMicMutedSubject.next(true);
1836
- this.statusTextSubject.next('Listening to agent...');
1837
- handled = false;
1838
- this.dailyClient.speaking$.pipe(operators.filter(Boolean), operators.take(1)).subscribe(function () {
1839
- if (handled)
1840
- return;
1841
- handled = true;
1842
- console.log('[VoiceFlow] First bot response → enabling mic');
1843
- _this.dailyClient.setMuted(false);
1844
- _this.statusTextSubject.next('You can speak now');
1845
- });
1846
- // ⛑️ Fallback (if bot fails)
1847
- setTimeout(function () {
1848
- if (!handled) {
1849
- console.warn('[VoiceFlow] Fallback → enabling mic');
1850
- _this.dailyClient.setMuted(false);
1851
- _this.statusTextSubject.next('You can speak now');
1852
- }
1853
- }, 8000);
1854
- // rest same
1855
- this.subscriptions.add(rxjs.combineLatest([
2033
+ this.callSubscriptions.unsubscribe();
2034
+ this.callSubscriptions = new rxjs.Subscription();
2035
+ // Waveform: use local mic stream from Daily client
2036
+ this.callSubscriptions.add(this.dailyClient.localStream$
2037
+ .pipe(operators.filter(function (s) { return s != null; }), operators.take(1))
2038
+ .subscribe(function (stream) {
2039
+ _this.audioAnalyzer.start(stream);
2040
+ }));
2041
+ this.callSubscriptions.add(this.dailyClient.userSpeaking$.subscribe(function (s) { return _this.isUserSpeakingSubject.next(s); }));
2042
+ this.callSubscriptions.add(rxjs.combineLatest([
1856
2043
  this.dailyClient.speaking$,
1857
2044
  this.dailyClient.userSpeaking$,
1858
2045
  ]).subscribe(function (_b) {
1859
2046
  var _c = __read(_b, 2), bot = _c[0], user = _c[1];
1860
2047
  var current = _this.callStateSubject.value;
2048
+ if (current === 'connecting' && !bot) {
2049
+ return;
2050
+ }
1861
2051
  if (current === 'connecting' && bot) {
1862
2052
  _this.callStartTime = Date.now();
1863
2053
  _this.startDurationTimer();
@@ -1871,9 +2061,12 @@
1871
2061
  _this.callStateSubject.next('talking');
1872
2062
  }
1873
2063
  else {
1874
- _this.callStateSubject.next('connected');
2064
+ // Between bot turns: stay on listening to avoid flicker via 'connected'
2065
+ _this.callStateSubject.next('listening');
1875
2066
  }
1876
2067
  }));
2068
+ this.callSubscriptions.add(this.dailyClient.micMuted$.subscribe(function (muted) { return _this.isMicMutedSubject.next(muted); }));
2069
+ this.statusTextSubject.next('Connecting...');
1877
2070
  return [2 /*return*/];
1878
2071
  }
1879
2072
  });
@@ -1884,10 +2077,14 @@
1884
2077
  return __generator(this, function (_b) {
1885
2078
  switch (_b.label) {
1886
2079
  case 0:
2080
+ this.callSubscriptions.unsubscribe();
2081
+ this.callSubscriptions = new rxjs.Subscription();
1887
2082
  this.stopDurationTimer();
1888
2083
  this.audioAnalyzer.stop();
2084
+ // Daily first, then WebSocket
1889
2085
  return [4 /*yield*/, this.dailyClient.disconnect()];
1890
2086
  case 1:
2087
+ // Daily first, then WebSocket
1891
2088
  _b.sent();
1892
2089
  this.wsClient.disconnect();
1893
2090
  this.callStateSubject.next('ended');
@@ -1903,16 +2100,22 @@
1903
2100
  };
1904
2101
  VoiceAgentService.prototype.startDurationTimer = function () {
1905
2102
  var _this = this;
1906
- this.durationInterval = setInterval(function () {
1907
- var elapsed = Math.floor((Date.now() - _this.callStartTime) / 1000);
1908
- var m = Math.floor(elapsed / 60);
1909
- var s = elapsed % 60;
1910
- _this.durationSubject.next(m + ":" + String(s).padStart(2, '0'));
1911
- }, 1000);
2103
+ var updateDuration = function () {
2104
+ if (_this.callStartTime > 0) {
2105
+ var elapsed = Math.floor((Date.now() - _this.callStartTime) / 1000);
2106
+ var minutes = Math.floor(elapsed / 60);
2107
+ var seconds = elapsed % 60;
2108
+ _this.durationSubject.next(minutes + ":" + String(seconds).padStart(2, '0'));
2109
+ }
2110
+ };
2111
+ updateDuration();
2112
+ this.durationInterval = setInterval(updateDuration, 1000);
1912
2113
  };
1913
2114
  VoiceAgentService.prototype.stopDurationTimer = function () {
1914
- if (this.durationInterval)
2115
+ if (this.durationInterval) {
1915
2116
  clearInterval(this.durationInterval);
2117
+ this.durationInterval = null;
2118
+ }
1916
2119
  };
1917
2120
  return VoiceAgentService;
1918
2121
  }());
@@ -1934,10 +2137,11 @@
1934
2137
  var VOICE_MODAL_CLOSE_CALLBACK = new i0.InjectionToken('VOICE_MODAL_CLOSE_CALLBACK');
1935
2138
 
1936
2139
  var VoiceAgentModalComponent = /** @class */ (function () {
1937
- function VoiceAgentModalComponent(voiceAgentService, audioAnalyzer, injector) {
2140
+ function VoiceAgentModalComponent(voiceAgentService, audioAnalyzer, injector, platformId) {
1938
2141
  this.voiceAgentService = voiceAgentService;
1939
2142
  this.audioAnalyzer = audioAnalyzer;
1940
2143
  this.injector = injector;
2144
+ this.platformId = platformId;
1941
2145
  this.close = new i0.EventEmitter();
1942
2146
  this.apiKey = '';
1943
2147
  this.eventToken = '';
@@ -1949,6 +2153,8 @@
1949
2153
  this.usersApiUrl = '';
1950
2154
  this.injectedConfig = null;
1951
2155
  this.onCloseCallback = null;
2156
+ /** Held until destroy; passed to Daily so we do not stop/re-acquire (avoids extra prompts on some browsers). */
2157
+ this.warmMicStream = null;
1952
2158
  /** Hardcoded voice agent avatar (Nia). */
1953
2159
  this.displayAvatarUrl = 'https://www.jotform.com/uploads/mehmetkarakasli/form_files/1564593667676a8e85f23758.86945537_icon.png';
1954
2160
  this.callState = 'idle';
@@ -1964,63 +2170,108 @@
1964
2170
  this.isConnecting = false;
1965
2171
  }
1966
2172
  VoiceAgentModalComponent.prototype.ngOnInit = function () {
1967
- var _this = this;
2173
+ void this.bootstrap();
2174
+ };
2175
+ VoiceAgentModalComponent.prototype.bootstrap = function () {
1968
2176
  var _a, _b, _c, _d, _e, _f, _g, _h;
1969
- // When opened via Overlay, config is provided by injection
1970
- this.injectedConfig = this.injector.get(VOICE_MODAL_CONFIG, null);
1971
- this.onCloseCallback = this.injector.get(VOICE_MODAL_CLOSE_CALLBACK, null);
1972
- if (this.injectedConfig) {
1973
- this.apiUrl = this.injectedConfig.apiUrl;
1974
- this.token = this.injectedConfig.token;
1975
- this.botId = this.injectedConfig.botId;
1976
- this.conversationId = this.injectedConfig.conversationId;
1977
- this.apiKey = (_a = this.injectedConfig.apiKey) !== null && _a !== void 0 ? _a : '';
1978
- this.eventToken = (_b = this.injectedConfig.eventToken) !== null && _b !== void 0 ? _b : '';
1979
- this.eventId = (_c = this.injectedConfig.eventId) !== null && _c !== void 0 ? _c : '';
1980
- this.eventUrl = (_d = this.injectedConfig.eventUrl) !== null && _d !== void 0 ? _d : '';
1981
- this.domainAuthority = (_e = this.injectedConfig.domainAuthority) !== null && _e !== void 0 ? _e : 'prod-lite';
1982
- this.agentName = (_f = this.injectedConfig.agentName) !== null && _f !== void 0 ? _f : this.agentName;
1983
- this.agentRole = (_g = this.injectedConfig.agentRole) !== null && _g !== void 0 ? _g : this.agentRole;
1984
- this.agentAvatar = this.injectedConfig.agentAvatar;
1985
- this.usersApiUrl = (_h = this.injectedConfig.usersApiUrl) !== null && _h !== void 0 ? _h : this.usersApiUrl;
1986
- }
1987
- // Subscribe to observables
1988
- this.subscriptions.push(this.voiceAgentService.callState$.subscribe(function (state) {
1989
- _this.callState = state;
1990
- _this.isSpeaking = state === 'talking';
1991
- if (state === 'listening' || state === 'talking') {
1992
- _this.hasLeftConnectedOnce = true;
1993
- }
1994
- if (state === 'idle' || state === 'ended') {
1995
- _this.hasLeftConnectedOnce = false;
1996
- }
1997
- }));
1998
- this.subscriptions.push(this.voiceAgentService.statusText$.subscribe(function (text) {
1999
- _this.statusText = text;
2000
- }));
2001
- this.subscriptions.push(this.voiceAgentService.duration$.subscribe(function (duration) {
2002
- _this.duration = duration;
2003
- }));
2004
- this.subscriptions.push(this.voiceAgentService.isMicMuted$.subscribe(function (muted) {
2005
- _this.isMicMuted = muted;
2006
- }));
2007
- this.subscriptions.push(this.voiceAgentService.isUserSpeaking$.subscribe(function (speaking) {
2008
- _this.isUserSpeaking = speaking;
2009
- }));
2010
- this.subscriptions.push(this.voiceAgentService.audioLevels$.subscribe(function (levels) {
2011
- _this.audioLevels = levels;
2012
- }));
2013
- // Modal opens in idle state, then immediately starts connecting.
2014
- this.voiceAgentService.resetToIdle();
2015
- void this.startCall();
2177
+ return __awaiter(this, void 0, void 0, function () {
2178
+ var _this = this;
2179
+ return __generator(this, function (_j) {
2180
+ switch (_j.label) {
2181
+ case 0:
2182
+ this.injectedConfig = this.injector.get(VOICE_MODAL_CONFIG, null);
2183
+ this.onCloseCallback = this.injector.get(VOICE_MODAL_CLOSE_CALLBACK, null);
2184
+ if (this.injectedConfig) {
2185
+ this.apiUrl = this.injectedConfig.apiUrl;
2186
+ this.token = this.injectedConfig.token;
2187
+ this.botId = this.injectedConfig.botId;
2188
+ this.conversationId = this.injectedConfig.conversationId;
2189
+ this.apiKey = (_a = this.injectedConfig.apiKey) !== null && _a !== void 0 ? _a : '';
2190
+ this.eventToken = (_b = this.injectedConfig.eventToken) !== null && _b !== void 0 ? _b : '';
2191
+ this.eventId = (_c = this.injectedConfig.eventId) !== null && _c !== void 0 ? _c : '';
2192
+ this.eventUrl = (_d = this.injectedConfig.eventUrl) !== null && _d !== void 0 ? _d : '';
2193
+ this.domainAuthority = (_e = this.injectedConfig.domainAuthority) !== null && _e !== void 0 ? _e : 'prod-lite';
2194
+ this.agentName = (_f = this.injectedConfig.agentName) !== null && _f !== void 0 ? _f : this.agentName;
2195
+ this.agentRole = (_g = this.injectedConfig.agentRole) !== null && _g !== void 0 ? _g : this.agentRole;
2196
+ this.agentAvatar = this.injectedConfig.agentAvatar;
2197
+ this.usersApiUrl = (_h = this.injectedConfig.usersApiUrl) !== null && _h !== void 0 ? _h : this.usersApiUrl;
2198
+ }
2199
+ // Subscribe to observables
2200
+ this.subscriptions.push(this.voiceAgentService.callState$.subscribe(function (state) {
2201
+ _this.callState = state;
2202
+ _this.isSpeaking = state === 'talking';
2203
+ if (state === 'listening' || state === 'talking') {
2204
+ _this.hasLeftConnectedOnce = true;
2205
+ }
2206
+ if (state === 'idle' || state === 'ended') {
2207
+ _this.hasLeftConnectedOnce = false;
2208
+ }
2209
+ }));
2210
+ this.subscriptions.push(this.voiceAgentService.statusText$.subscribe(function (text) {
2211
+ _this.statusText = text;
2212
+ }));
2213
+ this.subscriptions.push(this.voiceAgentService.duration$.subscribe(function (duration) {
2214
+ _this.duration = duration;
2215
+ }));
2216
+ this.subscriptions.push(this.voiceAgentService.isMicMuted$.subscribe(function (muted) {
2217
+ _this.isMicMuted = muted;
2218
+ }));
2219
+ this.subscriptions.push(this.voiceAgentService.isUserSpeaking$.subscribe(function (speaking) {
2220
+ _this.isUserSpeaking = speaking;
2221
+ }));
2222
+ this.subscriptions.push(this.voiceAgentService.audioLevels$.subscribe(function (levels) {
2223
+ _this.audioLevels = levels;
2224
+ }));
2225
+ this.voiceAgentService.resetToIdle();
2226
+ return [4 /*yield*/, this.startCall()];
2227
+ case 1:
2228
+ _j.sent();
2229
+ return [2 /*return*/];
2230
+ }
2231
+ });
2232
+ });
2016
2233
  };
2017
2234
  VoiceAgentModalComponent.prototype.ngOnDestroy = function () {
2235
+ var _this = this;
2018
2236
  this.subscriptions.forEach(function (sub) { return sub.unsubscribe(); });
2019
- this.disconnect();
2237
+ void this.disconnect().finally(function () {
2238
+ var _a;
2239
+ (_a = _this.warmMicStream) === null || _a === void 0 ? void 0 : _a.getTracks().forEach(function (t) { return t.stop(); });
2240
+ _this.warmMicStream = null;
2241
+ });
2242
+ };
2243
+ /** Ensures a live mic stream for this call (re-acquire after Daily stops tracks on hang-up). */
2244
+ VoiceAgentModalComponent.prototype.ensureMicForCall = function () {
2245
+ var _a;
2246
+ return __awaiter(this, void 0, void 0, function () {
2247
+ var _j, _b_1;
2248
+ return __generator(this, function (_k) {
2249
+ switch (_k.label) {
2250
+ case 0:
2251
+ if (!common.isPlatformBrowser(this.platformId))
2252
+ return [2 /*return*/, undefined];
2253
+ if ((_a = this.warmMicStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks().some(function (t) { return t.readyState === 'live'; })) {
2254
+ return [2 /*return*/, this.warmMicStream];
2255
+ }
2256
+ _k.label = 1;
2257
+ case 1:
2258
+ _k.trys.push([1, 3, , 4]);
2259
+ _j = this;
2260
+ return [4 /*yield*/, navigator.mediaDevices.getUserMedia({ audio: true })];
2261
+ case 2:
2262
+ _j.warmMicStream = _k.sent();
2263
+ return [2 /*return*/, this.warmMicStream];
2264
+ case 3:
2265
+ _b_1 = _k.sent();
2266
+ return [2 /*return*/, undefined];
2267
+ case 4: return [2 /*return*/];
2268
+ }
2269
+ });
2270
+ });
2020
2271
  };
2021
2272
  VoiceAgentModalComponent.prototype.startCall = function () {
2022
2273
  return __awaiter(this, void 0, void 0, function () {
2023
- var error_1;
2274
+ var mic, error_1;
2024
2275
  return __generator(this, function (_j) {
2025
2276
  switch (_j.label) {
2026
2277
  case 0:
@@ -2029,19 +2280,22 @@
2029
2280
  this.isConnecting = true;
2030
2281
  _j.label = 1;
2031
2282
  case 1:
2032
- _j.trys.push([1, 3, 4, 5]);
2033
- return [4 /*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)];
2283
+ _j.trys.push([1, 4, 5, 6]);
2284
+ return [4 /*yield*/, this.ensureMicForCall()];
2034
2285
  case 2:
2035
- _j.sent();
2036
- return [3 /*break*/, 5];
2286
+ mic = _j.sent();
2287
+ return [4 /*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)];
2037
2288
  case 3:
2289
+ _j.sent();
2290
+ return [3 /*break*/, 6];
2291
+ case 4:
2038
2292
  error_1 = _j.sent();
2039
2293
  console.error('Failed to connect voice agent:', error_1);
2040
- return [3 /*break*/, 5];
2041
- case 4:
2294
+ return [3 /*break*/, 6];
2295
+ case 5:
2042
2296
  this.isConnecting = false;
2043
2297
  return [7 /*endfinally*/];
2044
- case 5: return [2 /*return*/];
2298
+ case 6: return [2 /*return*/];
2045
2299
  }
2046
2300
  });
2047
2301
  });
@@ -2086,7 +2340,7 @@
2086
2340
  /** Call Again: reset to idle then start a new call. */
2087
2341
  VoiceAgentModalComponent.prototype.callAgain = function () {
2088
2342
  this.voiceAgentService.resetToIdle();
2089
- this.startCall();
2343
+ void this.startCall();
2090
2344
  };
2091
2345
  /** Back to Chat: close modal and disconnect. */
2092
2346
  VoiceAgentModalComponent.prototype.backToChat = function () {
@@ -2114,7 +2368,8 @@
2114
2368
  VoiceAgentModalComponent.ctorParameters = function () { return [
2115
2369
  { type: VoiceAgentService },
2116
2370
  { type: AudioAnalyzerService },
2117
- { type: i0.Injector }
2371
+ { type: i0.Injector },
2372
+ { type: Object, decorators: [{ type: i0.Inject, args: [i0.PLATFORM_ID,] }] }
2118
2373
  ]; };
2119
2374
  VoiceAgentModalComponent.propDecorators = {
2120
2375
  close: [{ type: i0.Output }],
@@ -5600,7 +5855,7 @@
5600
5855
  };
5601
5856
  ChatDrawerComponent.prototype.openVoiceModal = function () {
5602
5857
  var _this = this;
5603
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
5858
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
5604
5859
  var conversationId = (_b = (_a = this.conversationKey) !== null && _a !== void 0 ? _a : this.conversationService.getKey(this.botId, false, this.eventId)) !== null && _b !== void 0 ? _b : '';
5605
5860
  this.voiceModalConversationId = conversationId;
5606
5861
  this.setupVoiceTranscripts();
@@ -5617,13 +5872,13 @@
5617
5872
  conversationId: conversationId,
5618
5873
  apiKey: (_g = this.apiKey) !== null && _g !== void 0 ? _g : '',
5619
5874
  eventToken: (_h = this.eventToken) !== null && _h !== void 0 ? _h : '',
5620
- eventId: (_j = this.eventId) !== null && _j !== void 0 ? _j : '',
5621
- eventUrl: (_k = this.eventUrl) !== null && _k !== void 0 ? _k : '',
5622
- domainAuthority: (_l = this.domainAuthorityValue) !== null && _l !== void 0 ? _l : 'prod-lite',
5875
+ eventId: this.eventId,
5876
+ eventUrl: (_j = this.eventUrl) !== null && _j !== void 0 ? _j : '',
5877
+ domainAuthority: (_k = this.domainAuthorityValue) !== null && _k !== void 0 ? _k : 'prod-lite',
5623
5878
  agentName: this.botName || 'AI Assistant',
5624
5879
  agentRole: this.botSkills || 'AI Agent Specialist',
5625
5880
  agentAvatar: this.botIcon,
5626
- usersApiUrl: (_o = (_m = this.environment) === null || _m === void 0 ? void 0 : _m.USERS_API) !== null && _o !== void 0 ? _o : '',
5881
+ usersApiUrl: (_m = (_l = this.environment) === null || _l === void 0 ? void 0 : _l.USERS_API) !== null && _m !== void 0 ? _m : '',
5627
5882
  };
5628
5883
  var closeCallback = function () {
5629
5884
  if (_this.voiceModalOverlayRef) {