@hivegpt/hiveai-angular 0.0.581 → 0.0.583

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 +420 -490
  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 +195 -83
  8. package/esm2015/lib/components/voice-agent/services/websocket-voice-client.service.js +160 -49
  9. package/esm2015/lib/components/voice-agent/voice-agent.module.js +3 -5
  10. package/fesm2015/hivegpt-hiveai-angular.js +338 -416
  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 +22 -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 +30 -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,38 @@
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
+ this.audioChunkSubject = new rxjs.Subject();
1361
+ /** Fires once each time the WebSocket reaches OPEN. */
1362
+ this.opened$ = this.openedSubject.asObservable();
1363
+ /** Fires when the socket closes without a client-initiated {@link disconnect}. */
1364
+ this.remoteClose$ = this.remoteCloseSubject.asObservable();
1363
1365
  this.userTranscript$ = this.userTranscriptSubject.asObservable();
1364
- /** Emits bot transcript updates. */
1365
1366
  this.botTranscript$ = this.botTranscriptSubject.asObservable();
1367
+ /** Assistant/bot speaking, when the server sends explicit events (see {@link handleJsonMessage}). */
1368
+ this.assistantSpeaking$ = this.assistantSpeakingSubject.asObservable();
1369
+ /** User speaking from server-side VAD, if provided. */
1370
+ this.serverUserSpeaking$ = this.serverUserSpeakingSubject.asObservable();
1371
+ /** Binary audio frames from server (when backend streams bot audio over WS). */
1372
+ this.audioChunk$ = this.audioChunkSubject.asObservable();
1366
1373
  }
1367
- /** Connect to signaling WebSocket. No audio over this connection. */
1368
1374
  WebSocketVoiceClientService.prototype.connect = function (wsUrl) {
1369
1375
  var _this = this;
1370
1376
  var _a;
@@ -1372,41 +1378,39 @@
1372
1378
  return;
1373
1379
  }
1374
1380
  if (this.ws) {
1381
+ this.closeInitiatedByClient = true;
1375
1382
  this.ws.close();
1376
- this.ws = null;
1377
1383
  }
1378
1384
  try {
1379
- this.ws = new WebSocket(wsUrl);
1380
- this.ws.onmessage = function (event) {
1381
- var _a;
1382
- 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);
1385
+ var socket_1 = new WebSocket(wsUrl);
1386
+ this.ws = socket_1;
1387
+ socket_1.onopen = function () {
1388
+ if (_this.ws !== socket_1)
1389
+ return;
1390
+ _this.ngZone.run(function () { return _this.openedSubject.next(); });
1391
+ };
1392
+ socket_1.onmessage = function (event) {
1393
+ if (_this.ws !== socket_1)
1394
+ return;
1395
+ void _this.handleIncomingMessage(event.data);
1396
+ };
1397
+ socket_1.onerror = function () {
1398
+ _this.ngZone.run(function () {
1399
+ if (_this.ws === socket_1 && socket_1.readyState !== WebSocket.CLOSED) {
1400
+ socket_1.close();
1398
1401
  }
1402
+ });
1403
+ };
1404
+ socket_1.onclose = function () {
1405
+ if (_this.ws === socket_1) {
1406
+ _this.ws = null;
1399
1407
  }
1400
- catch (_b) {
1401
- // Ignore non-JSON or unknown messages
1408
+ var client = _this.closeInitiatedByClient;
1409
+ _this.closeInitiatedByClient = false;
1410
+ if (!client) {
1411
+ _this.ngZone.run(function () { return _this.remoteCloseSubject.next(); });
1402
1412
  }
1403
1413
  };
1404
- this.ws.onerror = function () {
1405
- _this.disconnect();
1406
- };
1407
- this.ws.onclose = function () {
1408
- _this.ws = null;
1409
- };
1410
1414
  }
1411
1415
  catch (err) {
1412
1416
  console.error('WebSocketVoiceClient: connect failed', err);
@@ -1414,374 +1418,157 @@
1414
1418
  throw err;
1415
1419
  }
1416
1420
  };
