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