@hivegpt/hiveai-angular 0.0.425 → 0.0.428

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 (29) hide show
  1. package/bundles/hivegpt-hiveai-angular.umd.js +506 -164
  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 +6 -4
  6. package/esm2015/lib/components/chat-drawer/chat-drawer.component.js +22 -7
  7. package/esm2015/lib/components/voice-agent/services/audio-analyzer.service.js +5 -1
  8. package/esm2015/lib/components/voice-agent/services/daily-voice-client.service.js +181 -0
  9. package/esm2015/lib/components/voice-agent/services/voice-agent.service.js +134 -140
  10. package/esm2015/lib/components/voice-agent/services/websocket-voice-client.service.js +95 -0
  11. package/esm2015/lib/components/voice-agent/voice-agent.module.js +10 -2
  12. package/fesm2015/hivegpt-hiveai-angular.js +432 -149
  13. package/fesm2015/hivegpt-hiveai-angular.js.map +1 -1
  14. package/hivegpt-hiveai-angular.d.ts +5 -3
  15. package/hivegpt-hiveai-angular.d.ts.map +1 -1
  16. package/hivegpt-hiveai-angular.metadata.json +1 -1
  17. package/lib/components/chat-drawer/chat-drawer.component.d.ts +7 -0
  18. package/lib/components/chat-drawer/chat-drawer.component.d.ts.map +1 -1
  19. package/lib/components/voice-agent/services/audio-analyzer.service.d.ts +4 -0
  20. package/lib/components/voice-agent/services/audio-analyzer.service.d.ts.map +1 -1
  21. package/lib/components/voice-agent/services/daily-voice-client.service.d.ts +48 -0
  22. package/lib/components/voice-agent/services/daily-voice-client.service.d.ts.map +1 -0
  23. package/lib/components/voice-agent/services/voice-agent.service.d.ts +24 -8
  24. package/lib/components/voice-agent/services/voice-agent.service.d.ts.map +1 -1
  25. package/lib/components/voice-agent/services/websocket-voice-client.service.d.ts +49 -0
  26. package/lib/components/voice-agent/services/websocket-voice-client.service.d.ts.map +1 -0
  27. package/lib/components/voice-agent/voice-agent.module.d.ts +4 -0
  28. package/lib/components/voice-agent/voice-agent.module.d.ts.map +1 -1
  29. package/package.json +2 -1
