@hivegpt/hiveai-angular 0.0.581 → 0.0.582

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 (25) hide show
  1. package/bundles/hivegpt-hiveai-angular.umd.js +285 -503
  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/hivegpt-hiveai-angular.js +4 -5
  6. package/esm2015/lib/components/voice-agent/services/audio-analyzer.service.js +3 -3
  7. package/esm2015/lib/components/voice-agent/services/voice-agent.service.js +118 -85
  8. package/esm2015/lib/components/voice-agent/services/websocket-voice-client.service.js +114 -46
  9. package/esm2015/lib/components/voice-agent/voice-agent.module.js +3 -5
  10. package/fesm2015/hivegpt-hiveai-angular.js +223 -422
  11. package/fesm2015/hivegpt-hiveai-angular.js.map +1 -1
  12. package/hivegpt-hiveai-angular.d.ts +3 -4
  13. package/hivegpt-hiveai-angular.d.ts.map +1 -1
  14. package/hivegpt-hiveai-angular.metadata.json +1 -1
  15. package/lib/components/voice-agent/services/audio-analyzer.service.d.ts +2 -2
  16. package/lib/components/voice-agent/services/voice-agent.service.d.ts +11 -13
  17. package/lib/components/voice-agent/services/voice-agent.service.d.ts.map +1 -1
  18. package/lib/components/voice-agent/services/websocket-voice-client.service.d.ts +23 -20
  19. package/lib/components/voice-agent/services/websocket-voice-client.service.d.ts.map +1 -1
  20. package/lib/components/voice-agent/voice-agent.module.d.ts +1 -1
  21. package/lib/components/voice-agent/voice-agent.module.d.ts.map +1 -1
  22. package/package.json +1 -1
  23. package/esm2015/lib/components/voice-agent/services/daily-voice-client.service.js +0 -305
  24. package/lib/components/voice-agent/services/daily-voice-client.service.d.ts +0 -62
  25. package/lib/components/voice-agent/services/daily-voice-client.service.d.ts.map +0 -1
