@hivegpt/hiveai-angular 0.0.574 → 0.0.576

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