@@ -1,8 +1,10 @@
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('ngx-socket-io'), require('@angular/forms'), require('microsoft-cognitiveservices-speech-sdk'), require('marked'), require('@pipecat-ai/client-js'), require('@pipecat-ai/websocket-transport'), require('@angular/common'), 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', 'ngx-socket-io', '@angular/forms', 'microsoft-cognitiveservices-speech-sdk', 'marked', '@pipecat-ai/client-js', '@pipecat-ai/websocket-transport', '@angular/common', '@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.ngxSocketIo, global.ng.forms, global.SpeechSDK, global.marked, global.clientJs, global.websocketTransport, global.ng.common, global.ng.material.icon, global.ng.material.sidenav, global.ngxQuill));
5
- })(this, (function (exports, overlay, portal, i1, i0, platformBrowser, rxjs, operators, ngxSocketIo, forms, SpeechSDK, marked, clientJs, websocketTransport, common, icon, sidenav, ngxQuill) { 'use strict';
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('ngx-socket-io'), require('@angular/forms'), require('microsoft-cognitiveservices-speech-sdk'), require('marked'), require('@daily-co/daily-js'), require('@angular/common'), 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', 'ngx-socket-io', '@angular/forms', 'microsoft-cognitiveservices-speech-sdk', 'marked', '@daily-co/daily-js', '@angular/common', '@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.ngxSocketIo, global.ng.forms, global.SpeechSDK, global.marked, global.Daily, global.ng.common, global.ng.material.icon, global.ng.material.sidenav, global.ngxQuill));
5
+ })(this, (function (exports, overlay, portal, i1, i0, platformBrowser, rxjs, operators, ngxSocketIo, forms, SpeechSDK, marked, Daily, common, icon, sidenav, ngxQuill) { 'use strict';
6
+
7
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
6
8
 
7
9
  function _interopNamespace(e) {
8
10
  if (e && e.__esModule) return e;
@@ -26,6 +28,7 @@
26
28
  var i0__namespace = /*#__PURE__*/_interopNamespace(i0);
27
29
  var SpeechSDK__namespace = /*#__PURE__*/_interopNamespace(SpeechSDK);
28
30
  var marked__namespace = /*#__PURE__*/_interopNamespace(marked);
31
+ var Daily__default = /*#__PURE__*/_interopDefaultLegacy(Daily);
29
32
 
30
33
  /******************************************************************************
31
34
  Copyright (c) Microsoft Corporation.
@@ -1028,6 +1031,10 @@
1028
1031
  { type: i1.HttpClient }
1029
1032
  ]; };
1030
1033
 
1034
+ /**
1035
+ * Audio analyzer for waveform visualization only.
1036
+ * Do NOT use isUserSpeaking$ for call state; speaking state must come from Daily.js.
1037
+ */
1031
1038
  var AudioAnalyzerService = /** @class */ (function () {
1032
1039
  function AudioAnalyzerService() {
1033
1040
  this.audioContext = null;
@@ -1146,14 +1153,323 @@
1146
1153
  },] }
1147
1154
  ];
1148
1155
 
1156
+ /**
1157
+ * WebSocket-only client for voice agent signaling.
1158
+ * CRITICAL: Uses native WebSocket only. NO Socket.IO, NO ngx-socket-io.
1159
+ *
1160
+ * Responsibilities:
1161
+ * - Connect to ws_url (from POST /ai/ask-voice response)
1162
+ * - Parse JSON messages (room_created, user_transcript, bot_transcript)
1163
+ * - Emit roomCreated$, userTranscript$, botTranscript$
1164
+ * - NO audio logic, NO mic logic. Audio is handled by Daily.js (WebRTC).
1165
+ */
1166
+ var WebSocketVoiceClientService = /** @class */ (function () {
1167
+ function WebSocketVoiceClientService() {
1168
+ this.ws = null;
1169
+ this.roomCreatedSubject = new rxjs.Subject();
1170
+ this.userTranscriptSubject = new rxjs.Subject();
1171
+ this.botTranscriptSubject = new rxjs.Subject();
1172
+ /** Emits room_url when backend sends room_created. */
1173
+ this.roomCreated$ = this.roomCreatedSubject.asObservable();
1174
+ /** Emits user transcript updates. */
1175
+ this.userTranscript$ = this.userTranscriptSubject.asObservable();
1176
+ /** Emits bot transcript updates. */
1177
+ this.botTranscript$ = this.botTranscriptSubject.asObservable();
1178
+ }
1179
+ /** Connect to signaling WebSocket. No audio over this connection. */
1180
+ WebSocketVoiceClientService.prototype.connect = function (wsUrl) {
1181
+ var _this = this;
1182
+ var _a;
1183
+ if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN) {
1184
+ return;
1185
+ }
1186
+ if (this.ws) {
1187
+ this.ws.close();
1188
+ this.ws = null;
1189
+ }
1190
+ try {
1191
+ this.ws = new WebSocket(wsUrl);
1192
+ this.ws.onmessage = function (event) {
1193
+ var _a;
1194
+ try {
1195
+ var msg = JSON.parse(event.data);
1196
+ if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'room_created') {
1197
+ var roomUrl = ((_a = msg.room_url) !== null && _a !== void 0 ? _a : msg.roomUrl);
1198
+ if (typeof roomUrl === 'string') {
1199
+ _this.roomCreatedSubject.next(roomUrl);
1200
+ }
1201
+ }
1202
+ else if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'user_transcript' && typeof msg.text === 'string') {
1203
+ _this.userTranscriptSubject.next({
1204
+ text: msg.text,
1205
+ final: msg.final === true,
1206
+ });
1207
+ }
1208
+ else if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'bot_transcript' && typeof msg.text === 'string') {
1209
+ _this.botTranscriptSubject.next(msg.text);
1210
+ }
1211
+ }
1212
+ catch (_b) {
1213
+ // Ignore non-JSON or unknown messages
1214
+ }
1215
+ };
1216
+ this.ws.onerror = function () {
1217
+ _this.disconnect();
1218
+ };
1219
+ this.ws.onclose = function () {
1220
+ _this.ws = null;
1221
+ };
1222
+ }
1223
+ catch (err) {
1224
+ console.error('WebSocketVoiceClient: connect failed', err);
1225
+ this.ws = null;
1226
+ throw err;
1227
+ }
1228
+ };
1229
+ /** Disconnect and cleanup. */
1230
+ WebSocketVoiceClientService.prototype.disconnect = function () {
1231
+ if (this.ws) {
1232
+ this.ws.close();
1233
+ this.ws = null;
1234
+ }
1235
+ };
1236
+ Object.defineProperty(WebSocketVoiceClientService.prototype, "isConnected", {
1237
+ /** Whether the WebSocket is open. */
1238
+ get: function () {
1239
+ var _a;
1240
+ return ((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN;
1241
+ },
1242
+ enumerable: false,
1243
+ configurable: true
1244
+ });
1245
+ return WebSocketVoiceClientService;
1246
+ }());
1247
+ WebSocketVoiceClientService.ɵprov = i0__namespace.ɵɵdefineInjectable({ factory: function WebSocketVoiceClientService_Factory() { return new WebSocketVoiceClientService(); }, token: WebSocketVoiceClientService, providedIn: "root" });
1248
+ WebSocketVoiceClientService.decorators = [
1249
+ { type: i0.Injectable, args: [{
1250
+ providedIn: 'root',
1251
+ },] }
1252
+ ];
1253
+
1254
+ /**
1255
+ * Daily.js WebRTC client for voice agent audio.
1256
+ * Responsibilities:
1257
+ * - Create and manage Daily CallObject
1258
+ * - Join Daily room using room_url
1259
+ * - Handle mic capture + speaker playback
1260
+ * - Provide real-time speaking detection via active-speaker-change (primary)
1261
+ * and track-started/track-stopped (fallback for immediate feedback)
1262
+ * - Expose speaking$ (bot speaking), userSpeaking$ (user speaking), micMuted$
1263
+ * - Expose localStream$ for waveform visualization (AudioAnalyzerService)
1264
+ *
1265
+ * Speaking state flips immediately when agent audio starts playing.
1266
+ * If user speaks while bot is talking, state switches to listening.
1267
+ */
1268
+ var DailyVoiceClientService = /** @class */ (function () {
1269
+ function DailyVoiceClientService(ngZone) {
1270
+ this.ngZone = ngZone;
1271
+ this.callObject = null;
1272
+ this.localStream = null;
1273
+ this.localSessionId = null;
1274
+ this.speakingSubject = new rxjs.BehaviorSubject(false);
1275
+ this.userSpeakingSubject = new rxjs.BehaviorSubject(false);
1276
+ this.micMutedSubject = new rxjs.BehaviorSubject(false);
1277
+ this.localStreamSubject = new rxjs.BehaviorSubject(null);
1278
+ /** True when bot (remote participant) is the active speaker. */
1279
+ this.speaking$ = this.speakingSubject.asObservable();
1280
+ /** True when user (local participant) is the active speaker. */
1281
+ this.userSpeaking$ = this.userSpeakingSubject.asObservable();
1282
+ /** True when mic is muted. */
1283
+ this.micMuted$ = this.micMutedSubject.asObservable();
1284
+ /** Emits local mic stream for waveform visualization. */
1285
+ this.localStream$ = this.localStreamSubject.asObservable();
1286
+ }
1287
+ /**
1288
+ * Connect to Daily room. Acquires mic first for waveform, then joins with audio.
1289
+ * @param roomUrl Daily room URL (from room_created)
1290
+ * @param token Optional meeting token
1291
+ */
1292
+ DailyVoiceClientService.prototype.connect = function (roomUrl, token) {
1293
+ return __awaiter(this, void 0, void 0, function () {
1294
+ var stream, audioTrack, callObject, participants, err_1;
1295
+ return __generator(this, function (_c) {
1296
+ switch (_c.label) {
1297
+ case 0:
1298
+ if (!this.callObject) return [3 /*break*/, 2];
1299
+ return [4 /*yield*/, this.disconnect()];
1300
+ case 1:
1301
+ _c.sent();
1302
+ _c.label = 2;
1303
+ case 2:
1304
+ _c.trys.push([2, 5, , 6]);
1305
+ return [4 /*yield*/, navigator.mediaDevices.getUserMedia({ audio: true })];
1306
+ case 3:
1307
+ stream = _c.sent();
1308
+ audioTrack = stream.getAudioTracks()[0];
1309
+ if (!audioTrack) {
1310
+ stream.getTracks().forEach(function (t) { return t.stop(); });
1311
+ throw new Error('No audio track');
1312
+ }
1313
+ this.localStream = stream;
1314
+ this.localStreamSubject.next(stream);
1315
+ callObject = Daily__default["default"].createCallObject({
1316
+ videoSource: false,
1317
+ audioSource: audioTrack,
1318
+ });
1319
+ this.callObject = callObject;
1320
+ this.setupEventHandlers(callObject);
1321
+ // Join room; Daily handles playback of remote (bot) audio automatically
1322
+ return [4 /*yield*/, callObject.join({ url: roomUrl, token: token })];
1323
+ case 4:
1324
+ // Join room; Daily handles playback of remote (bot) audio automatically
1325
+ _c.sent();
1326
+ participants = callObject.participants();
1327
+ if (participants === null || participants === void 0 ? void 0 : participants.local) {
1328
+ this.localSessionId = participants.local.session_id;
1329
+ }
1330
+ // Initial mute state: Daily starts with audio on
1331
+ this.micMutedSubject.next(!callObject.localAudio());
1332
+ return [3 /*break*/, 6];
1333
+ case 5:
1334
+ err_1 = _c.sent();
1335
+ this.cleanup();
1336
+ throw err_1;
1337
+ case 6: return [2 /*return*/];
1338
+ }
1339
+ });
1340
+ });
1341
+ };
1342
+ DailyVoiceClientService.prototype.setupEventHandlers = function (call) {
1343
+ var _this = this;
1344
+ // active-speaker-change: primary source for real-time speaking detection.
1345
+ // Emits when the loudest participant changes; bot speaking = remote is active.
1346
+ call.on('active-speaker-change', function (event) {
1347
+ _this.ngZone.run(function () {
1348
+ var _a;
1349
+ var peerId = (_a = event === null || event === void 0 ? void 0 : event.activeSpeaker) === null || _a === void 0 ? void 0 : _a.peerId;
1350
+ if (!peerId || !_this.localSessionId) {
1351
+ _this.speakingSubject.next(false);
1352
+ _this.userSpeakingSubject.next(false);
1353
+ return;
1354
+ }
1355
+ var isLocal = peerId === _this.localSessionId;
1356
+ _this.userSpeakingSubject.next(isLocal);
1357
+ _this.speakingSubject.next(!isLocal);
1358
+ });
1359
+ });
1360
+ // track-started / track-stopped: fallback for immediate feedback when
1361
+ // remote (bot) audio track starts or stops. Ensures talking indicator
1362
+ // flips as soon as agent audio begins, without waiting for active-speaker-change.
1363
+ call.on('track-started', function (event) {
1364
+ _this.ngZone.run(function () {
1365
+ var _a, _b;
1366
+ var p = event === null || event === void 0 ? void 0 : event.participant;
1367
+ 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;
1368
+ if (p && !p.local && type === 'audio') {
1369
+ _this.speakingSubject.next(true);
1370
+ }
1371
+ });
1372
+ });
1373
+ call.on('track-stopped', function (event) {
1374
+ _this.ngZone.run(function () {
1375
+ var _a, _b;
1376
+ var p = event === null || event === void 0 ? void 0 : event.participant;
1377
+ 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;
1378
+ if (p && !p.local && type === 'audio') {
1379
+ _this.speakingSubject.next(false);
1380
+ }
1381
+ });
1382
+ });
1383
+ call.on('left-meeting', function () {
1384
+ _this.ngZone.run(function () { return _this.cleanup(); });
1385
+ });
1386
+ call.on('error', function (event) {
1387
+ _this.ngZone.run(function () {
1388
+ var _a;
1389
+ console.error('DailyVoiceClient: Daily error', (_a = event === null || event === void 0 ? void 0 : event.errorMsg) !== null && _a !== void 0 ? _a : event);
1390
+ _this.cleanup();
1391
+ });
1392
+ });
1393
+ };
1394
+ /** Set mic muted state. */
1395
+ DailyVoiceClientService.prototype.setMuted = function (muted) {
1396
+ if (!this.callObject)
1397
+ return;
1398
+ this.callObject.setLocalAudio(!muted);
1399
+ this.micMutedSubject.next(muted);
1400
+ };
1401
+ /** Disconnect and cleanup. */
1402
+ DailyVoiceClientService.prototype.disconnect = function () {
1403
+ return __awaiter(this, void 0, void 0, function () {
1404
+ var e_1;
1405
+ return __generator(this, function (_c) {
1406
+ switch (_c.label) {
1407
+ case 0:
1408
+ if (!this.callObject) {
1409
+ this.cleanup();
1410
+ return [2 /*return*/];
1411
+ }
1412
+ _c.label = 1;
1413
+ case 1:
1414
+ _c.trys.push([1, 3, , 4]);
1415
+ return [4 /*yield*/, this.callObject.leave()];
1416
+ case 2:
1417
+ _c.sent();
1418
+ return [3 /*break*/, 4];
1419
+ case 3:
1420
+ e_1 = _c.sent();
1421
+ return [3 /*break*/, 4];
1422
+ case 4:
1423
+ this.cleanup();
1424
+ return [2 /*return*/];
1425
+ }
1426
+ });
1427
+ });
1428
+ };
1429
+ DailyVoiceClientService.prototype.cleanup = function () {
1430
+ if (this.callObject) {
1431
+ this.callObject.destroy().catch(function () { });
1432
+ this.callObject = null;
1433
+ }
1434
+ if (this.localStream) {
1435
+ this.localStream.getTracks().forEach(function (t) { return t.stop(); });
1436
+ this.localStream = null;
1437
+ }
1438
+ this.localSessionId = null;
1439
+ this.speakingSubject.next(false);
1440
+ this.userSpeakingSubject.next(false);
1441
+ this.localStreamSubject.next(null);
1442
+ // Keep last micMuted state; will reset on next connect
1443
+ };
1444
+ return DailyVoiceClientService;
1445
+ }());
1446
+ DailyVoiceClientService.ɵprov = i0__namespace.ɵɵdefineInjectable({ factory: function DailyVoiceClientService_Factory() { return new DailyVoiceClientService(i0__namespace.ɵɵinject(i0__namespace.NgZone)); }, token: DailyVoiceClientService, providedIn: "root" });
1447
+ DailyVoiceClientService.decorators = [
1448
+ { type: i0.Injectable, args: [{
1449
+ providedIn: 'root',
1450
+ },] }
1451
+ ];
1452
+ DailyVoiceClientService.ctorParameters = function () { return [
1453
+ { type: i0.NgZone }
1454
+ ]; };
1455
+
1456
+ /**
1457
+ * Voice agent orchestrator. Coordinates WebSocket (signaling) and Daily.js (WebRTC audio).
1458
+ *
1459
+ * CRITICAL: This service must NEVER use Socket.IO or ngx-socket-io. Voice flow uses only:
1460
+ * - Native WebSocket (WebSocketVoiceClientService) for signaling (room_created, transcripts)
1461
+ * - Daily.js (DailyVoiceClientService) for WebRTC audio. Audio does NOT flow over WebSocket.
1462
+ *
1463
+ * - Maintains callState, statusText, duration, isMicMuted, isUserSpeaking, audioLevels
1464
+ * - Uses WebSocket for room_created and transcripts only (no audio)
1465
+ * - Uses Daily.js for all audio, mic, and real-time speaking detection
1466
+ */
1149
1467
  var VoiceAgentService = /** @class */ (function () {
1150
- function VoiceAgentService(audioAnalyzer) {
1468
+ function VoiceAgentService(audioAnalyzer, wsClient, dailyClient) {
1151
1469
  var _this = this;
1152
1470
  this.audioAnalyzer = audioAnalyzer;
1153
- this.client = null;
1154
- this.transport = null;
1155
- this.userMediaStream = null;
1156
- this.botAudioElement = null;
1471
+ this.wsClient = wsClient;
1472
+ this.dailyClient = dailyClient;
1157
1473
  this.callStateSubject = new rxjs.BehaviorSubject('idle');
1158
1474
  this.statusTextSubject = new rxjs.BehaviorSubject('');
1159
1475
  this.durationSubject = new rxjs.BehaviorSubject('00:00');
@@ -1164,6 +1480,8 @@
1164
1480
  this.botTranscriptSubject = new rxjs.Subject();
1165
1481
  this.callStartTime = 0;
1166
1482
  this.durationInterval = null;
1483
+ this.subscriptions = new rxjs.Subscription();
1484
+ this.destroy$ = new rxjs.Subject();
1167
1485
  this.callState$ = this.callStateSubject.asObservable();
1168
1486
  this.statusText$ = this.statusTextSubject.asObservable();
1169
1487
  this.duration$ = this.durationSubject.asObservable();
@@ -1172,39 +1490,23 @@
1172
1490
  this.audioLevels$ = this.audioLevelsSubject.asObservable();
1173
1491
  this.userTranscript$ = this.userTranscriptSubject.asObservable();
1174
1492
  this.botTranscript$ = this.botTranscriptSubject.asObservable();
1175
- this.audioAnalyzer.audioLevels$.subscribe(function (levels) {
1176
- _this.audioLevelsSubject.next(levels);
1177
- });
1178
- this.audioAnalyzer.isUserSpeaking$.subscribe(function (isSpeaking) {
1179
- _this.isUserSpeakingSubject.next(isSpeaking);
1180
- if (isSpeaking && _this.callStateSubject.value === 'connected') {
1181
- _this.callStateSubject.next('listening');
1182
- }
1183
- else if (!isSpeaking && _this.callStateSubject.value === 'listening') {
1184
- _this.callStateSubject.next('connected');
1185
- }
1186
- });
1493
+ // Waveform visualization only - do NOT use for speaking state
1494
+ this.subscriptions.add(this.audioAnalyzer.audioLevels$.subscribe(function (levels) { return _this.audioLevelsSubject.next(levels); }));
1187
1495
  }
1496
+ VoiceAgentService.prototype.ngOnDestroy = function () {
1497
+ this.destroy$.next();
1498
+ this.subscriptions.unsubscribe();
1499
+ this.disconnect();
1500
+ };
1188
1501
  /** Reset to idle state (e.g. when modal opens so user can click Start Call). */
1189
1502
  VoiceAgentService.prototype.resetToIdle = function () {
1190
1503
  if (this.callStateSubject.value === 'idle')
1191
1504
  return;
1192
- if (this.durationInterval) {
1193
- clearInterval(this.durationInterval);
1194
- this.durationInterval = null;
1195
- }
1505
+ this.stopDurationTimer();
1196
1506
  this.audioAnalyzer.stop();
1197
- this.client = null;
1198
- this.transport = null;
1199
- if (this.userMediaStream) {
1200
- this.userMediaStream.getTracks().forEach(function (track) { return track.stop(); });
1201
- this.userMediaStream = null;
1202
- }
1203
- if (this.botAudioElement) {
1204
- this.botAudioElement.pause();
1205
- this.botAudioElement.srcObject = null;
1206
- this.botAudioElement = null;
1207
- }
1507
+ this.wsClient.disconnect();
1508
+ // Fire-and-forget: Daily disconnect is async; connect() will await if needed
1509
+ void this.dailyClient.disconnect();
1208
1510
  this.callStateSubject.next('idle');
1209
1511
  this.statusTextSubject.next('');
1210
1512
  this.durationSubject.next('0:00');
@@ -1212,7 +1514,8 @@
1212
1514
  VoiceAgentService.prototype.connect = function (apiUrl, token, botId, conversationId, apiKey, eventToken, eventUrl, domainAuthority) {
1213
1515
  var _a;
1214
1516
  return __awaiter(this, void 0, void 0, function () {
1215
- var baseUrl, connectUrl, headers, startBotParams, tracks, error_1;
1517
+ var baseUrl, postUrl, headers, res, json, wsUrl, error_1;
1518
+ var _this = this;
1216
1519
  return __generator(this, function (_b) {
1217
1520
  switch (_b.label) {
1218
1521
  case 0:
@@ -1222,152 +1525,160 @@
1222
1525
  }
1223
1526
  _b.label = 1;
1224
1527
  case 1:
1225
- _b.trys.push([1, 3, , 5]);
1528
+ _b.trys.push([1, 4, , 6]);
1226
1529
  this.callStateSubject.next('connecting');
1227
1530
  this.statusTextSubject.next('Connecting...');
1228
1531
  baseUrl = apiUrl.replace(/\/$/, '');
1229
- connectUrl = baseUrl + "/ai/ask-voice";
1230
- headers = new Headers();
1231
- headers.set('Authorization', "Bearer " + token);
1232
- // Do not set Content-Type here — the Pipecat client already sets "application/json" once; adding it again produces "application/json, application/json"
1233
- headers.set('domain-authority', domainAuthority);
1234
- headers.set('eventtoken', eventToken);
1235
- headers.set('eventurl', eventUrl);
1236
- headers.set('hive-bot-id', botId);
1237
- headers.set('x-api-key', apiKey);
1238
- startBotParams = {
1239
- endpoint: connectUrl.ws_url,
1240
- headers: headers,
1241
- requestData: {
1242
- bot_id: botId,
1243
- conversation_id: conversationId,
1244
- voice: 'alloy',
1245
- },
1532
+ postUrl = baseUrl + "/ai/ask-voice";
1533
+ headers = {
1534
+ 'Content-Type': 'application/json',
1535
+ Authorization: "Bearer " + token,
1536
+ 'domain-authority': domainAuthority,
1537
+ 'eventtoken': eventToken,
1538
+ 'eventurl': eventUrl,
1539
+ 'hive-bot-id': botId,
1540
+ 'x-api-key': apiKey,
1246
1541
  };
1247
- this.transport = new websocketTransport.WebSocketTransport();
1248
- this.client = new clientJs.PipecatClient({
1249
- transport: this.transport,
1250
- enableMic: true,
1251
- enableCam: false,
1252
- });
1253
- this.setupEventHandlers();
1254
- this.botAudioElement = new Audio();
1255
- this.botAudioElement.autoplay = true;
1256
- return [4 /*yield*/, this.client.startBotAndConnect(startBotParams)];
1542
+ return [4 /*yield*/, fetch(postUrl, {
1543
+ method: 'POST',
1544
+ headers: headers,
1545
+ body: JSON.stringify({
1546
+ bot_id: botId,
1547
+ conversation_id: conversationId,
1548
+ voice: 'alloy',
1549
+ }),
1550
+ })];
1257
1551
  case 2:
1258
- _b.sent();
1259
- tracks = this.client.tracks();
1260
- if ((_a = tracks === null || tracks === void 0 ? void 0 : tracks.local) === null || _a === void 0 ? void 0 : _a.audio) {
1261
- this.userMediaStream = new MediaStream([tracks.local.audio]);
1262
- this.audioAnalyzer.start(this.userMediaStream);
1552
+ res = _b.sent();
1553
+ if (!res.ok) {
1554
+ throw new Error("HTTP " + res.status);
1263
1555
  }
1264
- this.isMicMutedSubject.next(!this.client.isMicEnabled);
1265
- this.callStateSubject.next('connected');
1266
- this.statusTextSubject.next('Connected');
1267
- this.callStartTime = Date.now();
1268
- this.startDurationTimer();
1269
- return [3 /*break*/, 5];
1556
+ return [4 /*yield*/, res.json()];
1270
1557
  case 3:
1558
+ json = _b.sent();
1559
+ wsUrl = (_a = json === null || json === void 0 ? void 0 : json.ws_url) !== null && _a !== void 0 ? _a : json === null || json === void 0 ? void 0 : json.rn_ws_url;
1560
+ if (!wsUrl || typeof wsUrl !== 'string') {
1561
+ throw new Error('No ws_url in response');
1562
+ }
1563
+ // Subscribe to room_created BEFORE connecting to avoid race
1564
+ this.wsClient.roomCreated$
1565
+ .pipe(operators.take(1), operators.takeUntil(this.destroy$))
1566
+ .subscribe(function (roomUrl) { return __awaiter(_this, void 0, void 0, function () {
1567
+ var err_1;
1568
+ return __generator(this, function (_b) {
1569
+ switch (_b.label) {
1570
+ case 0:
1571
+ _b.trys.push([0, 2, , 4]);
1572
+ return [4 /*yield*/, this.onRoomCreated(roomUrl)];
1573
+ case 1:
1574
+ _b.sent();
1575
+ return [3 /*break*/, 4];
1576
+ case 2:
1577
+ err_1 = _b.sent();
1578
+ console.error('Daily join failed:', err_1);
1579
+ this.callStateSubject.next('ended');
1580
+ this.statusTextSubject.next('Connection failed');
1581
+ return [4 /*yield*/, this.disconnect()];
1582
+ case 3:
1583
+ _b.sent();
1584
+ throw err_1;
1585
+ case 4: return [2 /*return*/];
1586
+ }
1587
+ });
1588
+ }); });
1589
+ // Forward transcripts from WebSocket
1590
+ this.subscriptions.add(this.wsClient.userTranscript$
1591
+ .pipe(operators.takeUntil(this.destroy$))
1592
+ .subscribe(function (t) { return _this.userTranscriptSubject.next(t); }));
1593
+ this.subscriptions.add(this.wsClient.botTranscript$
1594
+ .pipe(operators.takeUntil(this.destroy$))
1595
+ .subscribe(function (t) { return _this.botTranscriptSubject.next(t); }));
1596
+ // Connect signaling WebSocket (no audio over WS)
1597
+ this.wsClient.connect(wsUrl);
1598
+ return [3 /*break*/, 6];
1599
+ case 4:
1271
1600
  error_1 = _b.sent();
1272
1601
  console.error('Error connecting voice agent:', error_1);
1273
1602
  this.callStateSubject.next('ended');
1274
1603
  return [4 /*yield*/, this.disconnect()];
1275
- case 4:
1604
+ case 5:
1276
1605
  _b.sent();
1277
1606
  this.statusTextSubject.next('Connection failed');
1278
1607
  throw error_1;
1279
- case 5: return [2 /*return*/];
1608
+ case 6: return [2 /*return*/];
1609
+ }
1610
+ });
1611
+ });
1612
+ };
1613
+ VoiceAgentService.prototype.onRoomCreated = function (roomUrl) {
1614
+ return __awaiter(this, void 0, void 0, function () {
1615
+ var _this = this;
1616
+ return __generator(this, function (_b) {
1617
+ switch (_b.label) {
1618
+ case 0:
1619
+ // Connect Daily.js for WebRTC audio
1620
+ return [4 /*yield*/, this.dailyClient.connect(roomUrl)];
1621
+ case 1:
1622
+ // Connect Daily.js for WebRTC audio
1623
+ _b.sent();
1624
+ // Waveform: use local mic stream from Daily client
1625
+ this.dailyClient.localStream$
1626
+ .pipe(operators.filter(function (s) { return s != null; }), operators.take(1))
1627
+ .subscribe(function (stream) {
1628
+ _this.audioAnalyzer.start(stream);
1629
+ });
1630
+ // Speaking state from Daily only (NOT from AudioAnalyzer).
1631
+ // User speaking overrides bot talking: if user speaks while bot talks, state = listening.
1632
+ this.subscriptions.add(this.dailyClient.userSpeaking$.subscribe(function (s) { return _this.isUserSpeakingSubject.next(s); }));
1633
+ this.subscriptions.add(rxjs.combineLatest([
1634
+ this.dailyClient.speaking$,
1635
+ this.dailyClient.userSpeaking$,
1636
+ ]).subscribe(function (_b) {
1637
+ var _c = __read(_b, 2), bot = _c[0], user = _c[1];
1638
+ if (user) {
1639
+ _this.callStateSubject.next('listening');
1640
+ }
1641
+ else if (bot) {
1642
+ _this.callStateSubject.next('talking');
1643
+ }
1644
+ else if (_this.callStateSubject.value === 'talking' ||
1645
+ _this.callStateSubject.value === 'listening') {
1646
+ _this.callStateSubject.next('connected');
1647
+ }
1648
+ }));
1649
+ this.subscriptions.add(this.dailyClient.micMuted$.subscribe(function (muted) { return _this.isMicMutedSubject.next(muted); }));
1650
+ this.callStateSubject.next('connected');
1651
+ this.statusTextSubject.next('Connected');
1652
+ this.callStartTime = Date.now();
1653
+ this.startDurationTimer();
1654
+ return [2 /*return*/];
1280
1655
  }
1281
1656
  });
1282
1657
  });
1283
1658
  };
1284
1659
  VoiceAgentService.prototype.disconnect = function () {
1285
1660
  return __awaiter(this, void 0, void 0, function () {
1286
- var error_2;
1287
1661
  return __generator(this, function (_b) {
1288
1662
  switch (_b.label) {
1289
1663
  case 0:
1290
- _b.trys.push([0, 3, , 4]);
1291
- if (this.durationInterval) {
1292
- clearInterval(this.durationInterval);
1293
- this.durationInterval = null;
1294
- }
1664
+ this.stopDurationTimer();
1295
1665
  this.audioAnalyzer.stop();
1296
- if (!this.client) return [3 /*break*/, 2];
1297
- return [4 /*yield*/, this.client.disconnect()];
1666
+ // Daily first, then WebSocket
1667
+ return [4 /*yield*/, this.dailyClient.disconnect()];
1298
1668
  case 1:
1669
+ // Daily first, then WebSocket
1299
1670
  _b.sent();
1300
- this.client = null;
1301
- _b.label = 2;
1302
- case 2:
1303
- this.transport = null;
1304
- if (this.userMediaStream) {
1305
- this.userMediaStream.getTracks().forEach(function (track) { return track.stop(); });
1306
- this.userMediaStream = null;
1307
- }
1308
- if (this.botAudioElement) {
1309
- this.botAudioElement.pause();
1310
- this.botAudioElement.srcObject = null;
1311
- this.botAudioElement = null;
1312
- }
1671
+ this.wsClient.disconnect();
1313
1672
  this.callStateSubject.next('ended');
1314
1673
  this.statusTextSubject.next('Call Ended');
1315
- return [3 /*break*/, 4];
1316
- case 3:
1317
- error_2 = _b.sent();
1318
- console.error('Error disconnecting voice agent:', error_2);
1319
- return [3 /*break*/, 4];
1320
- case 4: return [2 /*return*/];
1674
+ return [2 /*return*/];
1321
1675
  }
1322
1676
  });
1323
1677
  });
1324
1678
  };
1325
1679
  VoiceAgentService.prototype.toggleMic = function () {
1326
- if (!this.client)
1327
- return;
1328
- var newState = !this.client.isMicEnabled;
1329
- this.client.enableMic(newState);
1330
- this.isMicMutedSubject.next(!newState);
1331
- };
1332
- VoiceAgentService.prototype.setupEventHandlers = function () {
1333
- var _this = this;
1334
- if (!this.client)
1335
- return;
1336
- this.client.on(clientJs.RTVIEvent.BotStartedSpeaking, function () {
1337
- _this.callStateSubject.next('talking');
1338
- });
1339
- this.client.on(clientJs.RTVIEvent.BotStoppedSpeaking, function () {
1340
- if (_this.callStateSubject.value === 'talking') {
1341
- _this.callStateSubject.next('connected');
1342
- }
1343
- });
1344
- this.client.on(clientJs.RTVIEvent.TrackStarted, function (track, participant) {
1345
- if (_this.botAudioElement && participant && !participant.local && track.kind === 'audio') {
1346
- var stream = new MediaStream([track]);
1347
- _this.botAudioElement.srcObject = stream;
1348
- _this.botAudioElement.play().catch(console.error);
1349
- }
1350
- });
1351
- this.client.on(clientJs.RTVIEvent.UserTranscript, function (data) {
1352
- var _a;
1353
- _this.userTranscriptSubject.next({
1354
- text: data.text,
1355
- final: (_a = data.final) !== null && _a !== void 0 ? _a : false,
1356
- });
1357
- });
1358
- this.client.on(clientJs.RTVIEvent.BotTranscript, function (data) {
1359
- if (data === null || data === void 0 ? void 0 : data.text) {
1360
- _this.botTranscriptSubject.next(data.text);
1361
- }
1362
- });
1363
- this.client.on(clientJs.RTVIEvent.Error, function () {
1364
- _this.statusTextSubject.next('Error occurred');
1365
- });
1366
- this.client.on(clientJs.RTVIEvent.Disconnected, function () {
1367
- _this.callStateSubject.next('ended');
1368
- _this.statusTextSubject.next('Disconnected');
1369
- _this.disconnect();
1370
- });
1680
+ var current = this.isMicMutedSubject.value;
1681
+ this.dailyClient.setMuted(!current);
1371
1682
  };
1372
1683
  VoiceAgentService.prototype.startDurationTimer = function () {
1373
1684
  var _this = this;
@@ -1379,19 +1690,27 @@
1379
1690
  _this.durationSubject.next(minutes + ":" + String(seconds).padStart(2, '0'));
1380
1691
  }
1381
1692
  };
1382
- updateDuration(); // show 0:00 immediately
1693
+ updateDuration();
1383
1694
  this.durationInterval = setInterval(updateDuration, 1000);
1384
1695
  };
1696
+ VoiceAgentService.prototype.stopDurationTimer = function () {
1697
+ if (this.durationInterval) {
1698
+ clearInterval(this.durationInterval);
1699
+ this.durationInterval = null;
1700
+ }
1701
+ };
1385
1702
  return VoiceAgentService;
1386
1703
  }());
1387
- VoiceAgentService.ɵprov = i0__namespace.ɵɵdefineInjectable({ factory: function VoiceAgentService_Factory() { return new VoiceAgentService(i0__namespace.ɵɵinject(AudioAnalyzerService)); }, token: VoiceAgentService, providedIn: "root" });
1704
+ VoiceAgentService.ɵprov = i0__namespace.ɵɵdefineInjectable({ factory: function VoiceAgentService_Factory() { return new VoiceAgentService(i0__namespace.ɵɵinject(AudioAnalyzerService), i0__namespace.ɵɵinject(WebSocketVoiceClientService), i0__namespace.ɵɵinject(DailyVoiceClientService)); }, token: VoiceAgentService, providedIn: "root" });
1388
1705
  VoiceAgentService.decorators = [
1389
1706
  { type: i0.Injectable, args: [{
1390
- providedIn: 'root'
1707
+ providedIn: 'root',
1391
1708
  },] }
1392
1709
  ];
1393
1710
  VoiceAgentService.ctorParameters = function () { return [
1394
- { type: AudioAnalyzerService }
1711
+ { type: AudioAnalyzerService },
1712
+ { type: WebSocketVoiceClientService },
1713
+ { type: DailyVoiceClientService }
1395
1714
  ]; };
1396
1715
 
1397
1716
  var VOICE_MODAL_CONFIG = new i0.InjectionToken('VOICE_MODAL_CONFIG');
@@ -1652,6 +1971,8 @@
1652
1971
  this.voiceModalOverlayRef = null;
1653
1972
  this.voiceTranscriptSubscriptions = [];
1654
1973
  this.domainAuthorityValue = 'prod-lite';
1974
+ /** True after Socket has been initialized for chat (avoids connecting on voice-only usage). */
1975
+ this.chatSocketInitialized = false;
1655
1976
  this.isChatingWithAi = false;
1656
1977
  this.readAllChunks = function (stream) {
1657
1978
  var reader = stream.getReader();
@@ -1742,11 +2063,11 @@
1742
2063
  }
1743
2064
  }
1744
2065
  }
