@hivegpt/hiveai-angular 0.0.424 → 0.0.427

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 +474 -158
  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/voice-agent/services/audio-analyzer.service.js +5 -1
  7. package/esm2015/lib/components/voice-agent/services/daily-voice-client.service.js +181 -0
  8. package/esm2015/lib/components/voice-agent/services/voice-agent.service.js +128 -140
  9. package/esm2015/lib/components/voice-agent/services/websocket-voice-client.service.js +93 -0
  10. package/esm2015/lib/components/voice-agent/voice-agent.module.js +6 -2
  11. package/fesm2015/hivegpt-hiveai-angular.js +399 -143
  12. package/fesm2015/hivegpt-hiveai-angular.js.map +1 -1
  13. package/hivegpt-hiveai-angular.d.ts +5 -3
  14. package/hivegpt-hiveai-angular.d.ts.map +1 -1
  15. package/hivegpt-hiveai-angular.metadata.json +1 -1
  16. package/lib/components/voice-agent/services/audio-analyzer.service.d.ts +4 -0
  17. package/lib/components/voice-agent/services/audio-analyzer.service.d.ts.map +1 -1
  18. package/lib/components/voice-agent/services/daily-voice-client.service.d.ts +48 -0
  19. package/lib/components/voice-agent/services/daily-voice-client.service.d.ts.map +1 -0
  20. package/lib/components/voice-agent/services/voice-agent.service.d.ts +19 -8
  21. package/lib/components/voice-agent/services/voice-agent.service.d.ts.map +1 -1
  22. package/lib/components/voice-agent/services/websocket-voice-client.service.d.ts +47 -0
  23. package/lib/components/voice-agent/services/websocket-voice-client.service.d.ts.map +1 -0
  24. package/lib/components/voice-agent/voice-agent.module.d.ts.map +1 -1
  25. 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,316 @@
1146
1153
  },] }
1147
1154
  ];
1148
1155
 
