@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.
- package/bundles/hivegpt-hiveai-angular.umd.js +420 -490
- package/bundles/hivegpt-hiveai-angular.umd.js.map +1 -1
- package/bundles/hivegpt-hiveai-angular.umd.min.js +1 -1
- package/bundles/hivegpt-hiveai-angular.umd.min.js.map +1 -1
- package/esm2015/hivegpt-hiveai-angular.js +4 -5
- package/esm2015/lib/components/voice-agent/services/audio-analyzer.service.js +3 -3
- package/esm2015/lib/components/voice-agent/services/voice-agent.service.js +195 -83
- package/esm2015/lib/components/voice-agent/services/websocket-voice-client.service.js +160 -49
- package/esm2015/lib/components/voice-agent/voice-agent.module.js +3 -5
- package/fesm2015/hivegpt-hiveai-angular.js +338 -416
- package/fesm2015/hivegpt-hiveai-angular.js.map +1 -1
- package/hivegpt-hiveai-angular.d.ts +3 -4
- package/hivegpt-hiveai-angular.d.ts.map +1 -1
- package/hivegpt-hiveai-angular.metadata.json +1 -1
- package/lib/components/voice-agent/services/audio-analyzer.service.d.ts +2 -2
- package/lib/components/voice-agent/services/voice-agent.service.d.ts +22 -13
- package/lib/components/voice-agent/services/voice-agent.service.d.ts.map +1 -1
- package/lib/components/voice-agent/services/websocket-voice-client.service.d.ts +30 -20
- package/lib/components/voice-agent/services/websocket-voice-client.service.d.ts.map +1 -1
- package/lib/components/voice-agent/voice-agent.module.d.ts +1 -1
- package/lib/components/voice-agent/voice-agent.module.d.ts.map +1 -1
- package/package.json +1 -1
- package/esm2015/lib/components/voice-agent/services/daily-voice-client.service.js +0 -305
- package/lib/components/voice-agent/services/daily-voice-client.service.d.ts +0 -62
- 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('@
|
|
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', '@
|
|
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.
|
|
5
|
-
})(this, (function (exports, overlay, portal, i1, i0, platformBrowser, rxjs, operators, common, ngxSocketIo, forms, SpeechSDK, marked,
|
|
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
|
|
1222
|
-
*
|
|
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
|
|
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
|
-
*
|
|
1349
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
1361
|
-
this.
|
|
1362
|
-
|
|
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
|
-
|
|
1380
|
-
this.ws
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
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
|
-
|
|
1401
|
-
|
|
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
|
-
|
|
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
|
|
1485
|
-
return __generator(this, function (
|
|
1486
|
-
switch (
|
|
1423
|
+
var ab;
|
|
1424
|
+
return __generator(this, function (_b) {
|
|
1425
|
+
switch (_b.label) {
|
|
1487
1426
|
case 0:
|
|
1488
|
-
if (
|
|
1489
|
-
|
|
1490
|
-
|
|
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
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
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
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1598
|
-
|
|
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 (
|
|
1626
|
-
|
|
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
|
-
|
|
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
|
|
1638
|
-
var
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
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 (
|
|
1679
|
-
|
|
1476
|
+
catch (_a) {
|
|
1477
|
+
return null;
|
|
1680
1478
|
}
|
|
1681
1479
|
};
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
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 (
|
|
1688
|
-
|
|
1689
|
-
|
|
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
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
this.
|
|
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
|
-
|
|
1699
|
-
this.remoteAudioElement = null;
|
|
1503
|
+
return;
|
|
1700
1504
|
}
|
|
1701
|
-
|
|
1702
|
-
|
|
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
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
return
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
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 (
|
|
1745
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
1757
|
-
|
|
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
|
-
|
|
1556
|
+
WebSocketVoiceClientService.ctorParameters = function () { return [
|
|
1763
1557
|
{ type: i0.NgZone }
|
|
1764
1558
|
]; };
|
|
1765
1559
|
|
|
1766
1560
|
/**
|
|
1767
|
-
* Voice agent orchestrator
|
|
1768
|
-
*
|
|
1769
|
-
*
|
|
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,
|
|
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 (
|
|
1833
|
-
switch (
|
|
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
|
-
|
|
1638
|
+
_b.label = 1;
|
|
1840
1639
|
case 1:
|
|
1841
|
-
|
|
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
|
-
|
|
1645
|
+
_b.label = 2;
|
|
1847
1646
|
case 2:
|
|
1848
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1894
|
-
wsUrl = json === null || json === void 0 ? void 0 : json.
|
|
1895
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
1703
|
+
.pipe(operators.takeUntil(untilCallEnds$))
|
|
1930
1704
|
.subscribe(function (t) { return _this.botTranscriptSubject.next(t); }));
|
|
1931
|
-
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
1724
|
+
VoiceAgentService.prototype.onWebsocketOpened = function () {
|
|
1949
1725
|
return __awaiter(this, void 0, void 0, function () {
|
|
1950
|
-
var
|
|
1951
|
-
return __generator(this, function (
|
|
1952
|
-
switch (
|
|
1953
|
-
case 0:
|
|
1954
|
-
|
|
1955
|
-
|
|
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
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
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.
|
|
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
|
-
|
|
2001
|
-
|
|
1833
|
+
var context, chunk, decoded, _a_1;
|
|
1834
|
+
return __generator(this, function (_b) {
|
|
1835
|
+
switch (_b.label) {
|
|
2002
1836
|
case 0:
|
|
2003
|
-
this.
|
|
2004
|
-
this.
|
|
2005
|
-
|
|
2006
|
-
return [4 /*yield*/, this.dailyClient.disconnect()];
|
|
1837
|
+
this.remoteAudioPlaying = true;
|
|
1838
|
+
context = this.getOrCreateRemoteAudioContext();
|
|
1839
|
+
_b.label = 1;
|
|
2007
1840
|
case 1:
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
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
|
|
2020
|
-
this.
|
|
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(
|
|
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
|
|
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"] =
|
|
6557
|
-
exports["ɵh"] =
|
|
6558
|
-
exports["ɵi"] =
|
|
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
|
|