@hivegpt/hiveai-angular 0.0.574 → 0.0.576
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 +542 -278
- 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/lib/components/chat-drawer/chat-drawer.component.js +6 -6
- package/esm2015/lib/components/voice-agent/components/voice-agent-modal/voice-agent-modal.component.js +85 -54
- package/esm2015/lib/components/voice-agent/services/daily-voice-client.service.js +153 -63
- package/esm2015/lib/components/voice-agent/services/voice-agent.service.js +160 -88
- package/esm2015/lib/components/voice-agent/services/websocket-voice-client.service.js +81 -34
- package/fesm2015/hivegpt-hiveai-angular.js +478 -239
- package/fesm2015/hivegpt-hiveai-angular.js.map +1 -1
- package/hivegpt-hiveai-angular.metadata.json +1 -1
- package/lib/components/voice-agent/components/voice-agent-modal/voice-agent-modal.component.d.ts +7 -1
- package/lib/components/voice-agent/components/voice-agent-modal/voice-agent-modal.component.d.ts.map +1 -1
- package/lib/components/voice-agent/services/daily-voice-client.service.d.ts +37 -3
- package/lib/components/voice-agent/services/daily-voice-client.service.d.ts.map +1 -1
- package/lib/components/voice-agent/services/voice-agent.service.d.ts +19 -6
- 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 +5 -12
- package/lib/components/voice-agent/services/websocket-voice-client.service.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1349,6 +1349,7 @@
|
|
|
1349
1349
|
* - Emit roomCreated$, userTranscript$, botTranscript$
|
|
1350
1350
|
* - NO audio logic, NO mic logic. Audio is handled by Daily.js (WebRTC).
|
|
1351
1351
|
*/
|
|
1352
|
+
var WS_CONNECT_TIMEOUT_MS = 10000;
|
|
1352
1353
|
var WebSocketVoiceClientService = /** @class */ (function () {
|
|
1353
1354
|
function WebSocketVoiceClientService() {
|
|
1354
1355
|
this.ws = null;
|
|
@@ -1362,55 +1363,101 @@
|
|
|
1362
1363
|
/** Emits bot transcript updates. */
|
|
1363
1364
|
this.botTranscript$ = this.botTranscriptSubject.asObservable();
|
|
1364
1365
|
}
|
|
1365
|
-
/**
|
|
1366
|
+
/**
|
|
1367
|
+
* Connect to signaling WebSocket. No audio over this connection.
|
|
1368
|
+
* Resolves when the socket is open; rejects if the connection fails.
|
|
1369
|
+
*/
|
|
1366
1370
|
WebSocketVoiceClientService.prototype.connect = function (wsUrl) {
|
|
1367
1371
|
var _this = this;
|
|
1368
1372
|
var _a;
|
|
1369
1373
|
if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN) {
|
|
1370
|
-
return;
|
|
1374
|
+
return Promise.resolve();
|
|
1371
1375
|
}
|
|
1372
1376
|
if (this.ws) {
|
|
1373
1377
|
this.ws.close();
|
|
1374
1378
|
this.ws = null;
|
|
1375
1379
|
}
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1380
|
+
return new Promise(function (resolve, reject) {
|
|
1381
|
+
var settled = false;
|
|
1382
|
+
var timeout = setTimeout(function () {
|
|
1379
1383
|
var _a;
|
|
1384
|
+
if (settled)
|
|
1385
|
+
return;
|
|
1386
|
+
settled = true;
|
|
1380
1387
|
try {
|
|
1381
|
-
|
|
1382
|
-
if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'room_created') {
|
|
1383
|
-
var roomUrl = ((_a = msg.room_url) !== null && _a !== void 0 ? _a : msg.roomUrl);
|
|
1384
|
-
if (typeof roomUrl === 'string') {
|
|
1385
|
-
_this.roomCreatedSubject.next(roomUrl);
|
|
1386
|
-
}
|
|
1387
|
-
}
|
|
1388
|
-
else if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'user_transcript' && typeof msg.text === 'string') {
|
|
1389
|
-
_this.userTranscriptSubject.next({
|
|
1390
|
-
text: msg.text,
|
|
1391
|
-
final: msg.final === true,
|
|
1392
|
-
});
|
|
1393
|
-
}
|
|
1394
|
-
else if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'bot_transcript' && typeof msg.text === 'string') {
|
|
1395
|
-
_this.botTranscriptSubject.next(msg.text);
|
|
1396
|
-
}
|
|
1388
|
+
(_a = _this.ws) === null || _a === void 0 ? void 0 : _a.close();
|
|
1397
1389
|
}
|
|
1398
1390
|
catch (_b) {
|
|
1399
|
-
|
|
1391
|
+
/* ignore */
|
|
1400
1392
|
}
|
|
1401
|
-
};
|
|
1402
|
-
this.ws.onerror = function () {
|
|
1403
|
-
_this.disconnect();
|
|
1404
|
-
};
|
|
1405
|
-
this.ws.onclose = function () {
|
|
1406
1393
|
_this.ws = null;
|
|
1394
|
+
reject(new Error('WebSocket connection timed out'));
|
|
1395
|
+
}, WS_CONNECT_TIMEOUT_MS);
|
|
1396
|
+
var clear = function () {
|
|
1397
|
+
clearTimeout(timeout);
|
|
1407
1398
|
};
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1399
|
+
try {
|
|
1400
|
+
var ws = new WebSocket(wsUrl);
|
|
1401
|
+
_this.ws = ws;
|
|
1402
|
+
ws.onopen = function () {
|
|
1403
|
+
if (settled)
|
|
1404
|
+
return;
|
|
1405
|
+
settled = true;
|
|
1406
|
+
clear();
|
|
1407
|
+
resolve();
|
|
1408
|
+
};
|
|
1409
|
+
ws.onmessage = function (event) {
|
|
1410
|
+
var _a;
|
|
1411
|
+
try {
|
|
1412
|
+
var msg = JSON.parse(event.data);
|
|
1413
|
+
if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'room_created') {
|
|
1414
|
+
var roomUrl = ((_a = msg.room_url) !== null && _a !== void 0 ? _a : msg.roomUrl);
|
|
1415
|
+
if (typeof roomUrl === 'string') {
|
|
1416
|
+
_this.roomCreatedSubject.next(roomUrl);
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
else if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'user_transcript' && typeof msg.text === 'string') {
|
|
1420
|
+
_this.userTranscriptSubject.next({
|
|
1421
|
+
text: msg.text,
|
|
1422
|
+
final: msg.final === true,
|
|
1423
|
+
});
|
|
1424
|
+
}
|
|
1425
|
+
else if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'bot_transcript' && typeof msg.text === 'string') {
|
|
1426
|
+
_this.botTranscriptSubject.next(msg.text);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
catch (_b) {
|
|
1430
|
+
// Ignore non-JSON or unknown messages
|
|
1431
|
+
}
|
|
1432
|
+
};
|
|
1433
|
+
ws.onerror = function () {
|
|
1434
|
+
if (!settled) {
|
|
1435
|
+
settled = true;
|
|
1436
|
+
clear();
|
|
1437
|
+
_this.disconnect();
|
|
1438
|
+
reject(new Error('WebSocket connection failed'));
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
// After onopen, some environments fire onerror spuriously; closing here can
|
|
1442
|
+
// kill the socket before room_created is delivered. Let onclose clean up.
|
|
1443
|
+
console.warn('WebSocketVoiceClient: onerror after open (not forcing disconnect)');
|
|
1444
|
+
};
|
|
1445
|
+
ws.onclose = function () {
|
|
1446
|
+
_this.ws = null;
|
|
1447
|
+
if (!settled) {
|
|
1448
|
+
settled = true;
|
|
1449
|
+
clear();
|
|
1450
|
+
reject(new Error('WebSocket connection failed'));
|
|
1451
|
+
}
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1454
|
+
catch (err) {
|
|
1455
|
+
clear();
|
|
1456
|
+
console.error('WebSocketVoiceClient: connect failed', err);
|
|
1457
|
+
_this.ws = null;
|
|
1458
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
1459
|
+
}
|
|
1460
|
+
});
|
|
1414
1461
|
};
|
|
1415
1462
|
/** Disconnect and cleanup. */
|
|
1416
1463
|
WebSocketVoiceClientService.prototype.disconnect = function () {
|
|
@@ -1437,27 +1484,51 @@
|
|
|
1437
1484
|
},] }
|
|
1438
1485
|
];
|
|
1439
1486
|
|
|
1487
|
+
/**
|
|
1488
|
+
* Daily.js WebRTC client for voice agent audio.
|
|
1489
|
+
* Responsibilities:
|
|
1490
|
+
* - Create and manage Daily CallObject
|
|
1491
|
+
* - Join Daily room using room_url
|
|
1492
|
+
* - Handle mic capture + speaker playback
|
|
1493
|
+
* - Bot speaking detection via AnalyserNode on remote track (instant)
|
|
1494
|
+
* - User speaking detection via active-speaker-change
|
|
1495
|
+
* - Expose speaking$ (bot speaking), userSpeaking$ (user speaking), micMuted$
|
|
1496
|
+
* - Expose localStream$ for waveform visualization (AudioAnalyzerService)
|
|
1497
|
+
*/
|
|
1440
1498
|
var DailyVoiceClientService = /** @class */ (function () {
|
|
1441
1499
|
function DailyVoiceClientService(ngZone) {
|
|
1442
1500
|
this.ngZone = ngZone;
|
|
1443
1501
|
this.callObject = null;
|
|
1444
1502
|
this.localStream = null;
|
|
1445
1503
|
this.localSessionId = null;
|
|
1504
|
+
/** Explicit playback of remote (bot) audio; required in some browsers. */
|
|
1446
1505
|
this.remoteAudioElement = null;
|
|
1506
|
+
/** AnalyserNode-based remote audio monitor for instant bot speaking detection. */
|
|
1447
1507
|
this.remoteAudioContext = null;
|
|
1448
|
-
|
|
1508
|
+
/** Poll interval id (~100ms); named historically when RAF was used. */
|
|
1509
|
+
this.remoteSpeakingPollId = null;
|
|
1449
1510
|
this.speakingSubject = new rxjs.BehaviorSubject(false);
|
|
1450
1511
|
this.userSpeakingSubject = new rxjs.BehaviorSubject(false);
|
|
1451
|
-
this.micMutedSubject = new rxjs.BehaviorSubject(
|
|
1512
|
+
this.micMutedSubject = new rxjs.BehaviorSubject(false);
|
|
1452
1513
|
this.localStreamSubject = new rxjs.BehaviorSubject(null);
|
|
1514
|
+
/** True when bot (remote participant) is the active speaker. */
|
|
1453
1515
|
this.speaking$ = this.speakingSubject.asObservable();
|
|
1516
|
+
/** True when user (local participant) is the active speaker. */
|
|
1454
1517
|
this.userSpeaking$ = this.userSpeakingSubject.asObservable();
|
|
1518
|
+
/** True when mic is muted. */
|
|
1455
1519
|
this.micMuted$ = this.micMutedSubject.asObservable();
|
|
1520
|
+
/** Emits local mic stream for waveform visualization. */
|
|
1456
1521
|
this.localStream$ = this.localStreamSubject.asObservable();
|
|
1457
1522
|
}
|
|
1458
|
-
|
|
1523
|
+
/**
|
|
1524
|
+
* Connect to Daily room. Acquires mic first for waveform, then joins with audio.
|
|
1525
|
+
* @param roomUrl Daily room URL (from room_created)
|
|
1526
|
+
* @param token Optional meeting token
|
|
1527
|
+
* @param existingStream Optional pre-acquired mic (avoids a second getUserMedia / extra prompts on some browsers)
|
|
1528
|
+
*/
|
|
1529
|
+
DailyVoiceClientService.prototype.connect = function (roomUrl, token, existingStream) {
|
|
1459
1530
|
return __awaiter(this, void 0, void 0, function () {
|
|
1460
|
-
var stream, audioTrack, callObject, joinOptions, participants, err_1;
|
|
1531
|
+
var hasLiveTrack, stream, _e, audioTrack, callObject, joinOptions, participants, err_1;
|
|
1461
1532
|
return __generator(this, function (_f) {
|
|
1462
1533
|
switch (_f.label) {
|
|
1463
1534
|
case 0:
|
|
@@ -1467,13 +1538,22 @@
|
|
|
1467
1538
|
_f.sent();
|
|
1468
1539
|
_f.label = 2;
|
|
1469
1540
|
case 2:
|
|
1470
|
-
_f.trys.push([2,
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1541
|
+
_f.trys.push([2, 7, , 8]);
|
|
1542
|
+
hasLiveTrack = !!(existingStream === null || existingStream === void 0 ? void 0 : existingStream.getAudioTracks().some(function (t) { return t.readyState === 'live'; }));
|
|
1543
|
+
if (!hasLiveTrack) return [3 /*break*/, 3];
|
|
1544
|
+
_e = existingStream;
|
|
1545
|
+
return [3 /*break*/, 5];
|
|
1546
|
+
case 3: return [4 /*yield*/, navigator.mediaDevices.getUserMedia({ audio: true })];
|
|
1547
|
+
case 4:
|
|
1548
|
+
_e = _f.sent();
|
|
1549
|
+
_f.label = 5;
|
|
1550
|
+
case 5:
|
|
1551
|
+
stream = _e;
|
|
1474
1552
|
audioTrack = stream.getAudioTracks()[0];
|
|
1475
|
-
if (!audioTrack)
|
|
1553
|
+
if (!audioTrack) {
|
|
1554
|
+
stream.getTracks().forEach(function (t) { return t.stop(); });
|
|
1476
1555
|
throw new Error('No audio track');
|
|
1556
|
+
}
|
|
1477
1557
|
this.localStream = stream;
|
|
1478
1558
|
this.localStreamSubject.next(stream);
|
|
1479
1559
|
callObject = Daily__default["default"].createCallObject({
|
|
@@ -1482,36 +1562,34 @@
|
|
|
1482
1562
|
});
|
|
1483
1563
|
this.callObject = callObject;
|
|
1484
1564
|
this.setupEventHandlers(callObject);
|
|
1485
|
-
// 🔴 Ensure mic is OFF before join
|
|
1486
|
-
callObject.setLocalAudio(false);
|
|
1487
|
-
this.micMutedSubject.next(true);
|
|
1488
1565
|
joinOptions = { url: roomUrl };
|
|
1489
|
-
if (typeof token === 'string' && token.trim()) {
|
|
1566
|
+
if (typeof token === 'string' && token.trim() !== '') {
|
|
1490
1567
|
joinOptions.token = token;
|
|
1491
1568
|
}
|
|
1492
1569
|
return [4 /*yield*/, callObject.join(joinOptions)];
|
|
1493
|
-
case
|
|
1570
|
+
case 6:
|
|
1494
1571
|
_f.sent();
|
|
1495
|
-
console.log("[VoiceDebug]
|
|
1572
|
+
console.log("[VoiceDebug] Room connected (Daily join complete) \u2014 " + new Date().toISOString());
|
|
1496
1573
|
participants = callObject.participants();
|
|
1497
1574
|
if (participants === null || participants === void 0 ? void 0 : participants.local) {
|
|
1498
1575
|
this.localSessionId = participants.local.session_id;
|
|
1499
1576
|
}
|
|
1500
|
-
//
|
|
1501
|
-
callObject.
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
case 5:
|
|
1577
|
+
// Initial mute state: Daily starts with audio on
|
|
1578
|
+
this.micMutedSubject.next(!callObject.localAudio());
|
|
1579
|
+
return [3 /*break*/, 8];
|
|
1580
|
+
case 7:
|
|
1505
1581
|
err_1 = _f.sent();
|
|
1506
1582
|
this.cleanup();
|
|
1507
1583
|
throw err_1;
|
|
1508
|
-
case
|
|
1584
|
+
case 8: return [2 /*return*/];
|
|
1509
1585
|
}
|
|
1510
1586
|
});
|
|
1511
1587
|
});
|
|
1512
1588
|
};
|
|
1513
1589
|
DailyVoiceClientService.prototype.setupEventHandlers = function (call) {
|
|
1514
1590
|
var _this = this;
|
|
1591
|
+
// active-speaker-change: used ONLY for user speaking detection.
|
|
1592
|
+
// Bot speaking is detected by our own AnalyserNode (instant, no debounce).
|
|
1515
1593
|
call.on('active-speaker-change', function (event) {
|
|
1516
1594
|
_this.ngZone.run(function () {
|
|
1517
1595
|
var _a;
|
|
@@ -1520,20 +1598,23 @@
|
|
|
1520
1598
|
_this.userSpeakingSubject.next(false);
|
|
1521
1599
|
return;
|
|
1522
1600
|
}
|
|
1523
|
-
|
|
1601
|
+
var isLocal = peerId === _this.localSessionId;
|
|
1602
|
+
_this.userSpeakingSubject.next(isLocal);
|
|
1524
1603
|
});
|
|
1525
1604
|
});
|
|
1605
|
+
// track-started / track-stopped: set up remote audio playback + AnalyserNode monitor.
|
|
1526
1606
|
call.on('track-started', function (event) {
|
|
1527
1607
|
_this.ngZone.run(function () {
|
|
1528
|
-
var _a, _b, _c, _d
|
|
1608
|
+
var _a, _b, _c, _d;
|
|
1529
1609
|
var p = event === null || event === void 0 ? void 0 : event.participant;
|
|
1530
1610
|
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;
|
|
1611
|
+
var track = event === null || event === void 0 ? void 0 : event.track;
|
|
1531
1612
|
if (p && !p.local && type === 'audio') {
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
_this.playRemoteTrack(
|
|
1536
|
-
_this.monitorRemoteAudio(
|
|
1613
|
+
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());
|
|
1614
|
+
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;
|
|
1615
|
+
if (audioTrack && typeof audioTrack === 'object') {
|
|
1616
|
+
_this.playRemoteTrack(audioTrack);
|
|
1617
|
+
_this.monitorRemoteAudio(audioTrack);
|
|
1537
1618
|
}
|
|
1538
1619
|
}
|
|
1539
1620
|
});
|
|
@@ -1552,27 +1633,57 @@
|
|
|
1552
1633
|
call.on('left-meeting', function () {
|
|
1553
1634
|
_this.ngZone.run(function () { return _this.cleanup(); });
|
|
1554
1635
|
});
|
|
1555
|
-
call.on('error', function (
|
|
1556
|
-
|
|
1557
|
-
|
|
1636
|
+
call.on('error', function (event) {
|
|
1637
|
+
_this.ngZone.run(function () {
|
|
1638
|
+
var _a;
|
|
1639
|
+
console.error('DailyVoiceClient: Daily error', (_a = event === null || event === void 0 ? void 0 : event.errorMsg) !== null && _a !== void 0 ? _a : event);
|
|
1640
|
+
_this.cleanup();
|
|
1641
|
+
});
|
|
1558
1642
|
});
|
|
1559
1643
|
};
|
|
1644
|
+
/**
|
|
1645
|
+
* Play remote (bot) audio track via a dedicated audio element.
|
|
1646
|
+
* Required in many browsers where Daily's internal playback does not output to speakers.
|
|
1647
|
+
*/
|
|
1560
1648
|
DailyVoiceClientService.prototype.playRemoteTrack = function (track) {
|
|
1561
1649
|
this.stopRemoteAudio();
|
|
1562
1650
|
try {
|
|
1651
|
+
console.log("[VoiceDebug] playRemoteTrack called \u2014 track.readyState=" + track.readyState + ", track.muted=" + track.muted + " \u2014 " + new Date().toISOString());
|
|
1652
|
+
track.onunmute = function () {
|
|
1653
|
+
console.log("[VoiceDebug] Remote audio track UNMUTED (audio data arriving) \u2014 " + new Date().toISOString());
|
|
1654
|
+
};
|
|
1563
1655
|
var stream = new MediaStream([track]);
|
|
1564
1656
|
var audio = new Audio();
|
|
1565
1657
|
audio.autoplay = true;
|
|
1566
1658
|
audio.srcObject = stream;
|
|
1567
1659
|
this.remoteAudioElement = audio;
|
|
1568
|
-
audio.
|
|
1569
|
-
console.
|
|
1570
|
-
}
|
|
1660
|
+
audio.onplaying = function () {
|
|
1661
|
+
console.log("[VoiceDebug] Audio element PLAYING (browser started playback) \u2014 " + new Date().toISOString());
|
|
1662
|
+
};
|
|
1663
|
+
var firstTimeUpdate_1 = true;
|
|
1664
|
+
audio.ontimeupdate = function () {
|
|
1665
|
+
if (firstTimeUpdate_1) {
|
|
1666
|
+
firstTimeUpdate_1 = false;
|
|
1667
|
+
console.log("[VoiceDebug] Audio element first TIMEUPDATE (actual audio output) \u2014 " + new Date().toISOString());
|
|
1668
|
+
}
|
|
1669
|
+
};
|
|
1670
|
+
var p = audio.play();
|
|
1671
|
+
if (p && typeof p.then === 'function') {
|
|
1672
|
+
p.then(function () {
|
|
1673
|
+
console.log("[VoiceDebug] audio.play() resolved \u2014 " + new Date().toISOString());
|
|
1674
|
+
}).catch(function (err) {
|
|
1675
|
+
console.warn('DailyVoiceClient: remote audio play failed (may need user gesture)', err);
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1571
1678
|
}
|
|
1572
1679
|
catch (err) {
|
|
1573
|
-
console.warn('
|
|
1680
|
+
console.warn('DailyVoiceClient: failed to create remote audio element', err);
|
|
1574
1681
|
}
|
|
1575
1682
|
};
|
|
1683
|
+
/**
|
|
1684
|
+
* Monitor remote audio track energy via AnalyserNode.
|
|
1685
|
+
* Polls at ~10Hz; sufficient for speaking detection vs ~60fps RAF.
|
|
1686
|
+
*/
|
|
1576
1687
|
DailyVoiceClientService.prototype.monitorRemoteAudio = function (track) {
|
|
1577
1688
|
var _this = this;
|
|
1578
1689
|
this.stopRemoteAudioMonitor();
|
|
@@ -1583,75 +1694,96 @@
|
|
|
1583
1694
|
analyser_1.fftSize = 256;
|
|
1584
1695
|
source.connect(analyser_1);
|
|
1585
1696
|
this.remoteAudioContext = ctx;
|
|
1586
|
-
var
|
|
1587
|
-
var
|
|
1588
|
-
var
|
|
1589
|
-
var
|
|
1590
|
-
|
|
1697
|
+
var dataArray_1 = new Uint8Array(analyser_1.frequencyBinCount);
|
|
1698
|
+
var THRESHOLD_1 = 5;
|
|
1699
|
+
var SILENCE_MS_1 = 1500;
|
|
1700
|
+
var POLL_MS = 100;
|
|
1701
|
+
var lastSoundTime_1 = 0;
|
|
1702
|
+
var isSpeaking_1 = false;
|
|
1703
|
+
this.remoteSpeakingPollId = setInterval(function () {
|
|
1704
|
+
if (!_this.remoteAudioContext) {
|
|
1705
|
+
if (_this.remoteSpeakingPollId) {
|
|
1706
|
+
clearInterval(_this.remoteSpeakingPollId);
|
|
1707
|
+
_this.remoteSpeakingPollId = null;
|
|
1708
|
+
}
|
|
1591
1709
|
return;
|
|
1592
|
-
|
|
1593
|
-
|
|
1710
|
+
}
|
|
1711
|
+
analyser_1.getByteFrequencyData(dataArray_1);
|
|
1712
|
+
var sum = 0;
|
|
1713
|
+
for (var i = 0; i < dataArray_1.length; i++) {
|
|
1714
|
+
sum += dataArray_1[i];
|
|
1715
|
+
}
|
|
1716
|
+
var avg = sum / dataArray_1.length;
|
|
1594
1717
|
var now = Date.now();
|
|
1595
|
-
if (avg >
|
|
1596
|
-
|
|
1597
|
-
if (!
|
|
1598
|
-
|
|
1718
|
+
if (avg > THRESHOLD_1) {
|
|
1719
|
+
lastSoundTime_1 = now;
|
|
1720
|
+
if (!isSpeaking_1) {
|
|
1721
|
+
isSpeaking_1 = true;
|
|
1722
|
+
console.log("[VoiceDebug] Bot audio energy detected (speaking=true) \u2014 avg=" + avg.toFixed(1) + " \u2014 " + new Date().toISOString());
|
|
1599
1723
|
_this.ngZone.run(function () {
|
|
1600
1724
|
_this.userSpeakingSubject.next(false);
|
|
1601
1725
|
_this.speakingSubject.next(true);
|
|
1602
1726
|
});
|
|
1603
1727
|
}
|
|
1604
1728
|
}
|
|
1605
|
-
else if (
|
|
1606
|
-
|
|
1729
|
+
else if (isSpeaking_1 && now - lastSoundTime_1 > SILENCE_MS_1) {
|
|
1730
|
+
isSpeaking_1 = false;
|
|
1731
|
+
console.log("[VoiceDebug] Bot audio silence detected (speaking=false) \u2014 " + new Date().toISOString());
|
|
1607
1732
|
_this.ngZone.run(function () { return _this.speakingSubject.next(false); });
|
|
1608
1733
|
}
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1734
|
+
}, POLL_MS);
|
|
1735
|
+
}
|
|
1736
|
+
catch (err) {
|
|
1737
|
+
console.warn('DailyVoiceClient: failed to create remote audio monitor', err);
|
|
1612
1738
|
}
|
|
1613
|
-
catch (_a) { }
|
|
1614
1739
|
};
|
|
1615
1740
|
DailyVoiceClientService.prototype.stopRemoteAudioMonitor = function () {
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1741
|
+
if (this.remoteSpeakingPollId !== null) {
|
|
1742
|
+
clearInterval(this.remoteSpeakingPollId);
|
|
1743
|
+
this.remoteSpeakingPollId = null;
|
|
1744
|
+
}
|
|
1745
|
+
if (this.remoteAudioContext) {
|
|
1746
|
+
this.remoteAudioContext.close().catch(function () { });
|
|
1747
|
+
this.remoteAudioContext = null;
|
|
1620
1748
|
}
|
|
1621
|
-
(_a = this.remoteAudioContext) === null || _a === void 0 ? void 0 : _a.close().catch(function () { });
|
|
1622
|
-
this.remoteAudioContext = null;
|
|
1623
1749
|
};
|
|
1624
1750
|
DailyVoiceClientService.prototype.stopRemoteAudio = function () {
|
|
1625
1751
|
if (this.remoteAudioElement) {
|
|
1626
|
-
|
|
1627
|
-
|
|
1752
|
+
try {
|
|
1753
|
+
this.remoteAudioElement.pause();
|
|
1754
|
+
this.remoteAudioElement.srcObject = null;
|
|
1755
|
+
}
|
|
1756
|
+
catch (_) { }
|
|
1628
1757
|
this.remoteAudioElement = null;
|
|
1629
1758
|
}
|
|
1630
1759
|
};
|
|
1760
|
+
/** Set mic muted state. */
|
|
1631
1761
|
DailyVoiceClientService.prototype.setMuted = function (muted) {
|
|
1632
1762
|
if (!this.callObject)
|
|
1633
1763
|
return;
|
|
1634
1764
|
this.callObject.setLocalAudio(!muted);
|
|
1635
1765
|
this.micMutedSubject.next(muted);
|
|
1636
|
-
console.log("[VoiceDebug] Mic " + (muted ? 'MUTED' : 'UNMUTED'));
|
|
1637
1766
|
};
|
|
1767
|
+
/** Disconnect and cleanup. */
|
|
1638
1768
|
DailyVoiceClientService.prototype.disconnect = function () {
|
|
1639
1769
|
return __awaiter(this, void 0, void 0, function () {
|
|
1640
|
-
var
|
|
1641
|
-
return __generator(this, function (
|
|
1642
|
-
switch (
|
|
1770
|
+
var e_1;
|
|
1771
|
+
return __generator(this, function (_e) {
|
|
1772
|
+
switch (_e.label) {
|
|
1643
1773
|
case 0:
|
|
1644
|
-
if (!this.callObject)
|
|
1645
|
-
|
|
1646
|
-
|
|
1774
|
+
if (!this.callObject) {
|
|
1775
|
+
this.cleanup();
|
|
1776
|
+
return [2 /*return*/];
|
|
1777
|
+
}
|
|
1778
|
+
_e.label = 1;
|
|
1647
1779
|
case 1:
|
|
1648
|
-
|
|
1780
|
+
_e.trys.push([1, 3, , 4]);
|
|
1649
1781
|
return [4 /*yield*/, this.callObject.leave()];
|
|
1650
1782
|
case 2:
|
|
1651
|
-
|
|
1783
|
+
_e.sent();
|
|
1652
1784
|
return [3 /*break*/, 4];
|
|
1653
1785
|
case 3:
|
|
1654
|
-
|
|
1786
|
+
e_1 = _e.sent();
|
|
1655
1787
|
return [3 /*break*/, 4];
|
|
1656
1788
|
case 4:
|
|
1657
1789
|
this.cleanup();
|
|
@@ -1661,17 +1793,21 @@
|
|
|
1661
1793
|
});
|
|
1662
1794
|
};
|
|
1663
1795
|
DailyVoiceClientService.prototype.cleanup = function () {
|
|
1664
|
-
var _a, _b;
|
|
1665
1796
|
this.stopRemoteAudioMonitor();
|
|
1666
1797
|
this.stopRemoteAudio();
|
|
1667
|
-
(
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1798
|
+
if (this.callObject) {
|
|
1799
|
+
this.callObject.destroy().catch(function () { });
|
|
1800
|
+
this.callObject = null;
|
|
1801
|
+
}
|
|
1802
|
+
if (this.localStream) {
|
|
1803
|
+
this.localStream.getTracks().forEach(function (t) { return t.stop(); });
|
|
1804
|
+
this.localStream = null;
|
|
1805
|
+
}
|
|
1671
1806
|
this.localSessionId = null;
|
|
1672
1807
|
this.speakingSubject.next(false);
|
|
1673
1808
|
this.userSpeakingSubject.next(false);
|
|
1674
1809
|
this.localStreamSubject.next(null);
|
|
1810
|
+
// Keep last micMuted state; will reset on next connect
|
|
1675
1811
|
};
|
|
1676
1812
|
return DailyVoiceClientService;
|
|
1677
1813
|
}());
|
|
@@ -1685,8 +1821,21 @@
|
|
|
1685
1821
|
{ type: i0.NgZone }
|
|
1686
1822
|
]; };
|
|
1687
1823
|
|
|
1824
|
+
/**
|
|
1825
|
+
* Voice agent orchestrator. Coordinates WebSocket (signaling) and Daily.js (WebRTC audio).
|
|
1826
|
+
*
|
|
1827
|
+
* CRITICAL: This service must NEVER use Socket.IO or ngx-socket-io. Voice flow uses only:
|
|
1828
|
+
* - Native WebSocket (WebSocketVoiceClientService) for signaling (room_created, transcripts)
|
|
1829
|
+
* - Daily.js (DailyVoiceClientService) for WebRTC audio. Audio does NOT flow over WebSocket.
|
|
1830
|
+
*
|
|
1831
|
+
* - Maintains callState, statusText, duration, isMicMuted, isUserSpeaking, audioLevels
|
|
1832
|
+
* - Uses WebSocket for room_created and transcripts only (no audio)
|
|
1833
|
+
* - Uses Daily.js for all audio, mic, and real-time speaking detection
|
|
1834
|
+
*/
|
|
1688
1835
|
var VoiceAgentService = /** @class */ (function () {
|
|
1689
|
-
function VoiceAgentService(audioAnalyzer, wsClient, dailyClient, platformTokenRefresh,
|
|
1836
|
+
function VoiceAgentService(audioAnalyzer, wsClient, dailyClient, platformTokenRefresh,
|
|
1837
|
+
/** `Object` not `object` — ngc metadata collection rejects the `object` type in DI params. */
|
|
1838
|
+
platformId) {
|
|
1690
1839
|
var _this = this;
|
|
1691
1840
|
this.audioAnalyzer = audioAnalyzer;
|
|
1692
1841
|
this.wsClient = wsClient;
|
|
@@ -1696,7 +1845,7 @@
|
|
|
1696
1845
|
this.callStateSubject = new rxjs.BehaviorSubject('idle');
|
|
1697
1846
|
this.statusTextSubject = new rxjs.BehaviorSubject('');
|
|
1698
1847
|
this.durationSubject = new rxjs.BehaviorSubject('00:00');
|
|
1699
|
-
this.isMicMutedSubject = new rxjs.BehaviorSubject(
|
|
1848
|
+
this.isMicMutedSubject = new rxjs.BehaviorSubject(false);
|
|
1700
1849
|
this.isUserSpeakingSubject = new rxjs.BehaviorSubject(false);
|
|
1701
1850
|
this.audioLevelsSubject = new rxjs.BehaviorSubject([]);
|
|
1702
1851
|
this.userTranscriptSubject = new rxjs.Subject();
|
|
@@ -1704,6 +1853,8 @@
|
|
|
1704
1853
|
this.callStartTime = 0;
|
|
1705
1854
|
this.durationInterval = null;
|
|
1706
1855
|
this.subscriptions = new rxjs.Subscription();
|
|
1856
|
+
/** Per-call only; cleared on disconnect / reset / new room so handlers do not stack. */
|
|
1857
|
+
this.callSubscriptions = new rxjs.Subscription();
|
|
1707
1858
|
this.destroy$ = new rxjs.Subject();
|
|
1708
1859
|
this.callState$ = this.callStateSubject.asObservable();
|
|
1709
1860
|
this.statusText$ = this.statusTextSubject.asObservable();
|
|
@@ -1713,151 +1864,199 @@
|
|
|
1713
1864
|
this.audioLevels$ = this.audioLevelsSubject.asObservable();
|
|
1714
1865
|
this.userTranscript$ = this.userTranscriptSubject.asObservable();
|
|
1715
1866
|
this.botTranscript$ = this.botTranscriptSubject.asObservable();
|
|
1867
|
+
// Waveform visualization only - do NOT use for speaking state
|
|
1716
1868
|
this.subscriptions.add(this.audioAnalyzer.audioLevels$.subscribe(function (levels) { return _this.audioLevelsSubject.next(levels); }));
|
|
1869
|
+
// Transcripts: single subscription for service lifetime (avoid stacking on each connect()).
|
|
1870
|
+
// WebSocket is disconnected between calls; no replay — new subscribers (setupVoiceTranscripts)
|
|
1871
|
+
// only receive messages from the new WS after connect.
|
|
1872
|
+
this.subscriptions.add(this.wsClient.userTranscript$
|
|
1873
|
+
.pipe(operators.takeUntil(this.destroy$))
|
|
1874
|
+
.subscribe(function (t) { return _this.userTranscriptSubject.next(t); }));
|
|
1875
|
+
this.subscriptions.add(this.wsClient.botTranscript$
|
|
1876
|
+
.pipe(operators.takeUntil(this.destroy$))
|
|
1877
|
+
.subscribe(function (t) { return _this.botTranscriptSubject.next(t); }));
|
|
1717
1878
|
}
|
|
1718
1879
|
VoiceAgentService.prototype.ngOnDestroy = function () {
|
|
1719
1880
|
this.destroy$.next();
|
|
1720
1881
|
this.subscriptions.unsubscribe();
|
|
1721
1882
|
this.disconnect();
|
|
1722
1883
|
};
|
|
1723
|
-
/**
|
|
1724
|
-
* Tear down transports and reset UI state so a new `connect()` can run.
|
|
1725
|
-
* `connect()` only proceeds from `idle`; use this after `ended` or when reopening the modal.
|
|
1726
|
-
*/
|
|
1884
|
+
/** Reset to idle state (e.g. when modal opens so user can click Start Call). */
|
|
1727
1885
|
VoiceAgentService.prototype.resetToIdle = function () {
|
|
1886
|
+
if (this.callStateSubject.value === 'idle')
|
|
1887
|
+
return;
|
|
1888
|
+
this.callSubscriptions.unsubscribe();
|
|
1889
|
+
this.callSubscriptions = new rxjs.Subscription();
|
|
1728
1890
|
this.stopDurationTimer();
|
|
1729
1891
|
this.audioAnalyzer.stop();
|
|
1730
1892
|
this.wsClient.disconnect();
|
|
1893
|
+
// Fire-and-forget: Daily disconnect is async; connect() will await if needed
|
|
1731
1894
|
void this.dailyClient.disconnect();
|
|
1732
1895
|
this.callStateSubject.next('idle');
|
|
1733
1896
|
this.statusTextSubject.next('');
|
|
1734
|
-
this.durationSubject.next('
|
|
1735
|
-
this.isMicMutedSubject.next(true);
|
|
1736
|
-
this.isUserSpeakingSubject.next(false);
|
|
1737
|
-
this.audioLevelsSubject.next([]);
|
|
1897
|
+
this.durationSubject.next('0:00');
|
|
1738
1898
|
};
|
|
1739
|
-
VoiceAgentService.prototype.connect = function (apiUrl, token, botId, conversationId, apiKey, eventToken, eventId, eventUrl, domainAuthority, usersApiUrl) {
|
|
1899
|
+
VoiceAgentService.prototype.connect = function (apiUrl, token, botId, conversationId, apiKey, eventToken, eventId, eventUrl, domainAuthority, usersApiUrl, existingMicStream) {
|
|
1900
|
+
var _a;
|
|
1740
1901
|
return __awaiter(this, void 0, void 0, function () {
|
|
1741
|
-
var
|
|
1902
|
+
var tokenPromise, prepPromise, micPromise, _b, accessToken, _c, postUrl, body, micStream_1, headers, res, json, wsUrl, roomCreatedSub_1, roomJoined, e_1, error_1;
|
|
1742
1903
|
var _this = this;
|
|
1743
|
-
return __generator(this, function (
|
|
1744
|
-
switch (
|
|
1904
|
+
return __generator(this, function (_d) {
|
|
1905
|
+
switch (_d.label) {
|
|
1745
1906
|
case 0:
|
|
1746
|
-
if (this.callStateSubject.value !== 'idle')
|
|
1907
|
+
if (this.callStateSubject.value !== 'idle') {
|
|
1908
|
+
console.warn('Call already in progress');
|
|
1747
1909
|
return [2 /*return*/];
|
|
1748
|
-
|
|
1910
|
+
}
|
|
1911
|
+
_d.label = 1;
|
|
1749
1912
|
case 1:
|
|
1750
|
-
|
|
1913
|
+
_d.trys.push([1, 10, , 12]);
|
|
1751
1914
|
this.callStateSubject.next('connecting');
|
|
1752
1915
|
this.statusTextSubject.next('Connecting...');
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
_b.label = 2;
|
|
1756
|
-
case 2:
|
|
1757
|
-
_b.trys.push([2, 4, , 5]);
|
|
1758
|
-
return [4 /*yield*/, this.platformTokenRefresh
|
|
1916
|
+
tokenPromise = usersApiUrl && common.isPlatformBrowser(this.platformId)
|
|
1917
|
+
? this.platformTokenRefresh
|
|
1759
1918
|
.ensureValidAccessToken(token, usersApiUrl)
|
|
1760
1919
|
.pipe(operators.take(1))
|
|
1761
|
-
.toPromise()
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
base = (apiUrl || '').replace(/\/+$/, '');
|
|
1773
|
-
postUrl = base + "/ai/ask-voice";
|
|
1774
|
-
eventIdHeader = (eventId && String(eventId).trim()) || '';
|
|
1775
|
-
return [4 /*yield*/, fetch(postUrl, {
|
|
1776
|
-
method: 'POST',
|
|
1777
|
-
headers: {
|
|
1778
|
-
'Content-Type': 'application/json',
|
|
1779
|
-
Authorization: "Bearer " + accessToken,
|
|
1780
|
-
'domain-authority': domainAuthority,
|
|
1781
|
-
eventtoken: eventToken,
|
|
1782
|
-
eventurl: eventUrl,
|
|
1783
|
-
'hive-bot-id': botId,
|
|
1784
|
-
'x-api-key': apiKey,
|
|
1785
|
-
eventId: eventIdHeader,
|
|
1786
|
-
},
|
|
1920
|
+
.toPromise()
|
|
1921
|
+
.then(function (ensured) { var _a; return (_a = ensured === null || ensured === void 0 ? void 0 : ensured.accessToken) !== null && _a !== void 0 ? _a : token; })
|
|
1922
|
+
.catch(function (e) {
|
|
1923
|
+
console.warn('[HiveGpt Voice] Token refresh before connect failed', e);
|
|
1924
|
+
return token;
|
|
1925
|
+
})
|
|
1926
|
+
: Promise.resolve(token);
|
|
1927
|
+
prepPromise = Promise.resolve().then(function () {
|
|
1928
|
+
var baseUrl = apiUrl.replace(/\/$/, '');
|
|
1929
|
+
return {
|
|
1930
|
+
postUrl: baseUrl + "/ai/ask-voice",
|
|
1787
1931
|
body: JSON.stringify({
|
|
1788
1932
|
bot_id: botId,
|
|
1789
1933
|
conversation_id: conversationId,
|
|
1790
1934
|
voice: 'alloy',
|
|
1791
1935
|
}),
|
|
1936
|
+
};
|
|
1937
|
+
});
|
|
1938
|
+
micPromise = (existingMicStream === null || existingMicStream === void 0 ? void 0 : existingMicStream.getAudioTracks().some(function (t) { return t.readyState === 'live'; }))
|
|
1939
|
+
? Promise.resolve(existingMicStream)
|
|
1940
|
+
: common.isPlatformBrowser(this.platformId) &&
|
|
1941
|
+
((_a = navigator.mediaDevices) === null || _a === void 0 ? void 0 : _a.getUserMedia)
|
|
1942
|
+
? navigator.mediaDevices
|
|
1943
|
+
.getUserMedia({ audio: true })
|
|
1944
|
+
.catch(function () { return undefined; })
|
|
1945
|
+
: Promise.resolve(undefined);
|
|
1946
|
+
return [4 /*yield*/, Promise.all([
|
|
1947
|
+
tokenPromise,
|
|
1948
|
+
prepPromise,
|
|
1949
|
+
micPromise,
|
|
1950
|
+
])];
|
|
1951
|
+
case 2:
|
|
1952
|
+
_b = __read.apply(void 0, [_d.sent(), 3]), accessToken = _b[0], _c = _b[1], postUrl = _c.postUrl, body = _c.body, micStream_1 = _b[2];
|
|
1953
|
+
headers = {
|
|
1954
|
+
'Content-Type': 'application/json',
|
|
1955
|
+
Authorization: "Bearer " + accessToken,
|
|
1956
|
+
'x-api-key': apiKey,
|
|
1957
|
+
'hive-bot-id': botId,
|
|
1958
|
+
'domain-authority': domainAuthority,
|
|
1959
|
+
eventUrl: eventUrl,
|
|
1960
|
+
eventId: eventId,
|
|
1961
|
+
eventToken: eventToken,
|
|
1962
|
+
'ngrok-skip-browser-warning': 'true',
|
|
1963
|
+
};
|
|
1964
|
+
return [4 /*yield*/, fetch(postUrl, {
|
|
1965
|
+
method: 'POST',
|
|
1966
|
+
headers: headers,
|
|
1967
|
+
body: body,
|
|
1792
1968
|
})];
|
|
1793
|
-
case
|
|
1794
|
-
res =
|
|
1969
|
+
case 3:
|
|
1970
|
+
res = _d.sent();
|
|
1971
|
+
if (!res.ok) {
|
|
1972
|
+
throw new Error("HTTP " + res.status);
|
|
1973
|
+
}
|
|
1795
1974
|
return [4 /*yield*/, res.json()];
|
|
1796
|
-
case
|
|
1797
|
-
json =
|
|
1975
|
+
case 4:
|
|
1976
|
+
json = _d.sent();
|
|
1798
1977
|
wsUrl = json === null || json === void 0 ? void 0 : json.rn_ws_url;
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1978
|
+
if (!wsUrl || typeof wsUrl !== 'string') {
|
|
1979
|
+
throw new Error('No ws_url in response');
|
|
1980
|
+
}
|
|
1981
|
+
roomJoined = new Promise(function (resolve, reject) {
|
|
1982
|
+
roomCreatedSub_1 = _this.wsClient.roomCreated$
|
|
1983
|
+
.pipe(operators.take(1), operators.takeUntil(_this.destroy$))
|
|
1984
|
+
.subscribe(function (roomUrl) { return __awaiter(_this, void 0, void 0, function () {
|
|
1985
|
+
var err_1;
|
|
1986
|
+
return __generator(this, function (_b) {
|
|
1987
|
+
switch (_b.label) {
|
|
1988
|
+
case 0:
|
|
1989
|
+
_b.trys.push([0, 2, , 3]);
|
|
1990
|
+
return [4 /*yield*/, this.onRoomCreated(roomUrl, micStream_1 !== null && micStream_1 !== void 0 ? micStream_1 : undefined)];
|
|
1991
|
+
case 1:
|
|
1992
|
+
_b.sent();
|
|
1993
|
+
resolve();
|
|
1994
|
+
return [3 /*break*/, 3];
|
|
1995
|
+
case 2:
|
|
1996
|
+
err_1 = _b.sent();
|
|
1997
|
+
console.error('Daily join failed:', err_1);
|
|
1998
|
+
reject(err_1);
|
|
1999
|
+
return [3 /*break*/, 3];
|
|
2000
|
+
case 3: return [2 /*return*/];
|
|
2001
|
+
}
|
|
2002
|
+
});
|
|
2003
|
+
}); }, function (err) { return reject(err); });
|
|
2004
|
+
});
|
|
2005
|
+
_d.label = 5;
|
|
2006
|
+
case 5:
|
|
2007
|
+
_d.trys.push([5, 8, , 9]);
|
|
2008
|
+
return [4 /*yield*/, this.wsClient.connect(wsUrl)];
|
|
2009
|
+
case 6:
|
|
2010
|
+
_d.sent();
|
|
2011
|
+
return [4 /*yield*/, roomJoined];
|
|
2012
|
+
case 7:
|
|
2013
|
+
_d.sent();
|
|
1814
2014
|
return [3 /*break*/, 9];
|
|
1815
2015
|
case 8:
|
|
1816
|
-
e_1 =
|
|
2016
|
+
e_1 = _d.sent();
|
|
2017
|
+
roomCreatedSub_1 === null || roomCreatedSub_1 === void 0 ? void 0 : roomCreatedSub_1.unsubscribe();
|
|
2018
|
+
throw e_1;
|
|
2019
|
+
case 9: return [3 /*break*/, 12];
|
|
2020
|
+
case 10:
|
|
2021
|
+
error_1 = _d.sent();
|
|
2022
|
+
console.error('Error connecting voice agent:', error_1);
|
|
1817
2023
|
this.callStateSubject.next('ended');
|
|
1818
|
-
return [
|
|
1819
|
-
case
|
|
2024
|
+
return [4 /*yield*/, this.disconnect()];
|
|
2025
|
+
case 11:
|
|
2026
|
+
_d.sent();
|
|
2027
|
+
this.statusTextSubject.next('Connection failed');
|
|
2028
|
+
throw error_1;
|
|
2029
|
+
case 12: return [2 /*return*/];
|
|
1820
2030
|
}
|
|
1821
2031
|
});
|
|
1822
2032
|
});
|
|
1823
2033
|
};
|
|
1824
|
-
VoiceAgentService.prototype.onRoomCreated = function (roomUrl) {
|
|
2034
|
+
VoiceAgentService.prototype.onRoomCreated = function (roomUrl, micStream) {
|
|
1825
2035
|
return __awaiter(this, void 0, void 0, function () {
|
|
1826
|
-
var handled;
|
|
1827
2036
|
var _this = this;
|
|
1828
2037
|
return __generator(this, function (_b) {
|
|
1829
2038
|
switch (_b.label) {
|
|
1830
|
-
case 0: return [4 /*yield*/, this.dailyClient.connect(roomUrl)];
|
|
2039
|
+
case 0: return [4 /*yield*/, this.dailyClient.connect(roomUrl, undefined, micStream)];
|
|
1831
2040
|
case 1:
|
|
1832
2041
|
_b.sent();
|
|
1833
|
-
|
|
1834
|
-
this.
|
|
1835
|
-
|
|
1836
|
-
this.
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
_this.dailyClient.setMuted(false);
|
|
1844
|
-
_this.statusTextSubject.next('You can speak now');
|
|
1845
|
-
});
|
|
1846
|
-
// ⛑️ Fallback (if bot fails)
|
|
1847
|
-
setTimeout(function () {
|
|
1848
|
-
if (!handled) {
|
|
1849
|
-
console.warn('[VoiceFlow] Fallback → enabling mic');
|
|
1850
|
-
_this.dailyClient.setMuted(false);
|
|
1851
|
-
_this.statusTextSubject.next('You can speak now');
|
|
1852
|
-
}
|
|
1853
|
-
}, 8000);
|
|
1854
|
-
// rest same
|
|
1855
|
-
this.subscriptions.add(rxjs.combineLatest([
|
|
2042
|
+
this.callSubscriptions.unsubscribe();
|
|
2043
|
+
this.callSubscriptions = new rxjs.Subscription();
|
|
2044
|
+
// Waveform: use local mic stream from Daily client
|
|
2045
|
+
this.callSubscriptions.add(this.dailyClient.localStream$
|
|
2046
|
+
.pipe(operators.filter(function (s) { return s != null; }), operators.take(1))
|
|
2047
|
+
.subscribe(function (stream) {
|
|
2048
|
+
_this.audioAnalyzer.start(stream);
|
|
2049
|
+
}));
|
|
2050
|
+
this.callSubscriptions.add(this.dailyClient.userSpeaking$.subscribe(function (s) { return _this.isUserSpeakingSubject.next(s); }));
|
|
2051
|
+
this.callSubscriptions.add(rxjs.combineLatest([
|
|
1856
2052
|
this.dailyClient.speaking$,
|
|
1857
2053
|
this.dailyClient.userSpeaking$,
|
|
1858
2054
|
]).subscribe(function (_b) {
|
|
1859
2055
|
var _c = __read(_b, 2), bot = _c[0], user = _c[1];
|
|
1860
2056
|
var current = _this.callStateSubject.value;
|
|
2057
|
+
if (current === 'connecting' && !bot) {
|
|
2058
|
+
return;
|
|
2059
|
+
}
|
|
1861
2060
|
if (current === 'connecting' && bot) {
|
|
1862
2061
|
_this.callStartTime = Date.now();
|
|
1863
2062
|
_this.startDurationTimer();
|
|
@@ -1871,9 +2070,12 @@
|
|
|
1871
2070
|
_this.callStateSubject.next('talking');
|
|
1872
2071
|
}
|
|
1873
2072
|
else {
|
|
1874
|
-
|
|
2073
|
+
// Between bot turns: stay on listening to avoid flicker via 'connected'
|
|
2074
|
+
_this.callStateSubject.next('listening');
|
|
1875
2075
|
}
|
|
1876
2076
|
}));
|
|
2077
|
+
this.callSubscriptions.add(this.dailyClient.micMuted$.subscribe(function (muted) { return _this.isMicMutedSubject.next(muted); }));
|
|
2078
|
+
this.statusTextSubject.next('Connecting...');
|
|
1877
2079
|
return [2 /*return*/];
|
|
1878
2080
|
}
|
|
1879
2081
|
});
|
|
@@ -1884,10 +2086,14 @@
|
|
|
1884
2086
|
return __generator(this, function (_b) {
|
|
1885
2087
|
switch (_b.label) {
|
|
1886
2088
|
case 0:
|
|
2089
|
+
this.callSubscriptions.unsubscribe();
|
|
2090
|
+
this.callSubscriptions = new rxjs.Subscription();
|
|
1887
2091
|
this.stopDurationTimer();
|
|
1888
2092
|
this.audioAnalyzer.stop();
|
|
2093
|
+
// Daily first, then WebSocket
|
|
1889
2094
|
return [4 /*yield*/, this.dailyClient.disconnect()];
|
|
1890
2095
|
case 1:
|
|
2096
|
+
// Daily first, then WebSocket
|
|
1891
2097
|
_b.sent();
|
|
1892
2098
|
this.wsClient.disconnect();
|
|
1893
2099
|
this.callStateSubject.next('ended');
|
|
@@ -1903,16 +2109,22 @@
|
|
|
1903
2109
|
};
|
|
1904
2110
|
VoiceAgentService.prototype.startDurationTimer = function () {
|
|
1905
2111
|
var _this = this;
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
2112
|
+
var updateDuration = function () {
|
|
2113
|
+
if (_this.callStartTime > 0) {
|
|
2114
|
+
var elapsed = Math.floor((Date.now() - _this.callStartTime) / 1000);
|
|
2115
|
+
var minutes = Math.floor(elapsed / 60);
|
|
2116
|
+
var seconds = elapsed % 60;
|
|
2117
|
+
_this.durationSubject.next(minutes + ":" + String(seconds).padStart(2, '0'));
|
|
2118
|
+
}
|
|
2119
|
+
};
|
|
2120
|
+
updateDuration();
|
|
2121
|
+
this.durationInterval = setInterval(updateDuration, 1000);
|
|
1912
2122
|
};
|
|
1913
2123
|
VoiceAgentService.prototype.stopDurationTimer = function () {
|
|
1914
|
-
if (this.durationInterval)
|
|
2124
|
+
if (this.durationInterval) {
|
|
1915
2125
|
clearInterval(this.durationInterval);
|
|
2126
|
+
this.durationInterval = null;
|
|
2127
|
+
}
|
|
1916
2128
|
};
|
|
1917
2129
|
return VoiceAgentService;
|
|
1918
2130
|
}());
|
|
@@ -1934,10 +2146,11 @@
|
|
|
1934
2146
|
var VOICE_MODAL_CLOSE_CALLBACK = new i0.InjectionToken('VOICE_MODAL_CLOSE_CALLBACK');
|
|
1935
2147
|
|
|
1936
2148
|
var VoiceAgentModalComponent = /** @class */ (function () {
|
|
1937
|
-
function VoiceAgentModalComponent(voiceAgentService, audioAnalyzer, injector) {
|
|
2149
|
+
function VoiceAgentModalComponent(voiceAgentService, audioAnalyzer, injector, platformId) {
|
|
1938
2150
|
this.voiceAgentService = voiceAgentService;
|
|
1939
2151
|
this.audioAnalyzer = audioAnalyzer;
|
|
1940
2152
|
this.injector = injector;
|
|
2153
|
+
this.platformId = platformId;
|
|
1941
2154
|
this.close = new i0.EventEmitter();
|
|
1942
2155
|
this.apiKey = '';
|
|
1943
2156
|
this.eventToken = '';
|
|
@@ -1949,6 +2162,8 @@
|
|
|
1949
2162
|
this.usersApiUrl = '';
|
|
1950
2163
|
this.injectedConfig = null;
|
|
1951
2164
|
this.onCloseCallback = null;
|
|
2165
|
+
/** Held until destroy; passed to Daily so we do not stop/re-acquire (avoids extra prompts on some browsers). */
|
|
2166
|
+
this.warmMicStream = null;
|
|
1952
2167
|
/** Hardcoded voice agent avatar (Nia). */
|
|
1953
2168
|
this.displayAvatarUrl = 'https://www.jotform.com/uploads/mehmetkarakasli/form_files/1564593667676a8e85f23758.86945537_icon.png';
|
|
1954
2169
|
this.callState = 'idle';
|
|
@@ -1964,63 +2179,108 @@
|
|
|
1964
2179
|
this.isConnecting = false;
|
|
1965
2180
|
}
|
|
1966
2181
|
VoiceAgentModalComponent.prototype.ngOnInit = function () {
|
|
1967
|
-
|
|
2182
|
+
void this.bootstrap();
|
|
2183
|
+
};
|
|
2184
|
+
VoiceAgentModalComponent.prototype.bootstrap = function () {
|
|
1968
2185
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2186
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
2187
|
+
var _this = this;
|
|
2188
|
+
return __generator(this, function (_j) {
|
|
2189
|
+
switch (_j.label) {
|
|
2190
|
+
case 0:
|
|
2191
|
+
this.injectedConfig = this.injector.get(VOICE_MODAL_CONFIG, null);
|
|
2192
|
+
this.onCloseCallback = this.injector.get(VOICE_MODAL_CLOSE_CALLBACK, null);
|
|
2193
|
+
if (this.injectedConfig) {
|
|
2194
|
+
this.apiUrl = this.injectedConfig.apiUrl;
|
|
2195
|
+
this.token = this.injectedConfig.token;
|
|
2196
|
+
this.botId = this.injectedConfig.botId;
|
|
2197
|
+
this.conversationId = this.injectedConfig.conversationId;
|
|
2198
|
+
this.apiKey = (_a = this.injectedConfig.apiKey) !== null && _a !== void 0 ? _a : '';
|
|
2199
|
+
this.eventToken = (_b = this.injectedConfig.eventToken) !== null && _b !== void 0 ? _b : '';
|
|
2200
|
+
this.eventId = (_c = this.injectedConfig.eventId) !== null && _c !== void 0 ? _c : '';
|
|
2201
|
+
this.eventUrl = (_d = this.injectedConfig.eventUrl) !== null && _d !== void 0 ? _d : '';
|
|
2202
|
+
this.domainAuthority = (_e = this.injectedConfig.domainAuthority) !== null && _e !== void 0 ? _e : 'prod-lite';
|
|
2203
|
+
this.agentName = (_f = this.injectedConfig.agentName) !== null && _f !== void 0 ? _f : this.agentName;
|
|
2204
|
+
this.agentRole = (_g = this.injectedConfig.agentRole) !== null && _g !== void 0 ? _g : this.agentRole;
|
|
2205
|
+
this.agentAvatar = this.injectedConfig.agentAvatar;
|
|
2206
|
+
this.usersApiUrl = (_h = this.injectedConfig.usersApiUrl) !== null && _h !== void 0 ? _h : this.usersApiUrl;
|
|
2207
|
+
}
|
|
2208
|
+
// Subscribe to observables
|
|
2209
|
+
this.subscriptions.push(this.voiceAgentService.callState$.subscribe(function (state) {
|
|
2210
|
+
_this.callState = state;
|
|
2211
|
+
_this.isSpeaking = state === 'talking';
|
|
2212
|
+
if (state === 'listening' || state === 'talking') {
|
|
2213
|
+
_this.hasLeftConnectedOnce = true;
|
|
2214
|
+
}
|
|
2215
|
+
if (state === 'idle' || state === 'ended') {
|
|
2216
|
+
_this.hasLeftConnectedOnce = false;
|
|
2217
|
+
}
|
|
2218
|
+
}));
|
|
2219
|
+
this.subscriptions.push(this.voiceAgentService.statusText$.subscribe(function (text) {
|
|
2220
|
+
_this.statusText = text;
|
|
2221
|
+
}));
|
|
2222
|
+
this.subscriptions.push(this.voiceAgentService.duration$.subscribe(function (duration) {
|
|
2223
|
+
_this.duration = duration;
|
|
2224
|
+
}));
|
|
2225
|
+
this.subscriptions.push(this.voiceAgentService.isMicMuted$.subscribe(function (muted) {
|
|
2226
|
+
_this.isMicMuted = muted;
|
|
2227
|
+
}));
|
|
2228
|
+
this.subscriptions.push(this.voiceAgentService.isUserSpeaking$.subscribe(function (speaking) {
|
|
2229
|
+
_this.isUserSpeaking = speaking;
|
|
2230
|
+
}));
|
|
2231
|
+
this.subscriptions.push(this.voiceAgentService.audioLevels$.subscribe(function (levels) {
|
|
2232
|
+
_this.audioLevels = levels;
|
|
2233
|
+
}));
|
|
2234
|
+
this.voiceAgentService.resetToIdle();
|
|
2235
|
+
return [4 /*yield*/, this.startCall()];
|
|
2236
|
+
case 1:
|
|
2237
|
+
_j.sent();
|
|
2238
|
+
return [2 /*return*/];
|
|
2239
|
+
}
|
|
2240
|
+
});
|
|
2241
|
+
});
|
|
2016
2242
|
};
|
|
2017
2243
|
VoiceAgentModalComponent.prototype.ngOnDestroy = function () {
|
|
2244
|
+
var _this = this;
|
|
2018
2245
|
this.subscriptions.forEach(function (sub) { return sub.unsubscribe(); });
|
|
2019
|
-
this.disconnect()
|
|
2246
|
+
void this.disconnect().finally(function () {
|
|
2247
|
+
var _a;
|
|
2248
|
+
(_a = _this.warmMicStream) === null || _a === void 0 ? void 0 : _a.getTracks().forEach(function (t) { return t.stop(); });
|
|
2249
|
+
_this.warmMicStream = null;
|
|
2250
|
+
});
|
|
2251
|
+
};
|
|
2252
|
+
/** Ensures a live mic stream for this call (re-acquire after Daily stops tracks on hang-up). */
|
|
2253
|
+
VoiceAgentModalComponent.prototype.ensureMicForCall = function () {
|
|
2254
|
+
var _a;
|
|
2255
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
2256
|
+
var _j, _b_1;
|
|
2257
|
+
return __generator(this, function (_k) {
|
|
2258
|
+
switch (_k.label) {
|
|
2259
|
+
case 0:
|
|
2260
|
+
if (!common.isPlatformBrowser(this.platformId))
|
|
2261
|
+
return [2 /*return*/, undefined];
|
|
2262
|
+
if ((_a = this.warmMicStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks().some(function (t) { return t.readyState === 'live'; })) {
|
|
2263
|
+
return [2 /*return*/, this.warmMicStream];
|
|
2264
|
+
}
|
|
2265
|
+
_k.label = 1;
|
|
2266
|
+
case 1:
|
|
2267
|
+
_k.trys.push([1, 3, , 4]);
|
|
2268
|
+
_j = this;
|
|
2269
|
+
return [4 /*yield*/, navigator.mediaDevices.getUserMedia({ audio: true })];
|
|
2270
|
+
case 2:
|
|
2271
|
+
_j.warmMicStream = _k.sent();
|
|
2272
|
+
return [2 /*return*/, this.warmMicStream];
|
|
2273
|
+
case 3:
|
|
2274
|
+
_b_1 = _k.sent();
|
|
2275
|
+
return [2 /*return*/, undefined];
|
|
2276
|
+
case 4: return [2 /*return*/];
|
|
2277
|
+
}
|
|
2278
|
+
});
|
|
2279
|
+
});
|
|
2020
2280
|
};
|
|
2021
2281
|
VoiceAgentModalComponent.prototype.startCall = function () {
|
|
2022
2282
|
return __awaiter(this, void 0, void 0, function () {
|
|
2023
|
-
var error_1;
|
|
2283
|
+
var mic, error_1;
|
|
2024
2284
|
return __generator(this, function (_j) {
|
|
2025
2285
|
switch (_j.label) {
|
|
2026
2286
|
case 0:
|
|
@@ -2029,19 +2289,22 @@
|
|
|
2029
2289
|
this.isConnecting = true;
|
|
2030
2290
|
_j.label = 1;
|
|
2031
2291
|
case 1:
|
|
2032
|
-
_j.trys.push([1,
|
|
2033
|
-
return [4 /*yield*/, this.
|
|
2292
|
+
_j.trys.push([1, 4, 5, 6]);
|
|
2293
|
+
return [4 /*yield*/, this.ensureMicForCall()];
|
|
2034
2294
|
case 2:
|
|
2035
|
-
_j.sent();
|
|
2036
|
-
return [
|
|
2295
|
+
mic = _j.sent();
|
|
2296
|
+
return [4 /*yield*/, this.voiceAgentService.connect(this.apiUrl, this.token, this.botId, this.conversationId, this.apiKey, this.eventToken, this.eventId, this.eventUrl, this.domainAuthority, this.usersApiUrl || undefined, mic)];
|
|
2037
2297
|
case 3:
|
|
2298
|
+
_j.sent();
|
|
2299
|
+
return [3 /*break*/, 6];
|
|
2300
|
+
case 4:
|
|
2038
2301
|
error_1 = _j.sent();
|
|
2039
2302
|
console.error('Failed to connect voice agent:', error_1);
|
|
2040
|
-
return [3 /*break*/,
|
|
2041
|
-
case
|
|
2303
|
+
return [3 /*break*/, 6];
|
|
2304
|
+
case 5:
|
|
2042
2305
|
this.isConnecting = false;
|
|
2043
2306
|
return [7 /*endfinally*/];
|
|
2044
|
-
case
|
|
2307
|
+
case 6: return [2 /*return*/];
|
|
2045
2308
|
}
|
|
2046
2309
|
});
|
|
2047
2310
|
});
|
|
@@ -2086,7 +2349,7 @@
|
|
|
2086
2349
|
/** Call Again: reset to idle then start a new call. */
|
|
2087
2350
|
VoiceAgentModalComponent.prototype.callAgain = function () {
|
|
2088
2351
|
this.voiceAgentService.resetToIdle();
|
|
2089
|
-
this.startCall();
|
|
2352
|
+
void this.startCall();
|
|
2090
2353
|
};
|
|
2091
2354
|
/** Back to Chat: close modal and disconnect. */
|
|
2092
2355
|
VoiceAgentModalComponent.prototype.backToChat = function () {
|
|
@@ -2114,7 +2377,8 @@
|
|
|
2114
2377
|
VoiceAgentModalComponent.ctorParameters = function () { return [
|
|
2115
2378
|
{ type: VoiceAgentService },
|
|
2116
2379
|
{ type: AudioAnalyzerService },
|
|
2117
|
-
{ type: i0.Injector }
|
|
2380
|
+
{ type: i0.Injector },
|
|
2381
|
+
{ type: Object, decorators: [{ type: i0.Inject, args: [i0.PLATFORM_ID,] }] }
|
|
2118
2382
|
]; };
|
|
2119
2383
|
VoiceAgentModalComponent.propDecorators = {
|
|
2120
2384
|
close: [{ type: i0.Output }],
|
|
@@ -5600,7 +5864,7 @@
|
|
|
5600
5864
|
};
|
|
5601
5865
|
ChatDrawerComponent.prototype.openVoiceModal = function () {
|
|
5602
5866
|
var _this = this;
|
|
5603
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m
|
|
5867
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
5604
5868
|
var conversationId = (_b = (_a = this.conversationKey) !== null && _a !== void 0 ? _a : this.conversationService.getKey(this.botId, false, this.eventId)) !== null && _b !== void 0 ? _b : '';
|
|
5605
5869
|
this.voiceModalConversationId = conversationId;
|
|
5606
5870
|
this.setupVoiceTranscripts();
|
|
@@ -5617,13 +5881,13 @@
|
|
|
5617
5881
|
conversationId: conversationId,
|
|
5618
5882
|
apiKey: (_g = this.apiKey) !== null && _g !== void 0 ? _g : '',
|
|
5619
5883
|
eventToken: (_h = this.eventToken) !== null && _h !== void 0 ? _h : '',
|
|
5620
|
-
eventId:
|
|
5621
|
-
eventUrl: (
|
|
5622
|
-
domainAuthority: (
|
|
5884
|
+
eventId: this.eventId,
|
|
5885
|
+
eventUrl: (_j = this.eventUrl) !== null && _j !== void 0 ? _j : '',
|
|
5886
|
+
domainAuthority: (_k = this.domainAuthorityValue) !== null && _k !== void 0 ? _k : 'prod-lite',
|
|
5623
5887
|
agentName: this.botName || 'AI Assistant',
|
|
5624
5888
|
agentRole: this.botSkills || 'AI Agent Specialist',
|
|
5625
5889
|
agentAvatar: this.botIcon,
|
|
5626
|
-
usersApiUrl: (
|
|
5890
|
+
usersApiUrl: (_m = (_l = this.environment) === null || _l === void 0 ? void 0 : _l.USERS_API) !== null && _m !== void 0 ? _m : '',
|
|
5627
5891
|
};
|
|
5628
5892
|
var closeCallback = function () {
|
|
5629
5893
|
if (_this.voiceModalOverlayRef) {
|