1156
+ /**
1157
+ * WebSocket-only client for voice agent signaling.
1158
+ * Responsibilities:
1159
+ * - Connect to ws_url
1160
+ * - Parse JSON messages
1161
+ * - Emit roomCreated$, userTranscript$, botTranscript$
1162
+ * - NO audio logic, NO mic logic.
1163
+ */
1164
+ var WebSocketVoiceClientService = /** @class */ (function () {
1165
+ function WebSocketVoiceClientService() {
1166
+ this.ws = null;
1167
+ this.roomCreatedSubject = new rxjs.Subject();
1168
+ this.userTranscriptSubject = new rxjs.Subject();
1169
+ this.botTranscriptSubject = new rxjs.Subject();
1170
+ /** Emits room_url when backend sends room_created. */
1171
+ this.roomCreated$ = this.roomCreatedSubject.asObservable();
1172
+ /** Emits user transcript updates. */
1173
+ this.userTranscript$ = this.userTranscriptSubject.asObservable();
1174
+ /** Emits bot transcript updates. */
1175
+ this.botTranscript$ = this.botTranscriptSubject.asObservable();
1176
+ }
1177
+ /** Connect to signaling WebSocket. No audio over this connection. */
1178
+ WebSocketVoiceClientService.prototype.connect = function (wsUrl) {
1179
+ var _this = this;
1180
+ var _a;
1181
+ if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN) {
1182
+ return;
1183
+ }
1184
+ if (this.ws) {
1185
+ this.ws.close();
1186
+ this.ws = null;
1187
+ }
1188
+ try {
1189
+ this.ws = new WebSocket(wsUrl);
1190
+ this.ws.onmessage = function (event) {
1191
+ var _a;
1192
+ try {
1193
+ var msg = JSON.parse(event.data);
1194
+ if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'room_created') {
1195
+ var roomUrl = ((_a = msg.room_url) !== null && _a !== void 0 ? _a : msg.roomUrl);
1196
+ if (typeof roomUrl === 'string') {
1197
+ _this.roomCreatedSubject.next(roomUrl);
1198
+ }
1199
+ }
1200
+ else if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'user_transcript' && typeof msg.text === 'string') {
1201
+ _this.userTranscriptSubject.next({
1202
+ text: msg.text,
1203
+ final: msg.final === true,
1204
+ });
1205
+ }
1206
+ else if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'bot_transcript' && typeof msg.text === 'string') {
1207
+ _this.botTranscriptSubject.next(msg.text);
1208
+ }
1209
+ }
1210
+ catch (_b) {
1211
+ // Ignore non-JSON or unknown messages
1212
+ }
1213
+ };
1214
+ this.ws.onerror = function () {
1215
+ _this.disconnect();
1216
+ };
1217
+ this.ws.onclose = function () {
1218
+ _this.ws = null;
1219
+ };
1220
+ }
1221
+ catch (err) {
1222
+ console.error('WebSocketVoiceClient: connect failed', err);
1223
+ this.ws = null;
1224
+ throw err;
1225
+ }
1226
+ };
1227
+ /** Disconnect and cleanup. */
1228
+ WebSocketVoiceClientService.prototype.disconnect = function () {
1229
+ if (this.ws) {
1230
+ this.ws.close();
1231
+ this.ws = null;
1232
+ }
1233
+ };
1234
+ Object.defineProperty(WebSocketVoiceClientService.prototype, "isConnected", {
1235
+ /** Whether the WebSocket is open. */
1236
+ get: function () {
1237
+ var _a;
1238
+ return ((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN;
1239
+ },
1240
+ enumerable: false,
1241
+ configurable: true
1242
+ });
1243
+ return WebSocketVoiceClientService;
1244
+ }());
1245
+ WebSocketVoiceClientService.ɵprov = i0__namespace.ɵɵdefineInjectable({ factory: function WebSocketVoiceClientService_Factory() { return new WebSocketVoiceClientService(); }, token: WebSocketVoiceClientService, providedIn: "root" });
1246
+ WebSocketVoiceClientService.decorators = [
1247
+ { type: i0.Injectable, args: [{
1248
+ providedIn: 'root',
1249
+ },] }
1250
+ ];
1251
+
1252
+ /**
1253
+ * Daily.js WebRTC client for voice agent audio.
1254
+ * Responsibilities:
1255
+ * - Create and manage Daily CallObject
1256
+ * - Join Daily room using room_url
1257
+ * - Handle mic capture + speaker playback
1258
+ * - Provide real-time speaking detection via active-speaker-change (primary)
1259
+ * and track-started/track-stopped (fallback for immediate feedback)
1260
+ * - Expose speaking$ (bot speaking), userSpeaking$ (user speaking), micMuted$
1261
+ * - Expose localStream$ for waveform visualization (AudioAnalyzerService)
1262
+ *
1263
+ * Speaking state flips immediately when agent audio starts playing.
1264
+ * If user speaks while bot is talking, state switches to listening.
1265
+ */
1266
+ var DailyVoiceClientService = /** @class */ (function () {
1267
+ function DailyVoiceClientService(ngZone) {
1268
+ this.ngZone = ngZone;
1269
+ this.callObject = null;
1270
+ this.localStream = null;
1271
+ this.localSessionId = null;
1272
+ this.speakingSubject = new rxjs.BehaviorSubject(false);
1273
+ this.userSpeakingSubject = new rxjs.BehaviorSubject(false);
1274
+ this.micMutedSubject = new rxjs.BehaviorSubject(false);
1275
+ this.localStreamSubject = new rxjs.BehaviorSubject(null);
1276
+ /** True when bot (remote participant) is the active speaker. */
1277
+ this.speaking$ = this.speakingSubject.asObservable();
1278
+ /** True when user (local participant) is the active speaker. */
1279
+ this.userSpeaking$ = this.userSpeakingSubject.asObservable();
1280
+ /** True when mic is muted. */
1281
+ this.micMuted$ = this.micMutedSubject.asObservable();
1282
+ /** Emits local mic stream for waveform visualization. */
1283
+ this.localStream$ = this.localStreamSubject.asObservable();
1284
+ }
1285
+ /**
1286
+ * Connect to Daily room. Acquires mic first for waveform, then joins with audio.
1287
+ * @param roomUrl Daily room URL (from room_created)
1288
+ * @param token Optional meeting token
1289
+ */
1290
+ DailyVoiceClientService.prototype.connect = function (roomUrl, token) {
1291
+ return __awaiter(this, void 0, void 0, function () {
1292
+ var stream, audioTrack, callObject, participants, err_1;
1293
+ return __generator(this, function (_c) {
1294
+ switch (_c.label) {
1295
+ case 0:
1296
+ if (!this.callObject) return [3 /*break*/, 2];
1297
+ return [4 /*yield*/, this.disconnect()];
1298
+ case 1:
1299
+ _c.sent();
1300
+ _c.label = 2;
1301
+ case 2:
1302
+ _c.trys.push([2, 5, , 6]);
1303
+ return [4 /*yield*/, navigator.mediaDevices.getUserMedia({ audio: true })];
1304
+ case 3:
1305
+ stream = _c.sent();
1306
+ audioTrack = stream.getAudioTracks()[0];
1307
+ if (!audioTrack) {
1308
+ stream.getTracks().forEach(function (t) { return t.stop(); });
1309
+ throw new Error('No audio track');
1310
+ }
1311
+ this.localStream = stream;
1312
+ this.localStreamSubject.next(stream);
1313
+ callObject = Daily__default["default"].createCallObject({
1314
+ videoSource: false,
1315
+ audioSource: audioTrack,
1316
+ });
1317
+ this.callObject = callObject;
1318
+ this.setupEventHandlers(callObject);
1319
+ // Join room; Daily handles playback of remote (bot) audio automatically
1320
+ return [4 /*yield*/, callObject.join({ url: roomUrl, token: token })];
1321
+ case 4:
1322
+ // Join room; Daily handles playback of remote (bot) audio automatically
1323
+ _c.sent();
1324
+ participants = callObject.participants();
1325
+ if (participants === null || participants === void 0 ? void 0 : participants.local) {
1326
+ this.localSessionId = participants.local.session_id;
1327
+ }
1328
+ // Initial mute state: Daily starts with audio on
1329
+ this.micMutedSubject.next(!callObject.localAudio());
1330
+ return [3 /*break*/, 6];
1331
+ case 5:
1332
+ err_1 = _c.sent();
1333
+ this.cleanup();
1334
+ throw err_1;
1335
+ case 6: return [2 /*return*/];
1336
+ }
1337
+ });
1338
+ });
1339
+ };
1340
+ DailyVoiceClientService.prototype.setupEventHandlers = function (call) {
1341
+ var _this = this;
1342
+ // active-speaker-change: primary source for real-time speaking detection.
1343
+ // Emits when the loudest participant changes; bot speaking = remote is active.
1344
+ call.on('active-speaker-change', function (event) {
1345
+ _this.ngZone.run(function () {
1346
+ var _a;
1347
+ var peerId = (_a = event === null || event === void 0 ? void 0 : event.activeSpeaker) === null || _a === void 0 ? void 0 : _a.peerId;
1348
+ if (!peerId || !_this.localSessionId) {
1349
+ _this.speakingSubject.next(false);
1350
+ _this.userSpeakingSubject.next(false);
1351
+ return;
1352
+ }
1353
+ var isLocal = peerId === _this.localSessionId;
1354
+ _this.userSpeakingSubject.next(isLocal);
1355
+ _this.speakingSubject.next(!isLocal);
1356
+ });
1357
+ });
1358
+ // track-started / track-stopped: fallback for immediate feedback when
1359
+ // remote (bot) audio track starts or stops. Ensures talking indicator
1360
+ // flips as soon as agent audio begins, without waiting for active-speaker-change.
1361
+ call.on('track-started', function (event) {
1362
+ _this.ngZone.run(function () {
1363
+ var _a, _b;
1364
+ var p = event === null || event === void 0 ? void 0 : event.participant;
1365
+ 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;
1366
+ if (p && !p.local && type === 'audio') {
1367
+ _this.speakingSubject.next(true);
1368
+ }
1369
+ });
1370
+ });
1371
+ call.on('track-stopped', function (event) {
1372
+ _this.ngZone.run(function () {
1373
+ var _a, _b;
1374
+ var p = event === null || event === void 0 ? void 0 : event.participant;
1375
+ 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;
1376
+ if (p && !p.local && type === 'audio') {
1377
+ _this.speakingSubject.next(false);
1378
+ }
1379
+ });
1380
+ });
1381
+ call.on('left-meeting', function () {
1382
+ _this.ngZone.run(function () { return _this.cleanup(); });
1383
+ });
1384
+ call.on('error', function (event) {
1385
+ _this.ngZone.run(function () {
1386
+ var _a;
1387
+ console.error('DailyVoiceClient: Daily error', (_a = event === null || event === void 0 ? void 0 : event.errorMsg) !== null && _a !== void 0 ? _a : event);
1388
+ _this.cleanup();
1389
+ });
1390
+ });
1391
+ };
1392
+ /** Set mic muted state. */
1393
+ DailyVoiceClientService.prototype.setMuted = function (muted) {
1394
+ if (!this.callObject)
1395
+ return;
1396
+ this.callObject.setLocalAudio(!muted);
1397
+ this.micMutedSubject.next(muted);
1398
+ };
1399
+ /** Disconnect and cleanup. */
1400
+ DailyVoiceClientService.prototype.disconnect = function () {
1401
+ return __awaiter(this, void 0, void 0, function () {
1402
+ var e_1;
1403
+ return __generator(this, function (_c) {
1404
+ switch (_c.label) {
1405
+ case 0:
1406
+ if (!this.callObject) {
1407
+ this.cleanup();
1408
+ return [2 /*return*/];
1409
+ }
1410
+ _c.label = 1;
1411
+ case 1:
1412
+ _c.trys.push([1, 3, , 4]);
1413
+ return [4 /*yield*/, this.callObject.leave()];
1414
+ case 2:
1415
+ _c.sent();
1416
+ return [3 /*break*/, 4];
1417
+ case 3:
1418
+ e_1 = _c.sent();
1419
+ return [3 /*break*/, 4];
1420
+ case 4:
1421
+ this.cleanup();
1422
+ return [2 /*return*/];
1423
+ }
1424
+ });
1425
+ });
1426
+ };
1427
+ DailyVoiceClientService.prototype.cleanup = function () {
1428
+ if (this.callObject) {
1429
+ this.callObject.destroy().catch(function () { });
1430
+ this.callObject = null;
1431
+ }
1432
+ if (this.localStream) {
1433
+ this.localStream.getTracks().forEach(function (t) { return t.stop(); });
1434
+ this.localStream = null;
1435
+ }
1436
+ this.localSessionId = null;
1437
+ this.speakingSubject.next(false);
1438
+ this.userSpeakingSubject.next(false);
1439
+ this.localStreamSubject.next(null);
1440
+ // Keep last micMuted state; will reset on next connect
1441
+ };
1442
+ return DailyVoiceClientService;
1443
+ }());
1444
+ DailyVoiceClientService.ɵprov = i0__namespace.ɵɵdefineInjectable({ factory: function DailyVoiceClientService_Factory() { return new DailyVoiceClientService(i0__namespace.ɵɵinject(i0__namespace.NgZone)); }, token: DailyVoiceClientService, providedIn: "root" });
1445
+ DailyVoiceClientService.decorators = [
1446
+ { type: i0.Injectable, args: [{
1447
+ providedIn: 'root',
1448
+ },] }
1449
+ ];
1450
+ DailyVoiceClientService.ctorParameters = function () { return [
1451
+ { type: i0.NgZone }
1452
+ ]; };
1453
+
1454
+ /**
1455
+ * Voice agent orchestrator. Coordinates WebSocket (signaling) and Daily.js (WebRTC audio).
1456
+ * - Maintains callState, statusText, duration, isMicMuted, isUserSpeaking, audioLevels
1457
+ * - Uses WebSocket for room_created and transcripts only (no audio)
1458
+ * - Uses Daily.js for all audio, mic, and real-time speaking detection
1459
+ */
1149
1460
  var VoiceAgentService = /** @class */ (function () {
1150
- function VoiceAgentService(audioAnalyzer) {
1461
+ function VoiceAgentService(audioAnalyzer, wsClient, dailyClient) {
1151
1462
  var _this = this;
1152
1463
  this.audioAnalyzer = audioAnalyzer;
1153
- this.client = null;
1154
- this.transport = null;
1155
- this.userMediaStream = null;
1156
- this.botAudioElement = null;
1464
+ this.wsClient = wsClient;
1465
+ this.dailyClient = dailyClient;
1157
1466
  this.callStateSubject = new rxjs.BehaviorSubject('idle');
1158
1467
  this.statusTextSubject = new rxjs.BehaviorSubject('');
1159
1468
  this.durationSubject = new rxjs.BehaviorSubject('00:00');
@@ -1164,6 +1473,8 @@
1164
1473
  this.botTranscriptSubject = new rxjs.Subject();
1165
1474
  this.callStartTime = 0;
1166
1475
  this.durationInterval = null;
1476
+ this.subscriptions = new rxjs.Subscription();
1477
+ this.destroy$ = new rxjs.Subject();
1167
1478
  this.callState$ = this.callStateSubject.asObservable();
1168
1479
  this.statusText$ = this.statusTextSubject.asObservable();
1169
1480
  this.duration$ = this.durationSubject.asObservable();
@@ -1172,39 +1483,23 @@
1172
1483
  this.audioLevels$ = this.audioLevelsSubject.asObservable();
1173
1484
  this.userTranscript$ = this.userTranscriptSubject.asObservable();
1174
1485
  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
- });
1486
+ // Waveform visualization only - do NOT use for speaking state
1487
+ this.subscriptions.add(this.audioAnalyzer.audioLevels$.subscribe(function (levels) { return _this.audioLevelsSubject.next(levels); }));
1187
1488
  }
1489
+ VoiceAgentService.prototype.ngOnDestroy = function () {
1490
+ this.destroy$.next();
1491
+ this.subscriptions.unsubscribe();
1492
+ this.disconnect();
1493
+ };
1188
1494
  /** Reset to idle state (e.g. when modal opens so user can click Start Call). */
1189
1495
  VoiceAgentService.prototype.resetToIdle = function () {
1190
1496
  if (this.callStateSubject.value === 'idle')
1191
1497
  return;
1192
- if (this.durationInterval) {
1193
- clearInterval(this.durationInterval);
1194
- this.durationInterval = null;
1195
- }
1498
+ this.stopDurationTimer();
1196
1499
  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
- }
1500
+ this.wsClient.disconnect();
1501
+ // Fire-and-forget: Daily disconnect is async; connect() will await if needed
1502
+ void this.dailyClient.disconnect();
1208
1503
  this.callStateSubject.next('idle');
1209
1504
  this.statusTextSubject.next('');
1210
1505
  this.durationSubject.next('0:00');
@@ -1212,7 +1507,8 @@
1212
1507
  VoiceAgentService.prototype.connect = function (apiUrl, token, botId, conversationId, apiKey, eventToken, eventUrl, domainAuthority) {
1213
1508
  var _a;
1214
1509
  return __awaiter(this, void 0, void 0, function () {
1215
- var baseUrl, connectUrl, headers, startBotParams, tracks, error_1;
1510
+ var baseUrl, postUrl, headers, res, json, wsUrl, error_1;
1511
+ var _this = this;
1216
1512
  return __generator(this, function (_b) {
1217
1513
  switch (_b.label) {
1218
1514
  case 0:
@@ -1222,152 +1518,160 @@
1222
1518
  }
1223
1519
  _b.label = 1;
1224
1520
  case 1:
1225
- _b.trys.push([1, 3, , 5]);
1521
+ _b.trys.push([1, 4, , 6]);
1226
1522
  this.callStateSubject.next('connecting');
1227
1523
  this.statusTextSubject.next('Connecting...');
1228
1524
  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
- },
1525
+ postUrl = baseUrl + "/ai/ask-voice";
1526
+ headers = {
1527
+ 'Content-Type': 'application/json',
1528
+ Authorization: "Bearer " + token,
1529
+ 'domain-authority': domainAuthority,
1530
+ 'eventtoken': eventToken,
1531
+ 'eventurl': eventUrl,
1532
+ 'hive-bot-id': botId,
1533
+ 'x-api-key': apiKey,
1246
1534
  };
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)];
1535
+ return [4 /*yield*/, fetch(postUrl, {
1536
+ method: 'POST',
1537
+ headers: headers,
1538
+ body: JSON.stringify({
1539
+ bot_id: botId,
1540
+ conversation_id: conversationId,
1541
+ voice: 'alloy',
1542
+ }),
1543
+ })];
1257
1544
  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);
1545
+ res = _b.sent();
1546
+ if (!res.ok) {
1547
+ throw new Error("HTTP " + res.status);
1263
1548
  }
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];
1549
+ return [4 /*yield*/, res.json()];
1270
1550
  case 3:
1551
+ json = _b.sent();
1552
+ 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;
1553
+ if (!wsUrl || typeof wsUrl !== 'string') {
1554
+ throw new Error('No ws_url in response');
1555
+ }
1556
+ // Subscribe to room_created BEFORE connecting to avoid race
1557
+ this.wsClient.roomCreated$
1558
+ .pipe(operators.take(1), operators.takeUntil(this.destroy$))
1559
+ .subscribe(function (roomUrl) { return __awaiter(_this, void 0, void 0, function () {
1560
+ var err_1;
1561
+ return __generator(this, function (_b) {
1562
+ switch (_b.label) {
1563
+ case 0:
1564
+ _b.trys.push([0, 2, , 4]);
1565
+ return [4 /*yield*/, this.onRoomCreated(roomUrl)];
1566
+ case 1:
1567
+ _b.sent();
1568
+ return [3 /*break*/, 4];
1569
+ case 2:
1570
+ err_1 = _b.sent();
1571
+ console.error('Daily join failed:', err_1);
1572
+ this.callStateSubject.next('ended');
1573
+ this.statusTextSubject.next('Connection failed');
1574
+ return [4 /*yield*/, this.disconnect()];
1575
+ case 3:
1576
+ _b.sent();
1577
+ throw err_1;
1578
+ case 4: return [2 /*return*/];
1579
+ }
1580
+ });
1581
+ }); });
1582
+ // Forward transcripts from WebSocket
1583
+ this.subscriptions.add(this.wsClient.userTranscript$
1584
+ .pipe(operators.takeUntil(this.destroy$))
1585
+ .subscribe(function (t) { return _this.userTranscriptSubject.next(t); }));
1586
+ this.subscriptions.add(this.wsClient.botTranscript$
1587
+ .pipe(operators.takeUntil(this.destroy$))
1588
+ .subscribe(function (t) { return _this.botTranscriptSubject.next(t); }));
1589
+ // Connect signaling WebSocket (no audio over WS)
1590
+ this.wsClient.connect(wsUrl);
1591
+ return [3 /*break*/, 6];
1592
+ case 4:
1271
1593
  error_1 = _b.sent();
1272
1594
  console.error('Error connecting voice agent:', error_1);
1273
1595
  this.callStateSubject.next('ended');
1274
1596
  return [4 /*yield*/, this.disconnect()];
1275
- case 4:
1597
+ case 5:
1276
1598
  _b.sent();
1277
1599
  this.statusTextSubject.next('Connection failed');
1278
1600
  throw error_1;
1279
- case 5: return [2 /*return*/];
1601
+ case 6: return [2 /*return*/];
1602
+ }
1603
+ });
1604
+ });
1605
+ };
1606
+ VoiceAgentService.prototype.onRoomCreated = function (roomUrl) {
1607
+ return __awaiter(this, void 0, void 0, function () {
1608
+ var _this = this;
1609
+ return __generator(this, function (_b) {
1610
+ switch (_b.label) {
1611
+ case 0:
1612
+ // Connect Daily.js for WebRTC audio
1613
+ return [4 /*yield*/, this.dailyClient.connect(roomUrl)];
1614
+ case 1:
1615
+ // Connect Daily.js for WebRTC audio
1616
+ _b.sent();
1617
+ // Waveform: use local mic stream from Daily client
1618
+ this.dailyClient.localStream$
1619
+ .pipe(operators.filter(function (s) { return s != null; }), operators.take(1))
1620
+ .subscribe(function (stream) {
1621
+ _this.audioAnalyzer.start(stream);
1622
+ });
1623
+ // Speaking state from Daily only (NOT from AudioAnalyzer).
1624
+ // User speaking overrides bot talking: if user speaks while bot talks, state = listening.
1625
+ this.subscriptions.add(this.dailyClient.userSpeaking$.subscribe(function (s) { return _this.isUserSpeakingSubject.next(s); }));
1626
+ this.subscriptions.add(rxjs.combineLatest([
1627
+ this.dailyClient.speaking$,
1628
+ this.dailyClient.userSpeaking$,
1629
+ ]).subscribe(function (_b) {
1630
+ var _c = __read(_b, 2), bot = _c[0], user = _c[1];
1631
+ if (user) {
1632
+ _this.callStateSubject.next('listening');
1633
+ }
1634
+ else if (bot) {
1635
+ _this.callStateSubject.next('talking');
1636
+ }
1637
+ else if (_this.callStateSubject.value === 'talking' ||
1638
+ _this.callStateSubject.value === 'listening') {
1639
+ _this.callStateSubject.next('connected');
1640
+ }
1641
+ }));
1642
+ this.subscriptions.add(this.dailyClient.micMuted$.subscribe(function (muted) { return _this.isMicMutedSubject.next(muted); }));
1643
+ this.callStateSubject.next('connected');
1644
+ this.statusTextSubject.next('Connected');
1645
+ this.callStartTime = Date.now();
1646
+ this.startDurationTimer();
1647
+ return [2 /*return*/];
1280
1648
  }
1281
1649
  });
1282
1650
  });
1283
1651
  };