@@ -1,10 +1,8 @@
1
1
  (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/cdk/overlay'), require('@angular/cdk/portal'), require('@angular/common/http'), require('@angular/core'), require('@angular/platform-browser'), require('rxjs'), require('rxjs/operators'), require('@angular/common'), require('ngx-socket-io'), require('@angular/forms'), require('microsoft-cognitiveservices-speech-sdk'), require('marked'), require('@daily-co/daily-js'), require('@angular/material/icon'), require('@angular/material/sidenav'), require('ngx-quill')) :
3
- typeof define === 'function' && define.amd ? define('@hivegpt/hiveai-angular', ['exports', '@angular/cdk/overlay', '@angular/cdk/portal', '@angular/common/http', '@angular/core', '@angular/platform-browser', 'rxjs', 'rxjs/operators', '@angular/common', 'ngx-socket-io', '@angular/forms', 'microsoft-cognitiveservices-speech-sdk', 'marked', '@daily-co/daily-js', '@angular/material/icon', '@angular/material/sidenav', 'ngx-quill'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.hivegpt = global.hivegpt || {}, global.hivegpt["hiveai-angular"] = {}), global.ng.cdk.overlay, global.ng.cdk.portal, global.ng.common.http, global.ng.core, global.ng.platformBrowser, global.rxjs, global.rxjs.operators, global.ng.common, global.ngxSocketIo, global.ng.forms, global.SpeechSDK, global.marked, global.Daily, global.ng.material.icon, global.ng.material.sidenav, global.ngxQuill));
5
- })(this, (function (exports, overlay, portal, i1, i0, platformBrowser, rxjs, operators, common, ngxSocketIo, forms, SpeechSDK, marked, Daily, icon, sidenav, ngxQuill) { 'use strict';
6
-
7
- function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/cdk/overlay'), require('@angular/cdk/portal'), require('@angular/common/http'), require('@angular/core'), require('@angular/platform-browser'), require('rxjs'), require('rxjs/operators'), require('@angular/common'), require('ngx-socket-io'), require('@angular/forms'), require('microsoft-cognitiveservices-speech-sdk'), require('marked'), require('@angular/material/icon'), require('@angular/material/sidenav'), require('ngx-quill')) :
3
+ typeof define === 'function' && define.amd ? define('@hivegpt/hiveai-angular', ['exports', '@angular/cdk/overlay', '@angular/cdk/portal', '@angular/common/http', '@angular/core', '@angular/platform-browser', 'rxjs', 'rxjs/operators', '@angular/common', 'ngx-socket-io', '@angular/forms', 'microsoft-cognitiveservices-speech-sdk', 'marked', '@angular/material/icon', '@angular/material/sidenav', 'ngx-quill'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.hivegpt = global.hivegpt || {}, global.hivegpt["hiveai-angular"] = {}), global.ng.cdk.overlay, global.ng.cdk.portal, global.ng.common.http, global.ng.core, global.ng.platformBrowser, global.rxjs, global.rxjs.operators, global.ng.common, global.ngxSocketIo, global.ng.forms, global.SpeechSDK, global.marked, global.ng.material.icon, global.ng.material.sidenav, global.ngxQuill));
5
+ })(this, (function (exports, overlay, portal, i1, i0, platformBrowser, rxjs, operators, common, ngxSocketIo, forms, SpeechSDK, marked, icon, sidenav, ngxQuill) { 'use strict';
8
6
 
9
7
  function _interopNamespace(e) {
10
8
  if (e && e.__esModule) return e;
@@ -28,7 +26,6 @@
28
26
  var i0__namespace = /*#__PURE__*/_interopNamespace(i0);
29
27
  var SpeechSDK__namespace = /*#__PURE__*/_interopNamespace(SpeechSDK);
30
28
  var marked__namespace = /*#__PURE__*/_interopNamespace(marked);
31
- var Daily__default = /*#__PURE__*/_interopDefaultLegacy(Daily);
32
29
 
33
30
  /******************************************************************************
34
31
  Copyright (c) Microsoft Corporation.
@@ -1218,8 +1215,8 @@
1218
1215
  ]; };
1219
1216
 
1220
1217
  /**
1221
- * Audio analyzer for waveform visualization only.
1222
- * Do NOT use isUserSpeaking$ for call state; speaking state must come from Daily.js.
1218
+ * Audio analyzer for waveform visualization and local (mic) speaking detection.
1219
+ * VoiceAgentService may combine this with WebSocket server events for call state.
1223
1220
  */
1224
1221
  var AudioAnalyzerService = /** @class */ (function () {
1225
1222
  function AudioAnalyzerService() {
@@ -1342,29 +1339,35 @@
1342
1339
  ];
1343
1340
 
1344
1341
  /**
1345
- * WebSocket-only client for voice agent signaling.
1342
+ * Native WebSocket client for voice session (signaling, transcripts, speaking hints).
1346
1343
  * CRITICAL: Uses native WebSocket only. NO Socket.IO, NO ngx-socket-io.
1347
1344
  *
1348
- * Responsibilities:
1349
- * - Connect to ws_url (from POST /ai/ask-voice response)
1350
- * - Parse JSON messages (room_created, user_transcript, bot_transcript)
1351
- * - Emit roomCreated$, userTranscript$, botTranscript$
1352
- * - NO audio logic, NO mic logic. Audio is handled by Daily.js (WebRTC).
1345
+ * Connects to `ws_url` from `POST {baseUrl}/ai/ask-voice-socket`.
1346
+ * Parses JSON messages for transcripts and optional assistant/user speaking flags.
1353
1347
  */
1354
1348
  var WebSocketVoiceClientService = /** @class */ (function () {
1355
- function WebSocketVoiceClientService() {
1349
+ function WebSocketVoiceClientService(ngZone) {
1350
+ this.ngZone = ngZone;
1356
1351
  this.ws = null;
1357
- this.roomCreatedSubject = new rxjs.Subject();
1352
+ /** True when {@link disconnect} initiated the close (not counted as remote close). */
1353
+ this.closeInitiatedByClient = false;
1354
+ this.openedSubject = new rxjs.Subject();
1355
+ this.remoteCloseSubject = new rxjs.Subject();
1358
1356
  this.userTranscriptSubject = new rxjs.Subject();
1359
1357
  this.botTranscriptSubject = new rxjs.Subject();
1360
- /** Emits room_url when backend sends room_created. */
1361
- this.roomCreated$ = this.roomCreatedSubject.asObservable();
1362
- /** Emits user transcript updates. */
1358
+ this.assistantSpeakingSubject = new rxjs.Subject();
1359
+ this.serverUserSpeakingSubject = new rxjs.Subject();
1360
+ /** Fires once each time the WebSocket reaches OPEN. */
1361
+ this.opened$ = this.openedSubject.asObservable();
1362
+ /** Fires when the socket closes without a client-initiated {@link disconnect}. */
1363
+ this.remoteClose$ = this.remoteCloseSubject.asObservable();
1363
1364
  this.userTranscript$ = this.userTranscriptSubject.asObservable();
1364
- /** Emits bot transcript updates. */
1365
1365
  this.botTranscript$ = this.botTranscriptSubject.asObservable();
1366
+ /** Assistant/bot speaking, when the server sends explicit events (see {@link handleJsonMessage}). */
1367
+ this.assistantSpeaking$ = this.assistantSpeakingSubject.asObservable();
1368
+ /** User speaking from server-side VAD, if provided. */
1369
+ this.serverUserSpeaking$ = this.serverUserSpeakingSubject.asObservable();
1366
1370
  }
1367
- /** Connect to signaling WebSocket. No audio over this connection. */
1368
1371
  WebSocketVoiceClientService.prototype.connect = function (wsUrl) {
1369
1372
  var _this = this;
1370
1373
  var _a;
@@ -1372,40 +1375,47 @@
1372
1375
  return;
1373
1376
  }
1374
1377
  if (this.ws) {
1378
+ this.closeInitiatedByClient = true;
1375
1379
  this.ws.close();
1376
- this.ws = null;
1377
1380
  }
1378
1381
  try {
1379
- this.ws = new WebSocket(wsUrl);
1380
- this.ws.onmessage = function (event) {
1381
- var _a;
1382
+ var socket_1 = new WebSocket(wsUrl);
1383
+ this.ws = socket_1;
1384
+ socket_1.onopen = function () {
1385
+ if (_this.ws !== socket_1)
1386
+ return;
1387
+ _this.ngZone.run(function () { return _this.openedSubject.next(); });
1388
+ };
1389
+ socket_1.onmessage = function (event) {
1390
+ if (_this.ws !== socket_1)
1391
+ return;
1392
+ if (typeof event.data !== 'string') {
1393
+ return;
1394
+ }
1382
1395
  try {
1383
- var msg = JSON.parse(event.data);
1384
- if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'room_created') {
1385
- var roomUrl = ((_a = msg.room_url) !== null && _a !== void 0 ? _a : msg.roomUrl);
1386
- if (typeof roomUrl === 'string') {
1387
- _this.roomCreatedSubject.next(roomUrl);
1388
- }
1389
- }
1390
- else if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'user_transcript' && typeof msg.text === 'string') {
1391
- _this.userTranscriptSubject.next({
1392
- text: msg.text,
1393
- final: msg.final === true,
1394
- });
1395
- }
1396
- else if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'bot_transcript' && typeof msg.text === 'string') {
1397
- _this.botTranscriptSubject.next(msg.text);
1398
- }
1396
+ var msg_1 = JSON.parse(event.data);
1397
+ _this.ngZone.run(function () { return _this.handleJsonMessage(msg_1); });
1399
1398
  }
1400
- catch (_b) {
1401
- // Ignore non-JSON or unknown messages
1399
+ catch (_a) {
1400
+ // Ignore non-JSON
1402
1401
  }
1403
1402
  };
1404
- this.ws.onerror = function () {
1405
- _this.disconnect();
1403
+ socket_1.onerror = function () {
1404
+ _this.ngZone.run(function () {
1405
+ if (_this.ws === socket_1 && socket_1.readyState !== WebSocket.CLOSED) {
1406
+ socket_1.close();
1407
+ }
1408
+ });
1406
1409
  };
1407
- this.ws.onclose = function () {
1408
- _this.ws = null;
1410
+ socket_1.onclose = function () {
1411
+ if (_this.ws === socket_1) {
1412
+ _this.ws = null;
1413
+ }
1414
+ var client = _this.closeInitiatedByClient;
1415
+ _this.closeInitiatedByClient = false;
1416
+ if (!client) {
1417
+ _this.ngZone.run(function () { return _this.remoteCloseSubject.next(); });
1418
+ }
1409
1419
  };
1410
1420
  }
1411
1421
  catch (err) {
@@ -1414,374 +1424,98 @@
1414
1424
  throw err;
1415
1425
  }
1416
1426
  };
1417
- /** Disconnect and cleanup. */
1418
- WebSocketVoiceClientService.prototype.disconnect = function () {
1419
- if (this.ws) {
1420
- this.ws.close();
1421
- this.ws = null;
1427
+ WebSocketVoiceClientService.prototype.handleJsonMessage = function (msg) {
1428
+ var type = msg.type;
1429
+ var typeStr = typeof type === 'string' ? type : '';
1430
+ if (typeStr === 'session_ready' || typeStr === 'connected' || typeStr === 'voice_session_started') {
1431
+ return;
1422
1432
  }
1423
- };
1424
- Object.defineProperty(WebSocketVoiceClientService.prototype, "isConnected", {
1425
- /** Whether the WebSocket is open. */
1426
- get: function () {
1427
- var _a;
1428
- return ((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN;
1429
- },
1430
- enumerable: false,
1431
- configurable: true
1432
- });
1433
- return WebSocketVoiceClientService;
1434
- }());
1435
- WebSocketVoiceClientService.ɵprov = i0__namespace.ɵɵdefineInjectable({ factory: function WebSocketVoiceClientService_Factory() { return new WebSocketVoiceClientService(); }, token: WebSocketVoiceClientService, providedIn: "root" });
1436
- WebSocketVoiceClientService.decorators = [
1437
- { type: i0.Injectable, args: [{
1438
- providedIn: 'root',
1439
- },] }
1440
- ];
1441
-
1442
- /**
1443
- * Daily.js WebRTC client for voice agent audio.
1444
- * Responsibilities:
1445
- * - Create and manage Daily CallObject
1446
- * - Join Daily room using room_url
1447
- * - Handle mic capture + speaker playback
1448
- * - Bot speaking detection via AnalyserNode on remote track (instant)
1449
- * - User speaking detection via active-speaker-change
1450
- * - Expose speaking$ (bot speaking), userSpeaking$ (user speaking), micMuted$
1451
- * - Expose localStream$ for waveform visualization (AudioAnalyzerService)
1452
- */
1453
- var DailyVoiceClientService = /** @class */ (function () {
1454
- function DailyVoiceClientService(ngZone) {
1455
- this.ngZone = ngZone;
1456
- this.callObject = null;
1457
- this.localStream = null;
1458
- this.localSessionId = null;
1459
- /** Explicit playback of remote (bot) audio; required in some browsers. */
1460
- this.remoteAudioElement = null;
1461
- /** AnalyserNode-based remote audio monitor for instant bot speaking detection. */
1462
- this.remoteAudioContext = null;
1463
- this.remoteSpeakingRAF = null;
1464
- this.speakingSubject = new rxjs.BehaviorSubject(false);
1465
- this.userSpeakingSubject = new rxjs.BehaviorSubject(false);
1466
- this.micMutedSubject = new rxjs.BehaviorSubject(false);
1467
- this.localStreamSubject = new rxjs.BehaviorSubject(null);
1468
- /** True when bot (remote participant) is the active speaker. */
1469
- this.speaking$ = this.speakingSubject.asObservable();
1470
- /** True when user (local participant) is the active speaker. */
1471
- this.userSpeaking$ = this.userSpeakingSubject.asObservable();
1472
- /** True when mic is muted. */
1473
- this.micMuted$ = this.micMutedSubject.asObservable();
1474
- /** Emits local mic stream for waveform visualization. */
1475
- this.localStream$ = this.localStreamSubject.asObservable();
1476
- }
1477
- /**
1478
- * Connect to Daily room. Acquires mic first for waveform, then joins with audio.
1479
- * @param roomUrl Daily room URL (from room_created)
1480
- * @param token Optional meeting token
1481
- */
1482
- DailyVoiceClientService.prototype.connect = function (roomUrl, token) {
1483
- return __awaiter(this, void 0, void 0, function () {
1484
- var stream, audioTrack, callObject, joinOptions, participants, err_1;
1485
- return __generator(this, function (_e) {
1486
- switch (_e.label) {
1487
- case 0:
1488
- if (!this.callObject) return [3 /*break*/, 2];
1489
- return [4 /*yield*/, this.disconnect()];
1490
- case 1:
1491
- _e.sent();
1492
- _e.label = 2;
1493
- case 2:
1494
- _e.trys.push([2, 5, , 6]);
1495
- return [4 /*yield*/, navigator.mediaDevices.getUserMedia({ audio: true })];
1496
- case 3:
1497
- stream = _e.sent();
1498
- audioTrack = stream.getAudioTracks()[0];
1499
- if (!audioTrack) {
1500
- stream.getTracks().forEach(function (t) { return t.stop(); });
1501
- throw new Error('No audio track');
1502
- }
1503
- this.localStream = stream;
1504
- this.localStreamSubject.next(stream);
1505
- callObject = Daily__default["default"].createCallObject({
1506
- videoSource: false,
1507
- audioSource: audioTrack,
1508
- });
1509
- this.callObject = callObject;
1510
- this.setupEventHandlers(callObject);
1511
- joinOptions = { url: roomUrl };
1512
- if (typeof token === 'string' && token.trim() !== '') {
1513
- joinOptions.token = token;
1514
- }
1515
- return [4 /*yield*/, callObject.join(joinOptions)];
1516
- case 4:
1517
- _e.sent();
1518
- console.log("[VoiceDebug] Room connected (Daily join complete) \u2014 " + new Date().toISOString());
1519
- participants = callObject.participants();
1520
- if (participants === null || participants === void 0 ? void 0 : participants.local) {
1521
- this.localSessionId = participants.local.session_id;
1522
- }
1523
- // Initial mute state: Daily starts with audio on
1524
- this.micMutedSubject.next(!callObject.localAudio());
1525
- return [3 /*break*/, 6];
1526
- case 5:
1527
- err_1 = _e.sent();
1528
- this.cleanup();
1529
- throw err_1;
1530
- case 6: return [2 /*return*/];
1531
- }
1532
- });
1533
- });
1534
- };
1535
- DailyVoiceClientService.prototype.setupEventHandlers = function (call) {
1536
- var _this = this;
1537
- // active-speaker-change: used ONLY for user speaking detection.
1538
- // Bot speaking is detected by our own AnalyserNode (instant, no debounce).
1539
- call.on('active-speaker-change', function (event) {
1540
- _this.ngZone.run(function () {
1541
- var _a;
1542
- var peerId = (_a = event === null || event === void 0 ? void 0 : event.activeSpeaker) === null || _a === void 0 ? void 0 : _a.peerId;
1543
- if (!peerId || !_this.localSessionId) {
1544
- _this.userSpeakingSubject.next(false);
1545
- return;
1546
- }
1547
- var isLocal = peerId === _this.localSessionId;
1548
- _this.userSpeakingSubject.next(isLocal);
1549
- });
1550
- });
1551
- // track-started / track-stopped: set up remote audio playback + AnalyserNode monitor.
1552
- call.on('track-started', function (event) {
1553
- _this.ngZone.run(function () {
1554
- var _a, _b, _c, _d;
1555
- var p = event === null || event === void 0 ? void 0 : event.participant;
1556
- 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;
1557
- var track = event === null || event === void 0 ? void 0 : event.track;
1558
- if (p && !p.local && type === 'audio') {
1559
- 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());
1560
- 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;
1561
- if (audioTrack && typeof audioTrack === 'object') {
1562
- _this.playRemoteTrack(audioTrack);
1563
- _this.monitorRemoteAudio(audioTrack);
1564
- }
1565
- }
1566
- });
1567
- });
1568
- call.on('track-stopped', function (event) {
1569
- _this.ngZone.run(function () {
1570
- var _a, _b;
1571
- var p = event === null || event === void 0 ? void 0 : event.participant;
1572
- 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;
1573
- if (p && !p.local && type === 'audio') {
1574
- _this.stopRemoteAudioMonitor();
1575
- _this.stopRemoteAudio();
1576
- }
1577
- });
1578
- });
1579
- call.on('left-meeting', function () {
1580
- _this.ngZone.run(function () { return _this.cleanup(); });
1581
- });
1582
- call.on('error', function (event) {
1583
- _this.ngZone.run(function () {
1584
- var _a;
1585
- console.error('DailyVoiceClient: Daily error', (_a = event === null || event === void 0 ? void 0 : event.errorMsg) !== null && _a !== void 0 ? _a : event);
1586
- _this.cleanup();
1587
- });
1588
- });
1589
- };
1590
- /**
1591
- * Play remote (bot) audio track via a dedicated audio element.
1592
- * Required in many browsers where Daily's internal playback does not output to speakers.
1593
- */
1594
- DailyVoiceClientService.prototype.playRemoteTrack = function (track) {
1595
- this.stopRemoteAudio();
1596
- try {
1597
- console.log("[VoiceDebug] playRemoteTrack called \u2014 track.readyState=" + track.readyState + ", track.muted=" + track.muted + " \u2014 " + new Date().toISOString());
1598
- track.onunmute = function () {
1599
- console.log("[VoiceDebug] Remote audio track UNMUTED (audio data arriving) \u2014 " + new Date().toISOString());
1600
- };
1601
- var stream = new MediaStream([track]);
1602
- var audio = new Audio();
1603
- audio.autoplay = true;
1604
- audio.srcObject = stream;
1605
- this.remoteAudioElement = audio;
1606
- audio.onplaying = function () {
1607
- console.log("[VoiceDebug] Audio element PLAYING (browser started playback) \u2014 " + new Date().toISOString());
1608
- };
1609
- var firstTimeUpdate_1 = true;
1610
- audio.ontimeupdate = function () {
1611
- if (firstTimeUpdate_1) {
1612
- firstTimeUpdate_1 = false;
1613
- console.log("[VoiceDebug] Audio element first TIMEUPDATE (actual audio output) \u2014 " + new Date().toISOString());
1614
- }
1615
- };
1616
- var p = audio.play();
1617
- if (p && typeof p.then === 'function') {
1618
- p.then(function () {
1619
- console.log("[VoiceDebug] audio.play() resolved \u2014 " + new Date().toISOString());
1620
- }).catch(function (err) {
1621
- console.warn('DailyVoiceClient: remote audio play failed (may need user gesture)', err);
1622
- });
1433
+ if (typeStr === 'assistant_speaking' ||
1434
+ typeStr === 'bot_speaking') {
1435
+ if (msg.active === true || msg.speaking === true) {
1436
+ this.assistantSpeakingSubject.next(true);
1623
1437
  }
1438
+ else if (msg.active === false || msg.speaking === false) {
1439
+ this.assistantSpeakingSubject.next(false);
1440
+ }
1441
+ return;
1624
1442
  }
1625
- catch (err) {
1626
- console.warn('DailyVoiceClient: failed to create remote audio element', err);
1443
+ if (typeStr === 'user_speaking') {
1444
+ if (msg.active === true || msg.speaking === true) {
1445
+ this.serverUserSpeakingSubject.next(true);
1446
+ }
1447
+ else if (msg.active === false || msg.speaking === false) {
1448
+ this.serverUserSpeakingSubject.next(false);
1449
+ }
1450
+ return;
1627
1451
  }
1628
- };
1629
- /**
1630
- * Monitor remote audio track energy via AnalyserNode.
1631
- * Polls at ~60fps and flips speakingSubject based on actual audio energy.
1632
- */
1633
- DailyVoiceClientService.prototype.monitorRemoteAudio = function (track) {
1634
- var _this = this;
1635
- this.stopRemoteAudioMonitor();
1636
- try {
1637
- var ctx = new AudioContext();
1638
- var source = ctx.createMediaStreamSource(new MediaStream([track]));
1639
- var analyser_1 = ctx.createAnalyser();
1640
- analyser_1.fftSize = 256;
1641
- source.connect(analyser_1);
1642
- this.remoteAudioContext = ctx;
1643
- var dataArray_1 = new Uint8Array(analyser_1.frequencyBinCount);
1644
- var THRESHOLD_1 = 5;
1645
- var SILENCE_MS_1 = 1500;
1646
- var lastSoundTime_1 = 0;
1647
- var isSpeaking_1 = false;
1648
- var poll_1 = function () {
1649
- if (!_this.remoteAudioContext)
1650
- return;
1651
- analyser_1.getByteFrequencyData(dataArray_1);
1652
- var sum = 0;
1653
- for (var i = 0; i < dataArray_1.length; i++) {
1654
- sum += dataArray_1[i];
1655
- }
1656
- var avg = sum / dataArray_1.length;
1657
- var now = Date.now();
1658
- if (avg > THRESHOLD_1) {
1659
- lastSoundTime_1 = now;
1660
- if (!isSpeaking_1) {
1661
- isSpeaking_1 = true;
1662
- console.log("[VoiceDebug] Bot audio energy detected (speaking=true) \u2014 avg=" + avg.toFixed(1) + " \u2014 " + new Date().toISOString());
1663
- _this.ngZone.run(function () {
1664
- _this.userSpeakingSubject.next(false);
1665
- _this.speakingSubject.next(true);
1666
- });
1667
- }
1668
- }
1669
- else if (isSpeaking_1 && now - lastSoundTime_1 > SILENCE_MS_1) {
1670
- isSpeaking_1 = false;
1671
- console.log("[VoiceDebug] Bot audio silence detected (speaking=false) \u2014 " + new Date().toISOString());
1672
- _this.ngZone.run(function () { return _this.speakingSubject.next(false); });
1673
- }
1674
- _this.remoteSpeakingRAF = requestAnimationFrame(poll_1);
1675
- };
1676
- this.remoteSpeakingRAF = requestAnimationFrame(poll_1);
1452
+ if (typeStr === 'input_audio_buffer.speech_started') {
1453
+ this.serverUserSpeakingSubject.next(true);
1454
+ return;
1677
1455
  }
1678
- catch (err) {
1679
- console.warn('DailyVoiceClient: failed to create remote audio monitor', err);
1456
+ if (typeStr === 'input_audio_buffer.speech_stopped') {
1457
+ this.serverUserSpeakingSubject.next(false);
1458
+ return;
1680
1459
  }
1681
- };
1682
- DailyVoiceClientService.prototype.stopRemoteAudioMonitor = function () {
1683
- if (this.remoteSpeakingRAF) {
1684
- cancelAnimationFrame(this.remoteSpeakingRAF);
1685
- this.remoteSpeakingRAF = null;
1460
+ if (typeStr === 'response.audio.delta') {
1461
+ this.assistantSpeakingSubject.next(true);
1462
+ return;
1686
1463
  }
1687
- if (this.remoteAudioContext) {
1688
- this.remoteAudioContext.close().catch(function () { });
1689
- this.remoteAudioContext = null;
1464
+ if (typeStr === 'response.audio.done' ||
1465
+ typeStr === 'response.output_audio.done') {
1466
+ this.assistantSpeakingSubject.next(false);
1467
+ return;
1690
1468
  }
1691
- };
1692
- DailyVoiceClientService.prototype.stopRemoteAudio = function () {
1693
- if (this.remoteAudioElement) {
1694
- try {
1695
- this.remoteAudioElement.pause();
1696
- this.remoteAudioElement.srcObject = null;
1697
- }
1698
- catch (_) { }
1699
- this.remoteAudioElement = null;
1469
+ if (typeStr === 'user_transcript' && typeof msg.text === 'string') {
1470
+ this.userTranscriptSubject.next({
1471
+ text: msg.text,
1472
+ final: msg.final === true,
1473
+ });
1474
+ return;
1475
+ }
1476
+ if (typeStr === 'bot_transcript' && typeof msg.text === 'string') {
1477
+ this.botTranscriptSubject.next(msg.text);
1700
1478
  }
1701
1479
  };
1702
- /** Set mic muted state. */
1703
- DailyVoiceClientService.prototype.setMuted = function (muted) {
1704
- if (!this.callObject)
1480
+ WebSocketVoiceClientService.prototype.disconnect = function () {
1481
+ if (!this.ws) {
1705
1482
  return;
1706
- this.callObject.setLocalAudio(!muted);
1707
- this.micMutedSubject.next(muted);
1708
- };
1709
- /** Disconnect and cleanup. */
1710
- DailyVoiceClientService.prototype.disconnect = function () {
1711
- return __awaiter(this, void 0, void 0, function () {
1712
- var e_1;
1713
- return __generator(this, function (_e) {
1714
- switch (_e.label) {
1715
- case 0:
1716
- if (!this.callObject) {
1717
- this.cleanup();
1718
- return [2 /*return*/];
1719
- }
1720
- _e.label = 1;
1721
- case 1:
1722
- _e.trys.push([1, 3, , 4]);
1723
- return [4 /*yield*/, this.callObject.leave()];
1724
- case 2:
1725
- _e.sent();
1726
- return [3 /*break*/, 4];
1727
- case 3:
1728
- e_1 = _e.sent();
1729
- return [3 /*break*/, 4];
1730
- case 4:
1731
- this.cleanup();
1732
- return [2 /*return*/];
1733
- }
1734
- });
1735
- });
1736
- };
1737
- DailyVoiceClientService.prototype.cleanup = function () {
1738
- this.stopRemoteAudioMonitor();
1739
- this.stopRemoteAudio();
1740
- if (this.callObject) {
1741
- this.callObject.destroy().catch(function () { });
1742
- this.callObject = null;
1743
- }
1744
- if (this.localStream) {
1745
- this.localStream.getTracks().forEach(function (t) { return t.stop(); });
1746
- this.localStream = null;
1747
1483
  }
1748
- this.localSessionId = null;
1749
- this.speakingSubject.next(false);
1750
- this.userSpeakingSubject.next(false);
1751
- this.localStreamSubject.next(null);
1752
- // Keep last micMuted state; will reset on next connect
1484
+ this.closeInitiatedByClient = true;
1485
+ this.ws.close();
1753
1486
  };
1754
- return DailyVoiceClientService;
1487
+ Object.defineProperty(WebSocketVoiceClientService.prototype, "isConnected", {
1488
+ get: function () {
1489
+ var _a;
1490
+ return ((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN;
1491
+ },
1492
+ enumerable: false,
1493
+ configurable: true
1494
+ });
1495
+ return WebSocketVoiceClientService;
1755
1496
  }());
1756
- DailyVoiceClientService.ɵprov = i0__namespace.ɵɵdefineInjectable({ factory: function DailyVoiceClientService_Factory() { return new DailyVoiceClientService(i0__namespace.ɵɵinject(i0__namespace.NgZone)); }, token: DailyVoiceClientService, providedIn: "root" });
1757
- DailyVoiceClientService.decorators = [
1497
+ WebSocketVoiceClientService.ɵprov = i0__namespace.ɵɵdefineInjectable({ factory: function WebSocketVoiceClientService_Factory() { return new WebSocketVoiceClientService(i0__namespace.ɵɵinject(i0__namespace.NgZone)); }, token: WebSocketVoiceClientService, providedIn: "root" });
1498
+ WebSocketVoiceClientService.decorators = [
1758
1499
  { type: i0.Injectable, args: [{
1759
1500
  providedIn: 'root',
1760
1501
  },] }
1761
1502
  ];
1762
- DailyVoiceClientService.ctorParameters = function () { return [
1503
+ WebSocketVoiceClientService.ctorParameters = function () { return [
1763
1504
  { type: i0.NgZone }
1764
1505
  ]; };
1765
1506
 
1766
1507
  /**
1767
- * Voice agent orchestrator. Coordinates WebSocket (signaling) and Daily.js (WebRTC audio).
1768
- *
1769
- * CRITICAL: This service must NEVER use Socket.IO or ngx-socket-io. Voice flow uses only:
1770
- * - Native WebSocket (WebSocketVoiceClientService) for signaling (room_created, transcripts)
1771
- * - Daily.js (DailyVoiceClientService) for WebRTC audio. Audio does NOT flow over WebSocket.
1772
- *
1773
- * - Maintains callState, statusText, duration, isMicMuted, isUserSpeaking, audioLevels
1774
- * - Uses WebSocket for room_created and transcripts only (no audio)
1775
- * - Uses Daily.js for all audio, mic, and real-time speaking detection
1508
+ * Voice agent orchestrator: single WebSocket (`ws_url` from POST /ai/ask-voice-socket)
1509
+ * for session events, transcripts, and optional speaking hints; local mic for capture
1510
+ * and waveform only (no Daily/WebRTC room).
1776
1511
  */
1777
1512
  var VoiceAgentService = /** @class */ (function () {
1778
- function VoiceAgentService(audioAnalyzer, wsClient, dailyClient, platformTokenRefresh,
1513
+ function VoiceAgentService(audioAnalyzer, wsClient, platformTokenRefresh,
1779
1514
  /** `Object` not `object` — ngc metadata collection rejects the `object` type in DI params. */
1780
1515
  platformId) {
1781
1516
  var _this = this;
1782
1517
  this.audioAnalyzer = audioAnalyzer;
1783
1518
  this.wsClient = wsClient;
1784
- this.dailyClient = dailyClient;
1785
1519
  this.platformTokenRefresh = platformTokenRefresh;
1786
1520
  this.platformId = platformId;
1787
1521
  this.callStateSubject = new rxjs.BehaviorSubject('idle');
@@ -1794,6 +1528,8 @@
1794
1528
  this.botTranscriptSubject = new rxjs.Subject();
1795
1529
  this.callStartTime = 0;
1796
1530
  this.durationInterval = null;
1531
+ this.localMicStream = null;
1532
+ this.endCall$ = new rxjs.Subject();
1797
1533
  this.subscriptions = new rxjs.Subscription();
1798
1534
  this.destroy$ = new rxjs.Subject();
1799
1535
  this.callState$ = this.callStateSubject.asObservable();
@@ -1804,8 +1540,10 @@
1804
1540
  this.audioLevels$ = this.audioLevelsSubject.asObservable();
1805
1541
  this.userTranscript$ = this.userTranscriptSubject.asObservable();
1806
1542
  this.botTranscript$ = this.botTranscriptSubject.asObservable();
1807
- // Waveform visualization only - do NOT use for speaking state
1808
1543
  this.subscriptions.add(this.audioAnalyzer.audioLevels$.subscribe(function (levels) { return _this.audioLevelsSubject.next(levels); }));
1544
+ this.subscriptions.add(this.wsClient.remoteClose$
1545
+ .pipe(operators.takeUntil(this.destroy$))
1546
+ .subscribe(function () { return void _this.handleRemoteClose(); }));
1809
1547
  }
1810
1548
  VoiceAgentService.prototype.ngOnDestroy = function () {
1811
1549
  this.destroy$.next();
@@ -1816,53 +1554,54 @@
1816
1554
  VoiceAgentService.prototype.resetToIdle = function () {
1817
1555
  if (this.callStateSubject.value === 'idle')
1818
1556
  return;
1557
+ this.endCall$.next();
1819
1558
  this.stopDurationTimer();
1559
+ this.callStartTime = 0;
1820
1560
  this.audioAnalyzer.stop();
1561
+ this.stopLocalMic();
1821
1562
  this.wsClient.disconnect();
1822
- // Fire-and-forget: Daily disconnect is async; connect() will await if needed
1823
- void this.dailyClient.disconnect();
1824
1563
  this.callStateSubject.next('idle');
1825
1564
  this.statusTextSubject.next('');
1826
1565
  this.durationSubject.next('0:00');
1827
1566
  };
1828
1567
  VoiceAgentService.prototype.connect = function (apiUrl, token, botId, conversationId, apiKey, eventToken, eventId, eventUrl, domainAuthority, usersApiUrl) {
1829
1568
  return __awaiter(this, void 0, void 0, function () {
1830
- var accessToken, ensured, e_1, baseUrl, postUrl, headers, res, json, wsUrl, error_1;
1569
+ var accessToken, ensured, e_1, baseUrl, postUrl, headers, res, json, wsUrl, untilCallEnds$, error_1;
1831
1570
  var _this = this;
1832
- return __generator(this, function (_a) {
1833
- switch (_a.label) {
1571
+ return __generator(this, function (_b) {
1572
+ switch (_b.label) {
1834
1573
  case 0:
1835
1574
  if (this.callStateSubject.value !== 'idle') {
1836
1575
  console.warn('Call already in progress');
1837
1576
  return [2 /*return*/];
1838
1577
  }
1839
- _a.label = 1;
1578
+ _b.label = 1;
1840
1579
  case 1:
1841
- _a.trys.push([1, 8, , 10]);
1580
+ _b.trys.push([1, 8, , 10]);
1842
1581
  this.callStateSubject.next('connecting');
1843
1582
  this.statusTextSubject.next('Connecting...');
1844
1583
  accessToken = token;
1845
1584
  if (!(usersApiUrl && common.isPlatformBrowser(this.platformId))) return [3 /*break*/, 5];
1846
- _a.label = 2;
1585
+ _b.label = 2;
1847
1586
  case 2:
1848
- _a.trys.push([2, 4, , 5]);
1587
+ _b.trys.push([2, 4, , 5]);
1849
1588
  return [4 /*yield*/, this.platformTokenRefresh
1850
1589
  .ensureValidAccessToken(token, usersApiUrl)
1851
1590
  .pipe(operators.take(1))
1852
1591
  .toPromise()];
1853
1592
  case 3:
1854
- ensured = _a.sent();
1593
+ ensured = _b.sent();
1855
1594
  if (ensured === null || ensured === void 0 ? void 0 : ensured.accessToken) {
1856
1595
  accessToken = ensured.accessToken;
1857
1596
  }
1858
1597
  return [3 /*break*/, 5];
1859
1598
  case 4:
1860
- e_1 = _a.sent();
1599
+ e_1 = _b.sent();
1861
1600
  console.warn('[HiveGpt Voice] Token refresh before connect failed', e_1);
1862
1601
  return [3 /*break*/, 5];
1863
1602
  case 5:
1864
1603
  baseUrl = apiUrl.replace(/\/$/, '');
1865
- postUrl = baseUrl + "/ai/ask-voice";
1604
+ postUrl = baseUrl + "/ai/ask-voice-socket";
1866
1605
  headers = {
1867
1606
  'Content-Type': 'application/json',
1868
1607
  Authorization: "Bearer " + accessToken,
@@ -1884,60 +1623,37 @@
1884
1623
  }),
1885
1624
  })];
1886
1625
  case 6:
1887
- res = _a.sent();
1626
+ res = _b.sent();
1888
1627
  if (!res.ok) {
1889
1628
  throw new Error("HTTP " + res.status);
1890
1629
  }
1891
1630
  return [4 /*yield*/, res.json()];
1892
1631
  case 7:
1893
- json = _a.sent();
1894
- wsUrl = json === null || json === void 0 ? void 0 : json.rn_ws_url;
1895
- if (!wsUrl || typeof wsUrl !== 'string') {
1632
+ json = _b.sent();
1633
+ wsUrl = (typeof (json === null || json === void 0 ? void 0 : json.ws_url) === 'string' && json.ws_url) ||
1634
+ (typeof (json === null || json === void 0 ? void 0 : json.rn_ws_url) === 'string' && json.rn_ws_url);
1635
+ if (!wsUrl) {
1896
1636
  throw new Error('No ws_url in response');
1897
1637
  }
1898
- // Subscribe to room_created BEFORE connecting to avoid race
1899
- this.wsClient.roomCreated$
1900
- .pipe(operators.take(1), operators.takeUntil(this.destroy$))
1901
- .subscribe(function (roomUrl) { return __awaiter(_this, void 0, void 0, function () {
1902
- var err_1;
1903
- return __generator(this, function (_a) {
1904
- switch (_a.label) {
1905
- case 0:
1906
- _a.trys.push([0, 2, , 4]);
1907
- return [4 /*yield*/, this.onRoomCreated(roomUrl)];
1908
- case 1:
1909
- _a.sent();
1910
- return [3 /*break*/, 4];
1911
- case 2:
1912
- err_1 = _a.sent();
1913
- console.error('Daily join failed:', err_1);
1914
- this.callStateSubject.next('ended');
1915
- this.statusTextSubject.next('Connection failed');
1916
- return [4 /*yield*/, this.disconnect()];
1917
- case 3:
1918
- _a.sent();
1919
- throw err_1;
1920
- case 4: return [2 /*return*/];
1921
- }
1922
- });
1923
- }); });
1924
- // Forward transcripts from WebSocket
1638
+ untilCallEnds$ = rxjs.merge(this.destroy$, this.endCall$);
1925
1639
  this.subscriptions.add(this.wsClient.userTranscript$
1926
- .pipe(operators.takeUntil(this.destroy$))
1640
+ .pipe(operators.takeUntil(untilCallEnds$))
1927
1641
  .subscribe(function (t) { return _this.userTranscriptSubject.next(t); }));
1928
1642
  this.subscriptions.add(this.wsClient.botTranscript$
1929
- .pipe(operators.takeUntil(this.destroy$))
1643
+ .pipe(operators.takeUntil(untilCallEnds$))
1930
1644
  .subscribe(function (t) { return _this.botTranscriptSubject.next(t); }));
1931
- // Connect signaling WebSocket (no audio over WS)
1645
+ this.subscriptions.add(this.wsClient.opened$
1646
+ .pipe(operators.takeUntil(untilCallEnds$), operators.take(1))
1647
+ .subscribe(function () { return void _this.onWebsocketOpened(); }));
1932
1648
  this.wsClient.connect(wsUrl);
1933
1649
  return [3 /*break*/, 10];
1934
1650
  case 8:
1935
- error_1 = _a.sent();
1651
+ error_1 = _b.sent();
1936
1652
  console.error('Error connecting voice agent:', error_1);
1937
1653
  this.callStateSubject.next('ended');
1938
1654
  return [4 /*yield*/, this.disconnect()];
1939
1655
  case 9:
1940
- _a.sent();
1656
+ _b.sent();
1941
1657
  this.statusTextSubject.next('Connection failed');
1942
1658
  throw error_1;
1943
1659
  case 10: return [2 /*return*/];
@@ -1945,79 +1661,148 @@
1945
1661
  });
1946
1662
  });
1947
1663
  };
1948
- VoiceAgentService.prototype.onRoomCreated = function (roomUrl) {
1664
+ VoiceAgentService.prototype.onWebsocketOpened = function () {
1949
1665
  return __awaiter(this, void 0, void 0, function () {
1950
- var _this = this;
1951
- return __generator(this, function (_a) {
1952
- switch (_a.label) {
1953
- case 0:
1954
- // Connect Daily.js for WebRTC audio
1955
- return [4 /*yield*/, this.dailyClient.connect(roomUrl)];
1666
+ var err_1;
1667
+ return __generator(this, function (_b) {
1668
+ switch (_b.label) {
1669
+ case 0:
1670
+ if (this.callStateSubject.value !== 'connecting') {
1671
+ return [2 /*return*/];
1672
+ }
1673
+ _b.label = 1;
1956
1674
  case 1:
1957
- // Connect Daily.js for WebRTC audio
1958
- _a.sent();
1959
- // Waveform: use local mic stream from Daily client
1960
- this.dailyClient.localStream$
1961
- .pipe(operators.filter(function (s) { return s != null; }), operators.take(1))
1962
- .subscribe(function (stream) {
1963
- _this.audioAnalyzer.start(stream);
1964
- });
1965
- this.subscriptions.add(this.dailyClient.userSpeaking$.subscribe(function (s) { return _this.isUserSpeakingSubject.next(s); }));
1966
- this.subscriptions.add(rxjs.combineLatest([
1967
- this.dailyClient.speaking$,
1968
- this.dailyClient.userSpeaking$,
1969
- ]).subscribe(function (_a) {
1970
- var _b = __read(_a, 2), bot = _b[0], user = _b[1];
1971
- var current = _this.callStateSubject.value;
1972
- if (current === 'connecting' && !bot) {
1973
- return;
1974
- }
1975
- if (current === 'connecting' && bot) {
1976
- _this.callStartTime = Date.now();
1977
- _this.startDurationTimer();
1978
- _this.callStateSubject.next('talking');
1979
- return;
1980
- }
1981
- if (user) {
1982
- _this.callStateSubject.next('listening');
1983
- }
1984
- else if (bot) {
1985
- _this.callStateSubject.next('talking');
1986
- }
1987
- else if (current === 'talking' || current === 'listening') {
1988
- _this.callStateSubject.next('connected');
1989
- }
1990
- }));
1991
- this.subscriptions.add(this.dailyClient.micMuted$.subscribe(function (muted) { return _this.isMicMutedSubject.next(muted); }));
1992
- this.statusTextSubject.next('Connecting...');
1993
- return [2 /*return*/];
1675
+ _b.trys.push([1, 3, , 5]);
1676
+ return [4 /*yield*/, this.startLocalMic()];
1677
+ case 2:
1678
+ _b.sent();
1679
+ this.statusTextSubject.next('Connected');
1680
+ this.callStateSubject.next('connected');
1681
+ this.wireSpeakingState();
1682
+ return [3 /*break*/, 5];
1683
+ case 3:
1684
+ err_1 = _b.sent();
1685
+ console.error('[HiveGpt Voice] Mic or session setup failed', err_1);
1686
+ this.callStateSubject.next('ended');
1687
+ this.statusTextSubject.next('Microphone unavailable');
1688
+ return [4 /*yield*/, this.disconnect()];
1689
+ case 4:
1690
+ _b.sent();
1691
+ return [3 /*break*/, 5];
1692
+ case 5: return [2 /*return*/];
1994
1693
  }
1995
1694
  });
1996
1695
  });
1997
1696
  };
1998
- VoiceAgentService.prototype.disconnect = function () {
1697
+ VoiceAgentService.prototype.wireSpeakingState = function () {
1698
+ var _this = this;
1699
+ var untilCallEnds$ = rxjs.merge(this.destroy$, this.endCall$);
1700
+ var transcriptDrivenAssistant$ = this.wsClient.botTranscript$.pipe(operators.switchMap(function () { return rxjs.concat(rxjs.of(true), rxjs.timer(800).pipe(operators.map(function () { return false; }))); }), operators.distinctUntilChanged());
1701
+ var assistantTalking$ = rxjs.merge(this.wsClient.assistantSpeaking$, transcriptDrivenAssistant$).pipe(operators.distinctUntilChanged(), operators.startWith(false));
1702
+ var userTalking$ = rxjs.combineLatest([
1703
+ this.audioAnalyzer.isUserSpeaking$,
1704
+ this.wsClient.serverUserSpeaking$.pipe(operators.startWith(false)),
1705
+ ]).pipe(operators.map(function (_b) {
1706
+ var _c = __read(_b, 2), local = _c[0], server = _c[1];
1707
+ return local || server;
1708
+ }), operators.distinctUntilChanged(), operators.startWith(false));
1709
+ this.subscriptions.add(rxjs.combineLatest([assistantTalking$, userTalking$])
1710
+ .pipe(operators.takeUntil(untilCallEnds$))
1711
+ .subscribe(function (_b) {
1712
+ var _c = __read(_b, 2), bot = _c[0], user = _c[1];
1713
+ var current = _this.callStateSubject.value;
1714
+ if (user) {
1715
+ _this.isUserSpeakingSubject.next(true);
1716
+ _this.callStateSubject.next('listening');
1717
+ }
1718
+ else {
1719
+ _this.isUserSpeakingSubject.next(false);
1720
+ }
1721
+ if (user) {
1722
+ return;
1723
+ }
1724
+ if (bot) {
1725
+ if (_this.callStartTime === 0) {
1726
+ _this.callStartTime = Date.now();
1727
+ _this.startDurationTimer();
1728
+ }
1729
+ _this.callStateSubject.next('talking');
1730
+ }
1731
+ else if (current === 'talking' || current === 'listening') {
1732
+ _this.callStateSubject.next('connected');
1733
+ }
1734
+ }));
1735
+ };
1736
+ VoiceAgentService.prototype.startLocalMic = function () {
1999
1737
  return __awaiter(this, void 0, void 0, function () {
2000
- return __generator(this, function (_a) {
2001
- switch (_a.label) {
1738
+ var stream, track;
1739
+ return __generator(this, function (_b) {
1740
+ switch (_b.label) {
2002
1741
  case 0:
2003
- this.stopDurationTimer();
2004
- this.audioAnalyzer.stop();
2005
- // Daily first, then WebSocket
2006
- return [4 /*yield*/, this.dailyClient.disconnect()];
1742
+ this.stopLocalMic();
1743
+ return [4 /*yield*/, navigator.mediaDevices.getUserMedia({ audio: true })];
2007
1744
  case 1:
2008
- // Daily first, then WebSocket
2009
- _a.sent();
2010
- this.wsClient.disconnect();
2011
- this.callStateSubject.next('ended');
2012
- this.statusTextSubject.next('Call Ended');
1745
+ stream = _b.sent();
1746
+ track = stream.getAudioTracks()[0];
1747
+ if (!track) {
1748
+ stream.getTracks().forEach(function (t) { return t.stop(); });
1749
+ throw new Error('No audio track');
1750
+ }
1751
+ this.localMicStream = stream;
1752
+ this.isMicMutedSubject.next(!track.enabled);
1753
+ this.audioAnalyzer.start(stream);
2013
1754
  return [2 /*return*/];
2014
1755
  }
2015
1756
  });
2016
1757
  });
2017
1758
  };
1759
+ VoiceAgentService.prototype.stopLocalMic = function () {
1760
+ if (this.localMicStream) {
1761
+ this.localMicStream.getTracks().forEach(function (t) { return t.stop(); });
1762
+ this.localMicStream = null;
1763
+ }
1764
+ };
1765
+ VoiceAgentService.prototype.handleRemoteClose = function () {
1766
+ return __awaiter(this, void 0, void 0, function () {
1767
+ var state;
1768
+ return __generator(this, function (_b) {
1769
+ state = this.callStateSubject.value;
1770
+ if (state === 'idle' || state === 'ended')
1771
+ return [2 /*return*/];
1772
+ this.endCall$.next();
1773
+ this.stopDurationTimer();
1774
+ this.callStartTime = 0;
1775
+ this.audioAnalyzer.stop();
1776
+ this.stopLocalMic();
1777
+ this.callStateSubject.next('ended');
1778
+ this.statusTextSubject.next('Connection lost');
1779
+ return [2 /*return*/];
1780
+ });
1781
+ });
1782
+ };
1783
+ VoiceAgentService.prototype.disconnect = function () {
1784
+ return __awaiter(this, void 0, void 0, function () {
1785
+ return __generator(this, function (_b) {
1786
+ this.endCall$.next();
1787
+ this.stopDurationTimer();
1788
+ this.callStartTime = 0;
1789
+ this.audioAnalyzer.stop();
1790
+ this.stopLocalMic();
1791
+ this.wsClient.disconnect();
1792
+ this.callStateSubject.next('ended');
1793
+ this.statusTextSubject.next('Call Ended');
1794
+ return [2 /*return*/];
1795
+ });
1796
+ });
1797
+ };
2018
1798
  VoiceAgentService.prototype.toggleMic = function () {
2019
- var current = this.isMicMutedSubject.value;
2020
- this.dailyClient.setMuted(!current);
1799
+ var _a;
1800
+ var nextMuted = !this.isMicMutedSubject.value;
1801
+ var track = (_a = this.localMicStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks()[0];
1802
+ if (track) {
1803
+ track.enabled = !nextMuted;
1804
+ }
1805
+ this.isMicMutedSubject.next(nextMuted);
2021
1806
  };
2022
1807
  VoiceAgentService.prototype.startDurationTimer = function () {
2023
1808
  var _this = this;
@@ -2040,7 +1825,7 @@
2040
1825
  };
2041
1826
  return VoiceAgentService;
2042
1827
  }());
2043
- VoiceAgentService.ɵprov = i0__namespace.ɵɵdefineInjectable({ factory: function VoiceAgentService_Factory() { return new VoiceAgentService(i0__namespace.ɵɵinject(AudioAnalyzerService), i0__namespace.ɵɵinject(WebSocketVoiceClientService), i0__namespace.ɵɵinject(DailyVoiceClientService), i0__namespace.ɵɵinject(PlatformTokenRefreshService), i0__namespace.ɵɵinject(i0__namespace.PLATFORM_ID)); }, token: VoiceAgentService, providedIn: "root" });
1828
+ VoiceAgentService.ɵprov = i0__namespace.ɵɵdefineInjectable({ factory: function VoiceAgentService_Factory() { return new VoiceAgentService(i0__namespace.ɵɵinject(AudioAnalyzerService), i0__namespace.ɵɵinject(WebSocketVoiceClientService), i0__namespace.ɵɵinject(PlatformTokenRefreshService), i0__namespace.ɵɵinject(i0__namespace.PLATFORM_ID)); }, token: VoiceAgentService, providedIn: "root" });
2044
1829
  VoiceAgentService.decorators = [
2045
1830
  { type: i0.Injectable, args: [{
2046
1831
  providedIn: 'root',
@@ -2049,7 +1834,6 @@
2049
1834
  VoiceAgentService.ctorParameters = function () { return [
2050
1835
  { type: AudioAnalyzerService },
2051
1836
  { type: WebSocketVoiceClientService },
2052
- { type: DailyVoiceClientService },
2053
1837
  { type: PlatformTokenRefreshService },
2054
1838
  { type: Object, decorators: [{ type: i0.Inject, args: [i0.PLATFORM_ID,] }] }
2055
1839
  ]; };
@@ -6231,7 +6015,7 @@
6231
6015
  };
6232
6016
 
6233
6017
  /**
6234
- * Voice agent module. Uses native WebSocket + Daily.js only.
6018
+ * Voice agent module. Uses native WebSocket for the voice session.
6235
6019
  * Does NOT use Socket.IO or ngx-socket-io.
6236
6020
  */
6237
6021
  var VoiceAgentModule = /** @class */ (function () {
@@ -6250,8 +6034,7 @@
6250
6034
  providers: [
6251
6035
  VoiceAgentService,
6252
6036
  AudioAnalyzerService,
6253
- WebSocketVoiceClientService,
6254
- DailyVoiceClientService
6037
+ WebSocketVoiceClientService
6255
6038
  ],
6256
6039
  exports: [
6257
6040
  VoiceAgentModalComponent
@@ -6553,10 +6336,9 @@
6553
6336
  exports["ɵd"] = NotificationSocket;
6554
6337
  exports["ɵe"] = TranslationService;
6555
6338
  exports["ɵf"] = WebSocketVoiceClientService;
6556
- exports["ɵg"] = DailyVoiceClientService;
6557
- exports["ɵh"] = VideoPlayerComponent;
6558
- exports["ɵi"] = SafeHtmlPipe;
6559
- exports["ɵj"] = BotHtmlEditorComponent;
6339
+ exports["ɵg"] = VideoPlayerComponent;
6340
+ exports["ɵh"] = SafeHtmlPipe;
6341
+ exports["ɵi"] = BotHtmlEditorComponent;
6560
6342
 
6561
6343
  Object.defineProperty(exports, '__esModule', { value: true });
6562
6344