1417
- /** Disconnect and cleanup. */
1418
- WebSocketVoiceClientService.prototype.disconnect = function () {
1419
- if (this.ws) {
1420
- this.ws.close();
1421
- this.ws = null;
1422
- }
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) {
1421
+ WebSocketVoiceClientService.prototype.handleIncomingMessage = function (payload) {
1483
1422
  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) {
1423
+ var ab;
1424
+ return __generator(this, function (_b) {
1425
+ switch (_b.label) {
1487
1426
  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;
1427
+ if (typeof payload === 'string') {
1428
+ this.handleJsonString(payload);
1429
+ return [2 /*return*/];
1514
1430
  }
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;
1431
+ if (payload instanceof ArrayBuffer) {
1432
+ this.handleBinaryMessage(payload);
1433
+ return [2 /*return*/];
1522
1434
  }
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*/];
1435
+ if (!(payload instanceof Blob)) return [3 /*break*/, 2];
1436
+ return [4 /*yield*/, payload.arrayBuffer()];
1437
+ case 1:
1438
+ ab = _b.sent();
1439
+ this.handleBinaryMessage(ab);
1440
+ _b.label = 2;
1441
+ case 2: return [2 /*return*/];
1531
1442
  }
1532
1443
  });
1533
1444
  });
1534
1445
  };
1535
- DailyVoiceClientService.prototype.setupEventHandlers = function (call) {
1446
+ WebSocketVoiceClientService.prototype.handleJsonString = function (jsonText) {
1536
1447
  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
1448
  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
- });
1623
- }
1449
+ var msg_1 = JSON.parse(jsonText);
1450
+ this.ngZone.run(function () { return _this.handleJsonMessage(msg_1); });
1624
1451
  }
1625
- catch (err) {
1626
- console.warn('DailyVoiceClient: failed to create remote audio element', err);
1452
+ catch (_a) {
1453
+ // Ignore non-JSON
1627
1454
  }
1628
1455
  };
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) {
1456
+ WebSocketVoiceClientService.prototype.handleBinaryMessage = function (buffer) {
1634
1457
  var _this = this;
1635
- this.stopRemoteAudioMonitor();
1458
+ // Some backends wrap JSON events inside binary WS frames.
1459
+ var maybeText = this.tryDecodeUtf8(buffer);
1460
+ if (maybeText !== null) {
1461
+ this.handleJsonString(maybeText);
1462
+ return;
1463
+ }
1464
+ // Otherwise treat binary as streamed assistant audio.
1465
+ this.ngZone.run(function () { return _this.audioChunkSubject.next(buffer); });
1466
+ };
1467
+ WebSocketVoiceClientService.prototype.tryDecodeUtf8 = function (buffer) {
1636
1468
  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);
1469
+ var text = new TextDecoder('utf-8', { fatal: true }).decode(buffer);
1470
+ var trimmed = text.trim();
1471
+ if (!trimmed || (trimmed[0] !== '{' && trimmed[0] !== '[')) {
1472
+ return null;
1473
+ }
1474
+ return trimmed;
1677
1475
  }
1678
- catch (err) {
1679
- console.warn('DailyVoiceClient: failed to create remote audio monitor', err);
1476
+ catch (_a) {
1477
+ return null;
1680
1478
  }
1681
1479
  };