1284
1652
  VoiceAgentService.prototype.disconnect = function () {
1285
1653
  return __awaiter(this, void 0, void 0, function () {
1286
- var error_2;
1287
1654
  return __generator(this, function (_b) {
1288
1655
  switch (_b.label) {
1289
1656
  case 0:
1290
- _b.trys.push([0, 3, , 4]);
1291
- if (this.durationInterval) {
1292
- clearInterval(this.durationInterval);
1293
- this.durationInterval = null;
1294
- }
1657
+ this.stopDurationTimer();
1295
1658
  this.audioAnalyzer.stop();
1296
- if (!this.client) return [3 /*break*/, 2];
1297
- return [4 /*yield*/, this.client.disconnect()];
1659
+ // Daily first, then WebSocket
1660
+ return [4 /*yield*/, this.dailyClient.disconnect()];
1298
1661
  case 1:
1662
+ // Daily first, then WebSocket
1299
1663
  _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
- }
1664
+ this.wsClient.disconnect();
1313
1665
  this.callStateSubject.next('ended');
1314
1666
  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*/];
1667
+ return [2 /*return*/];
1321
1668
  }
1322
1669
  });
1323
1670
  });
1324
1671
  };
1325
1672
  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
- });
1673
+ var current = this.isMicMutedSubject.value;
1674
+ this.dailyClient.setMuted(!current);
1371
1675
  };