1745
- if (changes.orgId) {
1746
- if (changes.orgId.currentValue != changes.orgId.previousValue &&
1747
- changes.orgId.currentValue) {
1748
- this.initializeSocket();
1749
- }
2066
+ // Do NOT call initializeSocket() here. Socket.IO is for chat only; we defer
2067
+ // until the user actually uses chat (send message or start new conversation)
2068
+ // so that voice-only usage never triggers Socket.IO (avoids v2/v3 mismatch error).
2069
+ if (changes.orgId && changes.orgId.currentValue != changes.orgId.previousValue) {
2070
+ this.chatSocketInitialized = false;
1750
2071
  }
1751
2072
  };
1752
2073
  ChatDrawerComponent.prototype.ngOnInit = function () {
@@ -1793,6 +2114,16 @@
1793
2114
  _this.initializeSpeechRecognizer(token);
1794
2115
  });
1795
2116
  };
2117
+ /**
2118
+ * Call before chat actions so Socket.IO is used only for chat, not for voice.
2119
+ * Voice agent uses native WebSocket + Daily.js only; Socket.IO must not run during voice flow.
2120
+ */
2121
+ ChatDrawerComponent.prototype.ensureChatSocket = function () {
2122
+ if (this.chatSocketInitialized || !this.orgId)
2123
+ return;
2124
+ this.chatSocketInitialized = true;
2125
+ this.initializeSocket();
2126
+ };
1796
2127
  ChatDrawerComponent.prototype.initializeSocket = function () {
1797
2128
  var _this = this;
1798
2129
  try {
@@ -2171,6 +2502,7 @@
2171
2502
  if (!this.input || this.loading) {
2172
2503
  return;
2173
2504
  }
2505
+ this.ensureChatSocket();
2174
2506
  this.chatLog.push({
2175
2507
  type: 'user',
2176
2508
  message: this.processMessageForDisplay(this.input),
@@ -2194,6 +2526,7 @@
2194
2526
  if (!inputMsg || this.loading) {
2195
2527
  return;
2196
2528
  }
2529
+ this.ensureChatSocket();
2197
2530
  try {
2198
2531
  chat.relatedListItems = [];
2199
2532
  this.cdr.detectChanges();
@@ -3253,8 +3586,9 @@
3253
3586
  this.conversationKey = this.conversationService.getKey(this.botId, true);
3254
3587
  this.chatLog = [this.chatLog[0]];
3255
3588
  this.isChatingWithAi = false;
3589
+ this.chatSocketInitialized = false;
3256
3590
  setTimeout(function () {
3257
- _this.initializeSocket();
3591
+ _this.ensureChatSocket();
3258
3592
  }, 200);
3259
3593
  this.scrollToBottom();
3260
3594
  this.cdr.detectChanges();
@@ -3557,6 +3891,10 @@
3557
3891
  isDev: [{ type: i0.Input }]
3558
3892
  };
3559
3893
 
3894
+ /**
3895
+ * Voice agent module. Uses native WebSocket + Daily.js only.
3896
+ * Does NOT use Socket.IO or ngx-socket-io.
3897
+ */
3560
3898
  var VoiceAgentModule = /** @class */ (function () {
3561
3899
  function VoiceAgentModule() {
3562
3900
  }
@@ -3572,7 +3910,9 @@
3572
3910
  ],
3573
3911
  providers: [
3574
3912
  VoiceAgentService,
3575
- AudioAnalyzerService
3913
+ AudioAnalyzerService,
3914
+ WebSocketVoiceClientService,
3915
+ DailyVoiceClientService
3576
3916
  ],
3577
3917
  exports: [
3578
3918
  VoiceAgentModalComponent
@@ -3870,9 +4210,11 @@
3870
4210
  exports["ɵc"] = ConversationService;
3871
4211
  exports["ɵd"] = NotificationSocket;
3872
4212
  exports["ɵe"] = TranslationService;
3873
- exports["ɵf"] = VideoPlayerComponent;
3874
- exports["ɵg"] = SafeHtmlPipe;
3875
- exports["ɵh"] = BotHtmlEditorComponent;
4213
+ exports["ɵf"] = WebSocketVoiceClientService;
4214
+ exports["ɵg"] = DailyVoiceClientService;
4215
+ exports["ɵh"] = VideoPlayerComponent;
4216
+ exports["ɵi"] = SafeHtmlPipe;
4217
+ exports["ɵj"] = BotHtmlEditorComponent;
3876
4218
 
3877
4219
  Object.defineProperty(exports, '__esModule', { value: true });
3878
4220