1682
- DailyVoiceClientService.prototype.stopRemoteAudioMonitor = function () {
1683
- if (this.remoteSpeakingRAF) {
1684
- cancelAnimationFrame(this.remoteSpeakingRAF);
1685
- this.remoteSpeakingRAF = null;
1480
+ WebSocketVoiceClientService.prototype.handleJsonMessage = function (msg) {
1481
+ var type = msg.type;
1482
+ var typeStr = typeof type === 'string' ? type : '';
1483
+ if (typeStr === 'session_ready' || typeStr === 'connected' || typeStr === 'voice_session_started') {
1484
+ return;
1686
1485
  }
1687
- if (this.remoteAudioContext) {
1688
- this.remoteAudioContext.close().catch(function () { });
1689
- this.remoteAudioContext = null;
1486
+ if (typeStr === 'assistant_speaking' ||
1487
+ typeStr === 'bot_speaking') {
1488
+ if (msg.active === true || msg.speaking === true) {
1489
+ this.assistantSpeakingSubject.next(true);
1490
+ }
1491
+ else if (msg.active === false || msg.speaking === false) {
1492
+ this.assistantSpeakingSubject.next(false);
1493
+ }
1494
+ return;
1690
1495
  }
1691
- };
1692
- DailyVoiceClientService.prototype.stopRemoteAudio = function () {
1693
- if (this.remoteAudioElement) {
1694
- try {
1695
- this.remoteAudioElement.pause();
1696
- this.remoteAudioElement.srcObject = null;
1496
+ if (typeStr === 'user_speaking') {
1497
+ if (msg.active === true || msg.speaking === true) {
1498
+ this.serverUserSpeakingSubject.next(true);
1499
+ }
1500
+ else if (msg.active === false || msg.speaking === false) {
1501
+ this.serverUserSpeakingSubject.next(false);
1697
1502
  }
1698
- catch (_) { }
1699
- this.remoteAudioElement = null;
1503
+ return;
1700
1504
  }
1701
- };
1702
- /** Set mic muted state. */
1703
- DailyVoiceClientService.prototype.setMuted = function (muted) {
1704
- if (!this.callObject)
1505
+ if (typeStr === 'input_audio_buffer.speech_started') {
1506
+ this.serverUserSpeakingSubject.next(true);
1705
1507
  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
- }
1508
+ }
1509
+ if (typeStr === 'input_audio_buffer.speech_stopped') {
1510
+ this.serverUserSpeakingSubject.next(false);
1511
+ return;
1512
+ }
1513
+ if (typeStr === 'response.audio.delta') {
1514
+ this.assistantSpeakingSubject.next(true);
1515
+ return;
1516
+ }
1517
+ if (typeStr === 'response.audio.done' ||
1518
+ typeStr === 'response.output_audio.done') {
1519
+ this.assistantSpeakingSubject.next(false);
1520
+ return;
1521
+ }
1522
+ if (typeStr === 'user_transcript' && typeof msg.text === 'string') {
1523
+ this.userTranscriptSubject.next({
1524
+ text: msg.text,
1525
+ final: msg.final === true,
1734
1526
  });
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;
1527
+ return;
1743
1528
  }
1744
- if (this.localStream) {
1745
- this.localStream.getTracks().forEach(function (t) { return t.stop(); });
1746
- this.localStream = null;
1529
+ if (typeStr === 'bot_transcript' && typeof msg.text === 'string') {
1530
+ this.botTranscriptSubject.next(msg.text);
1747
1531
  }
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
1753
1532
  };
1754
- return DailyVoiceClientService;
1533
+ WebSocketVoiceClientService.prototype.disconnect = function () {
1534
+ if (!this.ws) {
1535
+ return;
1536
+ }
1537
+ this.closeInitiatedByClient = true;
1538
+ this.ws.close();
1539
+ };
1540
+ Object.defineProperty(WebSocketVoiceClientService.prototype, "isConnected", {
1541
+ get: function () {
1542
+ var _a;
1543
+ return ((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN;
1544
+ },
1545
+ enumerable: false,
1546
+ configurable: true
1547
+ });
1548
+ return WebSocketVoiceClientService;
1755
1549
  }());
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 = [
1550
+ WebSocketVoiceClientService.ɵprov = i0__namespace.ɵɵdefineInjectable({ factory: function WebSocketVoiceClientService_Factory() { return new WebSocketVoiceClientService(i0__namespace.ɵɵinject(i0__namespace.NgZone)); }, token: WebSocketVoiceClientService, providedIn: "root" });
1551
+ WebSocketVoiceClientService.decorators = [
1758
1552
  { type: i0.Injectable, args: [{
1759
1553
  providedIn: 'root',
1760
1554
  },] }
1761
1555
  ];
1762
- DailyVoiceClientService.ctorParameters = function () { return [
1556
+ WebSocketVoiceClientService.ctorParameters = function () { return [
1763
1557
  { type: i0.NgZone }
1764
1558
  ]; };
1765
1559
 
1766
1560
  /**
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
1561
+ * Voice agent orchestrator: single WebSocket (`ws_url` from POST /ai/ask-voice-socket)
1562
+ * for session events, transcripts, and optional speaking hints; local mic for capture
1563
+ * and waveform only (no Daily/WebRTC room).
1776
1564
  */
1777
1565
  var VoiceAgentService = /** @class */ (function () {
1778
- function VoiceAgentService(audioAnalyzer, wsClient, dailyClient, platformTokenRefresh,
1566
+ function VoiceAgentService(audioAnalyzer, wsClient, platformTokenRefresh,
1779
1567
  /** `Object` not `object` — ngc metadata collection rejects the `object` type in DI params. */
1780
1568
  platformId) {
1781
1569
  var _this = this;
1782
1570
  this.audioAnalyzer = audioAnalyzer;
1783
1571
  this.wsClient = wsClient;
1784
- this.dailyClient = dailyClient;
1785
1572
  this.platformTokenRefresh = platformTokenRefresh;
1786
1573
  this.platformId = platformId;
1787
1574
  this.callStateSubject = new rxjs.BehaviorSubject('idle');
@@ -1794,6 +1581,11 @@
1794
1581
  this.botTranscriptSubject = new rxjs.Subject();
1795
1582
  this.callStartTime = 0;
1796
1583
  this.durationInterval = null;
1584
+ this.localMicStream = null;
1585
+ this.remoteAudioContext = null;
1586
+ this.pendingRemoteAudio = [];
1587
+ this.remoteAudioPlaying = false;
1588
+ this.endCall$ = new rxjs.Subject();
1797
1589
  this.subscriptions = new rxjs.Subscription();
1798
1590
  this.destroy$ = new rxjs.Subject();
1799
1591
  this.callState$ = this.callStateSubject.asObservable();
@@ -1804,8 +1596,13 @@
1804
1596
  this.audioLevels$ = this.audioLevelsSubject.asObservable();
1805
1597
  this.userTranscript$ = this.userTranscriptSubject.asObservable();
1806
1598
  this.botTranscript$ = this.botTranscriptSubject.asObservable();
1807
- // Waveform visualization only - do NOT use for speaking state
1808
1599
  this.subscriptions.add(this.audioAnalyzer.audioLevels$.subscribe(function (levels) { return _this.audioLevelsSubject.next(levels); }));
1600
+ this.subscriptions.add(this.wsClient.remoteClose$
1601
+ .pipe(operators.takeUntil(this.destroy$))
1602
+ .subscribe(function () { return void _this.handleRemoteClose(); }));
1603
+ this.subscriptions.add(this.wsClient.audioChunk$
1604
+ .pipe(operators.takeUntil(this.destroy$))
1605
+ .subscribe(function (chunk) { return _this.enqueueRemoteAudio(chunk); }));
1809
1606
  }
1810
1607
  VoiceAgentService.prototype.ngOnDestroy = function () {
1811
1608
  this.destroy$.next();
@@ -1816,53 +1613,55 @@
1816
1613
  VoiceAgentService.prototype.resetToIdle = function () {
1817
1614
  if (this.callStateSubject.value === 'idle')
1818
1615
  return;
1616
+ this.endCall$.next();
1819
1617
  this.stopDurationTimer();
1618
+ this.callStartTime = 0;
1820
1619
  this.audioAnalyzer.stop();
1620
+ this.stopLocalMic();
1621
+ this.resetRemoteAudioPlayback();
1821
1622
  this.wsClient.disconnect();
1822
- // Fire-and-forget: Daily disconnect is async; connect() will await if needed
1823
- void this.dailyClient.disconnect();
1824
1623
  this.callStateSubject.next('idle');
1825
1624
  this.statusTextSubject.next('');
1826
1625
  this.durationSubject.next('0:00');
1827
1626
  };
1828
1627
  VoiceAgentService.prototype.connect = function (apiUrl, token, botId, conversationId, apiKey, eventToken, eventId, eventUrl, domainAuthority, usersApiUrl) {
1829
1628
  return __awaiter(this, void 0, void 0, function () {
1830
- var accessToken, ensured, e_1, baseUrl, postUrl, headers, res, json, wsUrl, error_1;
1629
+ var accessToken, ensured, e_1, baseUrl, postUrl, headers, res, json, wsUrl, untilCallEnds$, error_1;
1831
1630
  var _this = this;
1832
- return __generator(this, function (_a) {
1833
- switch (_a.label) {
1631
+ return __generator(this, function (_b) {
1632
+ switch (_b.label) {
1834
1633
  case 0:
1835
1634
  if (this.callStateSubject.value !== 'idle') {
1836
1635
  console.warn('Call already in progress');
1837
1636
  return [2 /*return*/];
1838
1637
  }
1839
- _a.label = 1;
1638
+ _b.label = 1;
1840
1639
  case 1:
1841
- _a.trys.push([1, 8, , 10]);
1640
+ _b.trys.push([1, 8, , 10]);
1842
1641
  this.callStateSubject.next('connecting');
1843
1642
  this.statusTextSubject.next('Connecting...');
1844
1643
  accessToken = token;
1845
1644
  if (!(usersApiUrl && common.isPlatformBrowser(this.platformId))) return [3 /*break*/, 5];
1846
- _a.label = 2;
1645
+ _b.label = 2;
1847
1646
  case 2:
1848
- _a.trys.push([2, 4, , 5]);
1647
+ _b.trys.push([2, 4, , 5]);
1849
1648
  return [4 /*yield*/, this.platformTokenRefresh
1850
1649
  .ensureValidAccessToken(token, usersApiUrl)
1851
1650
  .pipe(operators.take(1))
1852
1651
  .toPromise()];
1853
1652
  case 3:
1854
- ensured = _a.sent();
1653
+ ensured = _b.sent();
1855
1654
  if (ensured === null || ensured === void 0 ? void 0 : ensured.accessToken) {
1856
1655
  accessToken = ensured.accessToken;
1857
1656
  }
1858
1657
  return [3 /*break*/, 5];
1859
1658
  case 4:
1860
- e_1 = _a.sent();
1659
+ e_1 = _b.sent();
1861
1660
  console.warn('[HiveGpt Voice] Token refresh before connect failed', e_1);
1862
1661
  return [3 /*break*/, 5];
1863
1662
  case 5:
1864
1663
  baseUrl = apiUrl.replace(/\/$/, '');
1865
- postUrl = baseUrl + "/ai/ask-voice";
1664
+ postUrl = baseUrl + "/ai/ask-voice-socket";
1866
1665
  headers = {
1867
1666
  'Content-Type': 'application/json',
1868
1667
  Authorization: "Bearer " + accessToken,
@@ -1884,60 +1683,37 @@
1884
1683
  }),
1885
1684
  })];
1886
1685
  case 6:
1887
- res = _a.sent();
1686
+ res = _b.sent();
1888
1687
  if (!res.ok) {
1889
1688
  throw new Error("HTTP " + res.status);
1890
1689
  }
1891
1690
  return [4 /*yield*/, res.json()];
1892
1691
  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') {
1692
+ json = _b.sent();
1693
+ wsUrl = (typeof (json === null || json === void 0 ? void 0 : json.ws_url) === 'string' && json.ws_url) ||
1694
+ (typeof (json === null || json === void 0 ? void 0 : json.rn_ws_url) === 'string' && json.rn_ws_url);
1695
+ if (!wsUrl) {
1896
1696
  throw new Error('No ws_url in response');
1897
1697
  }
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
1698
+ untilCallEnds$ = rxjs.merge(this.destroy$, this.endCall$);
1925
1699
  this.subscriptions.add(this.wsClient.userTranscript$
1926
- .pipe(operators.takeUntil(this.destroy$))
1700
+ .pipe(operators.takeUntil(untilCallEnds$))
1927
1701
  .subscribe(function (t) { return _this.userTranscriptSubject.next(t); }));
1928
1702
  this.subscriptions.add(this.wsClient.botTranscript$
1929
- .pipe(operators.takeUntil(this.destroy$))
1703
+ .pipe(operators.takeUntil(untilCallEnds$))
1930
1704
  .subscribe(function (t) { return _this.botTranscriptSubject.next(t); }));
1931
- // Connect signaling WebSocket (no audio over WS)
1705
+ this.subscriptions.add(this.wsClient.opened$
1706
+ .pipe(operators.takeUntil(untilCallEnds$), operators.take(1))
1707
+ .subscribe(function () { return void _this.onWebsocketOpened(); }));
1932
1708
  this.wsClient.connect(wsUrl);
1933
1709
  return [3 /*break*/, 10];
1934
1710
  case 8:
1935
- error_1 = _a.sent();
1711
+ error_1 = _b.sent();
1936
1712
  console.error('Error connecting voice agent:', error_1);
1937
1713
  this.callStateSubject.next('ended');
1938
1714
  return [4 /*yield*/, this.disconnect()];
1939
1715
  case 9:
1940
- _a.sent();
1716
+ _b.sent();
1941
1717
  this.statusTextSubject.next('Connection failed');
1942
1718
  throw error_1;
1943
1719
  case 10: return [2 /*return*/];
@@ -1945,79 +1721,236 @@
1945
1721
  });
1946
1722
  });
1947
1723
  };
1948
- VoiceAgentService.prototype.onRoomCreated = function (roomUrl) {
1724
+ VoiceAgentService.prototype.onWebsocketOpened = function () {
1949
1725
  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)];
1726
+ var err_1;
1727
+ return __generator(this, function (_b) {
1728
+ switch (_b.label) {
1729
+ case 0:
1730
+ if (this.callStateSubject.value !== 'connecting') {
1731
+ return [2 /*return*/];
1732
+ }
1733
+ _b.label = 1;
1956
1734
  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...');
1735
+ _b.trys.push([1, 3, , 5]);
1736
+ return [4 /*yield*/, this.startLocalMic()];
1737
+ case 2:
1738
+ _b.sent();
1739
+ this.statusTextSubject.next('Connected');
1740
+ this.callStateSubject.next('connected');
1741
+ this.wireSpeakingState();
1742
+ return [3 /*break*/, 5];
1743
+ case 3:
1744
+ err_1 = _b.sent();
1745
+ console.error('[HiveGpt Voice] Mic or session setup failed', err_1);
1746
+ this.callStateSubject.next('ended');
1747
+ this.statusTextSubject.next('Microphone unavailable');
1748
+ return [4 /*yield*/, this.disconnect()];
1749
+ case 4:
1750
+ _b.sent();
1751
+ return [3 /*break*/, 5];
1752
+ case 5: return [2 /*return*/];
1753
+ }
1754
+ });
1755
+ });
1756
+ };
1757
+ VoiceAgentService.prototype.wireSpeakingState = function () {
1758
+ var _this = this;
1759
+ var untilCallEnds$ = rxjs.merge(this.destroy$, this.endCall$);
1760
+ 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());
1761
+ var assistantTalking$ = rxjs.merge(this.wsClient.assistantSpeaking$, transcriptDrivenAssistant$).pipe(operators.distinctUntilChanged(), operators.startWith(false));
1762
+ var userTalking$ = rxjs.combineLatest([
1763
+ this.audioAnalyzer.isUserSpeaking$,
1764
+ this.wsClient.serverUserSpeaking$.pipe(operators.startWith(false)),
1765
+ ]).pipe(operators.map(function (_b) {
1766
+ var _c = __read(_b, 2), local = _c[0], server = _c[1];
1767
+ return local || server;
1768
+ }), operators.distinctUntilChanged(), operators.startWith(false));
1769
+ this.subscriptions.add(rxjs.combineLatest([assistantTalking$, userTalking$])
1770
+ .pipe(operators.takeUntil(untilCallEnds$))
1771
+ .subscribe(function (_b) {
1772
+ var _c = __read(_b, 2), bot = _c[0], user = _c[1];
1773
+ var current = _this.callStateSubject.value;
1774
+ if (user) {
1775
+ _this.isUserSpeakingSubject.next(true);
1776
+ _this.callStateSubject.next('listening');
1777
+ }
1778
+ else {
1779
+ _this.isUserSpeakingSubject.next(false);
1780
+ }
1781
+ if (user) {
1782
+ return;
1783
+ }
1784
+ if (bot) {
1785
+ if (_this.callStartTime === 0) {
1786
+ _this.callStartTime = Date.now();
1787
+ _this.startDurationTimer();
1788
+ }
1789
+ _this.callStateSubject.next('talking');
1790
+ }
1791
+ else if (current === 'talking' || current === 'listening') {
1792
+ _this.callStateSubject.next('connected');
1793
+ }
1794
+ }));
1795
+ };
1796
+ VoiceAgentService.prototype.startLocalMic = function () {
1797
+ return __awaiter(this, void 0, void 0, function () {
1798
+ var stream, track;
1799
+ return __generator(this, function (_b) {
1800
+ switch (_b.label) {
1801
+ case 0:
1802
+ this.stopLocalMic();
1803
+ return [4 /*yield*/, navigator.mediaDevices.getUserMedia({ audio: true })];
1804
+ case 1:
1805
+ stream = _b.sent();
1806
+ track = stream.getAudioTracks()[0];
1807
+ if (!track) {
1808
+ stream.getTracks().forEach(function (t) { return t.stop(); });
1809
+ throw new Error('No audio track');
1810
+ }
1811
+ this.localMicStream = stream;
1812
+ this.isMicMutedSubject.next(!track.enabled);
1813
+ this.audioAnalyzer.start(stream);
1993
1814
  return [2 /*return*/];
1994
1815
  }
1995
1816
  });
1996
1817
  });
1997
1818
  };
1998
- VoiceAgentService.prototype.disconnect = function () {
1819
+ VoiceAgentService.prototype.stopLocalMic = function () {
1820
+ if (this.localMicStream) {
1821
+ this.localMicStream.getTracks().forEach(function (t) { return t.stop(); });
1822
+ this.localMicStream = null;
1823
+ }
1824
+ };
1825
+ VoiceAgentService.prototype.enqueueRemoteAudio = function (chunk) {
1826
+ this.pendingRemoteAudio.push(chunk.slice(0));
1827
+ if (!this.remoteAudioPlaying) {
1828
+ void this.playRemoteAudioQueue();
1829
+ }
1830
+ };
1831
+ VoiceAgentService.prototype.playRemoteAudioQueue = function () {
1999
1832
  return __awaiter(this, void 0, void 0, function () {
2000
- return __generator(this, function (_a) {
2001
- switch (_a.label) {
1833
+ var context, chunk, decoded, _a_1;
1834
+ return __generator(this, function (_b) {
1835
+ switch (_b.label) {
2002
1836
  case 0:
2003
- this.stopDurationTimer();
2004
- this.audioAnalyzer.stop();
2005
- // Daily first, then WebSocket
2006
- return [4 /*yield*/, this.dailyClient.disconnect()];
1837
+ this.remoteAudioPlaying = true;
1838
+ context = this.getOrCreateRemoteAudioContext();
1839
+ _b.label = 1;
2007
1840
  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');
1841
+ if (!(this.pendingRemoteAudio.length > 0)) return [3 /*break*/, 7];
1842
+ chunk = this.pendingRemoteAudio.shift();
1843
+ if (!chunk)
1844
+ return [3 /*break*/, 1];
1845
+ _b.label = 2;
1846
+ case 2:
1847
+ _b.trys.push([2, 5, , 6]);
1848
+ return [4 /*yield*/, this.decodeAudioChunk(context, chunk)];
1849
+ case 3:
1850
+ decoded = _b.sent();
1851
+ this.assistantAudioStarted();
1852
+ return [4 /*yield*/, this.playDecodedBuffer(context, decoded)];
1853
+ case 4:
1854
+ _b.sent();
1855
+ return [3 /*break*/, 6];
1856
+ case 5:
1857
+ _a_1 = _b.sent();
1858
+ return [3 /*break*/, 6];
1859
+ case 6: return [3 /*break*/, 1];
1860
+ case 7:
1861
+ this.remoteAudioPlaying = false;
1862
+ this.assistantAudioStopped();
2013
1863
  return [2 /*return*/];
2014
1864
  }
2015
1865
  });
2016
1866
  });
2017
1867
  };
1868
+ VoiceAgentService.prototype.getOrCreateRemoteAudioContext = function () {
1869
+ if (!this.remoteAudioContext || this.remoteAudioContext.state === 'closed') {
1870
+ this.remoteAudioContext = new AudioContext();
1871
+ }
1872
+ if (this.remoteAudioContext.state === 'suspended') {
1873
+ void this.remoteAudioContext.resume();
1874
+ }
1875
+ return this.remoteAudioContext;
1876
+ };
1877
+ VoiceAgentService.prototype.decodeAudioChunk = function (context, chunk) {
1878
+ return new Promise(function (resolve, reject) {
1879
+ context.decodeAudioData(chunk.slice(0), resolve, reject);
1880
+ });
1881
+ };
1882
+ VoiceAgentService.prototype.playDecodedBuffer = function (context, buffer) {
1883
+ return new Promise(function (resolve) {
1884
+ var source = context.createBufferSource();
1885
+ source.buffer = buffer;
1886
+ source.connect(context.destination);
1887
+ source.onended = function () { return resolve(); };
1888
+ source.start();
1889
+ });
1890
+ };
1891
+ VoiceAgentService.prototype.assistantAudioStarted = function () {
1892
+ if (this.callStartTime === 0) {
1893
+ this.callStartTime = Date.now();
1894
+ this.startDurationTimer();
1895
+ }
1896
+ this.callStateSubject.next('talking');
1897
+ };
1898
+ VoiceAgentService.prototype.assistantAudioStopped = function () {
1899
+ if (this.callStateSubject.value === 'talking') {
1900
+ this.callStateSubject.next('connected');
1901
+ }
1902
+ };
1903
+ VoiceAgentService.prototype.resetRemoteAudioPlayback = function () {
1904
+ this.pendingRemoteAudio = [];
1905
+ this.remoteAudioPlaying = false;
1906
+ if (this.remoteAudioContext && this.remoteAudioContext.state !== 'closed') {
1907
+ this.remoteAudioContext.close().catch(function () { });
1908
+ }
1909
+ this.remoteAudioContext = null;
1910
+ };
1911
+ VoiceAgentService.prototype.handleRemoteClose = function () {
1912
+ return __awaiter(this, void 0, void 0, function () {
1913
+ var state;
1914
+ return __generator(this, function (_b) {
1915
+ state = this.callStateSubject.value;
1916
+ if (state === 'idle' || state === 'ended')
1917
+ return [2 /*return*/];
1918
+ this.endCall$.next();
1919
+ this.stopDurationTimer();
1920
+ this.callStartTime = 0;
1921
+ this.audioAnalyzer.stop();
1922
+ this.stopLocalMic();
1923
+ this.resetRemoteAudioPlayback();
1924
+ this.callStateSubject.next('ended');
1925
+ this.statusTextSubject.next('Connection lost');
1926
+ return [2 /*return*/];
1927
+ });
1928
+ });
1929
+ };
1930
+ VoiceAgentService.prototype.disconnect = function () {
1931
+ return __awaiter(this, void 0, void 0, function () {
1932
+ return __generator(this, function (_b) {
1933
+ this.endCall$.next();
1934
+ this.stopDurationTimer();
1935
+ this.callStartTime = 0;
1936
+ this.audioAnalyzer.stop();
1937
+ this.stopLocalMic();
1938
+ this.resetRemoteAudioPlayback();
1939
+ this.wsClient.disconnect();
1940
+ this.callStateSubject.next('ended');
1941
+ this.statusTextSubject.next('Call Ended');
1942
+ return [2 /*return*/];
1943
+ });
1944
+ });
1945
+ };
2018
1946
  VoiceAgentService.prototype.toggleMic = function () {
2019
- var current = this.isMicMutedSubject.value;
2020
- this.dailyClient.setMuted(!current);
1947
+ var _a;
1948
+ var nextMuted = !this.isMicMutedSubject.value;
1949
+ var track = (_a = this.localMicStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks()[0];
1950
+ if (track) {
1951
+ track.enabled = !nextMuted;
1952
+ }
1953
+ this.isMicMutedSubject.next(nextMuted);
2021
1954
  };
2022
1955
  VoiceAgentService.prototype.startDurationTimer = function () {
2023
1956
  var _this = this;
@@ -2040,7 +1973,7 @@
2040
1973
  };
2041
1974
  return VoiceAgentService;
2042
1975
  }());
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" });
1976
+ 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
1977
  VoiceAgentService.decorators = [
2045
1978
  { type: i0.Injectable, args: [{
2046
1979
  providedIn: 'root',
@@ -2049,7 +1982,6 @@
2049
1982
  VoiceAgentService.ctorParameters = function () { return [
2050
1983
  { type: AudioAnalyzerService },
2051
1984
  { type: WebSocketVoiceClientService },
2052
- { type: DailyVoiceClientService },
2053
1985
  { type: PlatformTokenRefreshService },
2054
1986
  { type: Object, decorators: [{ type: i0.Inject, args: [i0.PLATFORM_ID,] }] }
2055
1987
  ]; };
@@ -6231,7 +6163,7 @@
6231
6163
  };
6232
6164
 
6233
6165
  /**
6234
- * Voice agent module. Uses native WebSocket + Daily.js only.
6166
+ * Voice agent module. Uses native WebSocket for the voice session.
6235
6167
  * Does NOT use Socket.IO or ngx-socket-io.
6236
6168
  */
6237
6169
  var VoiceAgentModule = /** @class */ (function () {
@@ -6250,8 +6182,7 @@
6250
6182
  providers: [
6251
6183
  VoiceAgentService,
6252
6184
  AudioAnalyzerService,
6253
- WebSocketVoiceClientService,
6254
- DailyVoiceClientService
6185
+ WebSocketVoiceClientService
6255
6186
  ],
6256
6187
  exports: [
6257
6188
  VoiceAgentModalComponent
@@ -6553,10 +6484,9 @@
6553
6484
  exports["ɵd"] = NotificationSocket;
6554
6485
  exports["ɵe"] = TranslationService;
6555
6486
  exports["ɵf"] = WebSocketVoiceClientService;
6556
- exports["ɵg"] = DailyVoiceClientService;
6557
- exports["ɵh"] = VideoPlayerComponent;
6558
- exports["ɵi"] = SafeHtmlPipe;
6559
- exports["ɵj"] = BotHtmlEditorComponent;
6487
+ exports["ɵg"] = VideoPlayerComponent;
6488
+ exports["ɵh"] = SafeHtmlPipe;
6489
+ exports["ɵi"] = BotHtmlEditorComponent;
6560
6490
 
6561
6491
  Object.defineProperty(exports, '__esModule', { value: true });
6562
6492