1372
1676
  VoiceAgentService.prototype.startDurationTimer = function () {
1373
1677
  var _this = this;
@@ -1379,19 +1683,27 @@
1379
1683
  _this.durationSubject.next(minutes + ":" + String(seconds).padStart(2, '0'));
1380
1684
  }
1381
1685
  };
1382
- updateDuration(); // show 0:00 immediately
1686
+ updateDuration();
1383
1687
  this.durationInterval = setInterval(updateDuration, 1000);
1384
1688
  };
1689
+ VoiceAgentService.prototype.stopDurationTimer = function () {
1690
+ if (this.durationInterval) {
1691
+ clearInterval(this.durationInterval);
1692
+ this.durationInterval = null;
1693
+ }
1694
+ };
1385
1695
  return VoiceAgentService;
1386
1696
  }());
1387
- VoiceAgentService.ɵprov = i0__namespace.ɵɵdefineInjectable({ factory: function VoiceAgentService_Factory() { return new VoiceAgentService(i0__namespace.ɵɵinject(AudioAnalyzerService)); }, token: VoiceAgentService, providedIn: "root" });
1697
+ 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
1698
  VoiceAgentService.decorators = [
1389
1699
  { type: i0.Injectable, args: [{
1390
- providedIn: 'root'
1700
+ providedIn: 'root',
1391
1701
  },] }
1392
1702
  ];
1393
1703
  VoiceAgentService.ctorParameters = function () { return [
1394
- { type: AudioAnalyzerService }
1704
+ { type: AudioAnalyzerService },
1705
+ { type: WebSocketVoiceClientService },
1706
+ { type: DailyVoiceClientService }
1395
1707
  ]; };
1396
1708
 
1397
1709
  var VOICE_MODAL_CONFIG = new i0.InjectionToken('VOICE_MODAL_CONFIG');
@@ -3572,7 +3884,9 @@
3572
3884
  ],
3573
3885
  providers: [
3574
3886
  VoiceAgentService,
3575
- AudioAnalyzerService
3887
+ AudioAnalyzerService,
3888
+ WebSocketVoiceClientService,
3889
+ DailyVoiceClientService
3576
3890
  ],
3577
3891
  exports: [
3578
3892
  VoiceAgentModalComponent
@@ -3870,9 +4184,11 @@
3870
4184
  exports["ɵc"] = ConversationService;
3871
4185
  exports["ɵd"] = NotificationSocket;
3872
4186
  exports["ɵe"] = TranslationService;
3873
- exports["ɵf"] = VideoPlayerComponent;
3874
- exports["ɵg"] = SafeHtmlPipe;
3875
- exports["ɵh"] = BotHtmlEditorComponent;
4187
+ exports["ɵf"] = WebSocketVoiceClientService;
4188
+ exports["ɵg"] = DailyVoiceClientService;
4189
+ exports["ɵh"] = VideoPlayerComponent;
4190
+ exports["ɵi"] = SafeHtmlPipe;
4191
+ exports["ɵj"] = BotHtmlEditorComponent;
3876
4192
 
3877
4193
  Object.defineProperty(exports, '__esModule', { value: true });
3878
4194