@hivegpt/hiveai-angular 0.0.585 → 0.0.587
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 +733 -260
- package/bundles/hivegpt-hiveai-angular.umd.js.map +1 -1
- package/bundles/hivegpt-hiveai-angular.umd.min.js +1 -1
- package/bundles/hivegpt-hiveai-angular.umd.min.js.map +1 -1
- package/esm2015/hivegpt-hiveai-angular.js +6 -4
- package/esm2015/lib/components/voice-agent/components/voice-agent-modal/voice-agent-modal.component.js +24 -17
- package/esm2015/lib/components/voice-agent/services/audio-analyzer.service.js +3 -3
- package/esm2015/lib/components/voice-agent/services/daily-voice-client.service.js +326 -0
- package/esm2015/lib/components/voice-agent/services/voice-agent.service.js +187 -189
- package/esm2015/lib/components/voice-agent/services/websocket-voice-client.service.js +95 -0
- package/esm2015/lib/components/voice-agent/voice-agent.module.js +7 -3
- package/fesm2015/hivegpt-hiveai-angular.js +624 -207
- package/fesm2015/hivegpt-hiveai-angular.js.map +1 -1
- package/hivegpt-hiveai-angular.d.ts +5 -3
- package/hivegpt-hiveai-angular.d.ts.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 +4 -7
- package/lib/components/voice-agent/components/voice-agent-modal/voice-agent-modal.component.d.ts.map +1 -1
- package/lib/components/voice-agent/services/audio-analyzer.service.d.ts +2 -2
- package/lib/components/voice-agent/services/daily-voice-client.service.d.ts +70 -0
- package/lib/components/voice-agent/services/daily-voice-client.service.d.ts.map +1 -0
- package/lib/components/voice-agent/services/voice-agent.service.d.ts +19 -23
- 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 +49 -0
- package/lib/components/voice-agent/services/websocket-voice-client.service.d.ts.map +1 -0
- package/lib/components/voice-agent/voice-agent.module.d.ts +2 -2
- package/lib/components/voice-agent/voice-agent.module.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
(function (global, factory) {
|
|
2
|
-
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/cdk/overlay'), require('@angular/cdk/portal'), require('@angular/common/http'), require('@angular/core'), require('@angular/platform-browser'), require('rxjs'), require('rxjs/operators'), require('@angular/common'), require('ngx-socket-io'), require('@angular/forms'), require('microsoft-cognitiveservices-speech-sdk'), require('marked'), require('@
|
|
3
|
-
typeof define === 'function' && define.amd ? define('@hivegpt/hiveai-angular', ['exports', '@angular/cdk/overlay', '@angular/cdk/portal', '@angular/common/http', '@angular/core', '@angular/platform-browser', 'rxjs', 'rxjs/operators', '@angular/common', 'ngx-socket-io', '@angular/forms', 'microsoft-cognitiveservices-speech-sdk', 'marked', '@
|
|
4
|
-
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.hivegpt = global.hivegpt || {}, global.hivegpt["hiveai-angular"] = {}), global.ng.cdk.overlay, global.ng.cdk.portal, global.ng.common.http, global.ng.core, global.ng.platformBrowser, global.rxjs, global.rxjs.operators, global.ng.common, global.ngxSocketIo, global.ng.forms, global.SpeechSDK, global.marked, global.
|
|
5
|
-
})(this, (function (exports, overlay, portal, i1, i0, platformBrowser, rxjs, operators, common, ngxSocketIo, forms, SpeechSDK, marked,
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/cdk/overlay'), require('@angular/cdk/portal'), require('@angular/common/http'), require('@angular/core'), require('@angular/platform-browser'), require('rxjs'), require('rxjs/operators'), require('@angular/common'), require('ngx-socket-io'), require('@angular/forms'), require('microsoft-cognitiveservices-speech-sdk'), require('marked'), require('@daily-co/daily-js'), require('@angular/material/icon'), require('@angular/material/sidenav'), require('ngx-quill')) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define('@hivegpt/hiveai-angular', ['exports', '@angular/cdk/overlay', '@angular/cdk/portal', '@angular/common/http', '@angular/core', '@angular/platform-browser', 'rxjs', 'rxjs/operators', '@angular/common', 'ngx-socket-io', '@angular/forms', 'microsoft-cognitiveservices-speech-sdk', 'marked', '@daily-co/daily-js', '@angular/material/icon', '@angular/material/sidenav', 'ngx-quill'], factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.hivegpt = global.hivegpt || {}, global.hivegpt["hiveai-angular"] = {}), global.ng.cdk.overlay, global.ng.cdk.portal, global.ng.common.http, global.ng.core, global.ng.platformBrowser, global.rxjs, global.rxjs.operators, global.ng.common, global.ngxSocketIo, global.ng.forms, global.SpeechSDK, global.marked, global.Daily, global.ng.material.icon, global.ng.material.sidenav, global.ngxQuill));
|
|
5
|
+
})(this, (function (exports, overlay, portal, i1, i0, platformBrowser, rxjs, operators, common, ngxSocketIo, forms, SpeechSDK, marked, Daily, icon, sidenav, ngxQuill) { 'use strict';
|
|
6
|
+
|
|
7
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
6
8
|
|
|
7
9
|
function _interopNamespace(e) {
|
|
8
10
|
if (e && e.__esModule) return e;
|
|
@@ -26,6 +28,7 @@
|
|
|
26
28
|
var i0__namespace = /*#__PURE__*/_interopNamespace(i0);
|
|
27
29
|
var SpeechSDK__namespace = /*#__PURE__*/_interopNamespace(SpeechSDK);
|
|
28
30
|
var marked__namespace = /*#__PURE__*/_interopNamespace(marked);
|
|
31
|
+
var Daily__default = /*#__PURE__*/_interopDefaultLegacy(Daily);
|
|
29
32
|
|
|
30
33
|
/******************************************************************************
|
|
31
34
|
Copyright (c) Microsoft Corporation.
|
|
@@ -1215,8 +1218,8 @@
|
|
|
1215
1218
|
]; };
|
|
1216
1219
|
|
|
1217
1220
|
/**
|
|
1218
|
-
* Audio analyzer for waveform visualization
|
|
1219
|
-
*
|
|
1221
|
+
* Audio analyzer for waveform visualization only.
|
|
1222
|
+
* Do NOT use isUserSpeaking$ for call state; speaking state must come from Daily.js.
|
|
1220
1223
|
*/
|
|
1221
1224
|
var AudioAnalyzerService = /** @class */ (function () {
|
|
1222
1225
|
function AudioAnalyzerService() {
|
|
@@ -1339,24 +1342,478 @@
|
|
|
1339
1342
|
];
|
|
1340
1343
|
|
|
1341
1344
|
/**
|
|
1342
|
-
*
|
|
1345
|
+
* WebSocket-only client for voice agent signaling.
|
|
1346
|
+
* CRITICAL: Uses native WebSocket only. NO Socket.IO, NO ngx-socket-io.
|
|
1347
|
+
*
|
|
1348
|
+
* Responsibilities:
|
|
1349
|
+
* - Connect to ws_url (from POST /ai/ask-voice response)
|
|
1350
|
+
* - Parse JSON messages (room_created, user_transcript, bot_transcript)
|
|
1351
|
+
* - Emit roomCreated$, userTranscript$, botTranscript$
|
|
1352
|
+
* - NO audio logic, NO mic logic. Audio is handled by Daily.js (WebRTC).
|
|
1353
|
+
*/
|
|
1354
|
+
var WebSocketVoiceClientService = /** @class */ (function () {
|
|
1355
|
+
function WebSocketVoiceClientService() {
|
|
1356
|
+
this.ws = null;
|
|
1357
|
+
this.roomCreatedSubject = new rxjs.Subject();
|
|
1358
|
+
this.userTranscriptSubject = new rxjs.Subject();
|
|
1359
|
+
this.botTranscriptSubject = new rxjs.Subject();
|
|
1360
|
+
/** Emits room_url when backend sends room_created. */
|
|
1361
|
+
this.roomCreated$ = this.roomCreatedSubject.asObservable();
|
|
1362
|
+
/** Emits user transcript updates. */
|
|
1363
|
+
this.userTranscript$ = this.userTranscriptSubject.asObservable();
|
|
1364
|
+
/** Emits bot transcript updates. */
|
|
1365
|
+
this.botTranscript$ = this.botTranscriptSubject.asObservable();
|
|
1366
|
+
}
|
|
1367
|
+
/** Connect to signaling WebSocket. No audio over this connection. */
|
|
1368
|
+
WebSocketVoiceClientService.prototype.connect = function (wsUrl) {
|
|
1369
|
+
var _this = this;
|
|
1370
|
+
var _a;
|
|
1371
|
+
if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN) {
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
if (this.ws) {
|
|
1375
|
+
this.ws.close();
|
|
1376
|
+
this.ws = null;
|
|
1377
|
+
}
|
|
1378
|
+
try {
|
|
1379
|
+
this.ws = new WebSocket(wsUrl);
|
|
1380
|
+
this.ws.onmessage = function (event) {
|
|
1381
|
+
var _a;
|
|
1382
|
+
try {
|
|
1383
|
+
var msg = JSON.parse(event.data);
|
|
1384
|
+
if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'room_created') {
|
|
1385
|
+
var roomUrl = ((_a = msg.room_url) !== null && _a !== void 0 ? _a : msg.roomUrl);
|
|
1386
|
+
if (typeof roomUrl === 'string') {
|
|
1387
|
+
_this.roomCreatedSubject.next(roomUrl);
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
else if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'user_transcript' && typeof msg.text === 'string') {
|
|
1391
|
+
_this.userTranscriptSubject.next({
|
|
1392
|
+
text: msg.text,
|
|
1393
|
+
final: msg.final === true,
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1396
|
+
else if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'bot_transcript' && typeof msg.text === 'string') {
|
|
1397
|
+
_this.botTranscriptSubject.next(msg.text);
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
catch (_b) {
|
|
1401
|
+
// Ignore non-JSON or unknown messages
|
|
1402
|
+
}
|
|
1403
|
+
};
|
|
1404
|
+
this.ws.onerror = function () {
|
|
1405
|
+
_this.disconnect();
|
|
1406
|
+
};
|
|
1407
|
+
this.ws.onclose = function () {
|
|
1408
|
+
_this.ws = null;
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
catch (err) {
|
|
1412
|
+
console.error('WebSocketVoiceClient: connect failed', err);
|
|
1413
|
+
this.ws = null;
|
|
1414
|
+
throw err;
|
|
1415
|
+
}
|
|
1416
|
+
};
|
|
1417
|
+
/** Disconnect and cleanup. */
|
|
1418
|
+
WebSocketVoiceClientService.prototype.disconnect = function () {
|
|
1419
|
+
if (this.ws) {
|
|
1420
|
+
this.ws.close();
|
|
1421
|
+
this.ws = null;
|
|
1422
|
+
}
|
|
1423
|
+
};
|
|
1424
|
+
Object.defineProperty(WebSocketVoiceClientService.prototype, "isConnected", {
|
|
1425
|
+
/** Whether the WebSocket is open. */
|
|
1426
|
+
get: function () {
|
|
1427
|
+
var _a;
|
|
1428
|
+
return ((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN;
|
|
1429
|
+
},
|
|
1430
|
+
enumerable: false,
|
|
1431
|
+
configurable: true
|
|
1432
|
+
});
|
|
1433
|
+
return WebSocketVoiceClientService;
|
|
1434
|
+
}());
|
|
1435
|
+
WebSocketVoiceClientService.ɵprov = i0__namespace.ɵɵdefineInjectable({ factory: function WebSocketVoiceClientService_Factory() { return new WebSocketVoiceClientService(); }, token: WebSocketVoiceClientService, providedIn: "root" });
|
|
1436
|
+
WebSocketVoiceClientService.decorators = [
|
|
1437
|
+
{ type: i0.Injectable, args: [{
|
|
1438
|
+
providedIn: 'root',
|
|
1439
|
+
},] }
|
|
1440
|
+
];
|
|
1441
|
+
|
|
1442
|
+
/**
|
|
1443
|
+
* Daily.js WebRTC client for voice agent audio.
|
|
1444
|
+
* Responsibilities:
|
|
1445
|
+
* - Create and manage Daily CallObject
|
|
1446
|
+
* - Join Daily room using room_url
|
|
1447
|
+
* - Handle mic capture + speaker playback
|
|
1448
|
+
* - Bot speaking detection via AnalyserNode on remote track (instant)
|
|
1449
|
+
* - User speaking detection via active-speaker-change
|
|
1450
|
+
* - Expose speaking$ (bot speaking), userSpeaking$ (user speaking), micMuted$
|
|
1451
|
+
* - Expose localStream$ for waveform visualization (AudioAnalyzerService)
|
|
1452
|
+
*/
|
|
1453
|
+
var DailyVoiceClientService = /** @class */ (function () {
|
|
1454
|
+
function DailyVoiceClientService(ngZone) {
|
|
1455
|
+
this.ngZone = ngZone;
|
|
1456
|
+
this.callObject = null;
|
|
1457
|
+
this.localStream = null;
|
|
1458
|
+
this.localSessionId = null;
|
|
1459
|
+
/** Explicit playback of remote (bot) audio; required in some browsers. */
|
|
1460
|
+
this.remoteAudioElement = null;
|
|
1461
|
+
/** AnalyserNode-based remote audio monitor for instant bot speaking detection. */
|
|
1462
|
+
this.remoteAudioContext = null;
|
|
1463
|
+
this.remoteSpeakingRAF = null;
|
|
1464
|
+
this.speakingSubject = new rxjs.BehaviorSubject(false);
|
|
1465
|
+
this.userSpeakingSubject = new rxjs.BehaviorSubject(false);
|
|
1466
|
+
this.micMutedSubject = new rxjs.BehaviorSubject(false);
|
|
1467
|
+
this.localStreamSubject = new rxjs.BehaviorSubject(null);
|
|
1468
|
+
this.firstRemoteAudioFrameSubject = new rxjs.BehaviorSubject(false);
|
|
1469
|
+
/** True when bot (remote participant) is the active speaker. */
|
|
1470
|
+
this.speaking$ = this.speakingSubject.asObservable();
|
|
1471
|
+
/** True when user (local participant) is the active speaker. */
|
|
1472
|
+
this.userSpeaking$ = this.userSpeakingSubject.asObservable();
|
|
1473
|
+
/** True when mic is muted. */
|
|
1474
|
+
this.micMuted$ = this.micMutedSubject.asObservable();
|
|
1475
|
+
/** Emits local mic stream for waveform visualization. */
|
|
1476
|
+
this.localStream$ = this.localStreamSubject.asObservable();
|
|
1477
|
+
/** Emits true once when first remote audio frame starts playing. */
|
|
1478
|
+
this.firstRemoteAudioFrame$ = this.firstRemoteAudioFrameSubject.asObservable();
|
|
1479
|
+
}
|
|
1480
|
+
/**
|
|
1481
|
+
* Prompt for microphone access up front so callers can handle permission
|
|
1482
|
+
* denial before creating backend room/session resources.
|
|
1483
|
+
*/
|
|
1484
|
+
DailyVoiceClientService.prototype.ensureMicrophoneAccess = function () {
|
|
1485
|
+
var _a;
|
|
1486
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
1487
|
+
var stream;
|
|
1488
|
+
return __generator(this, function (_e) {
|
|
1489
|
+
switch (_e.label) {
|
|
1490
|
+
case 0:
|
|
1491
|
+
if (!((_a = navigator === null || navigator === void 0 ? void 0 : navigator.mediaDevices) === null || _a === void 0 ? void 0 : _a.getUserMedia)) {
|
|
1492
|
+
throw new Error('Microphone API unavailable');
|
|
1493
|
+
}
|
|
1494
|
+
return [4 /*yield*/, navigator.mediaDevices.getUserMedia({ audio: true })];
|
|
1495
|
+
case 1:
|
|
1496
|
+
stream = _e.sent();
|
|
1497
|
+
stream.getTracks().forEach(function (t) { return t.stop(); });
|
|
1498
|
+
return [2 /*return*/];
|
|
1499
|
+
}
|
|
1500
|
+
});
|
|
1501
|
+
});
|
|
1502
|
+
};
|
|
1503
|
+
/**
|
|
1504
|
+
* Connect to Daily room. Acquires mic first for waveform, then joins with audio.
|
|
1505
|
+
* @param roomUrl Daily room URL (from room_created)
|
|
1506
|
+
* @param token Optional meeting token
|
|
1507
|
+
*/
|
|
1508
|
+
DailyVoiceClientService.prototype.connect = function (roomUrl, token) {
|
|
1509
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
1510
|
+
var stream, audioTrack, callObject, joinOptions, participants, err_1;
|
|
1511
|
+
return __generator(this, function (_e) {
|
|
1512
|
+
switch (_e.label) {
|
|
1513
|
+
case 0:
|
|
1514
|
+
if (!this.callObject) return [3 /*break*/, 2];
|
|
1515
|
+
return [4 /*yield*/, this.disconnect()];
|
|
1516
|
+
case 1:
|
|
1517
|
+
_e.sent();
|
|
1518
|
+
_e.label = 2;
|
|
1519
|
+
case 2:
|
|
1520
|
+
_e.trys.push([2, 5, , 6]);
|
|
1521
|
+
return [4 /*yield*/, navigator.mediaDevices.getUserMedia({ audio: true })];
|
|
1522
|
+
case 3:
|
|
1523
|
+
stream = _e.sent();
|
|
1524
|
+
audioTrack = stream.getAudioTracks()[0];
|
|
1525
|
+
if (!audioTrack) {
|
|
1526
|
+
stream.getTracks().forEach(function (t) { return t.stop(); });
|
|
1527
|
+
throw new Error('No audio track');
|
|
1528
|
+
}
|
|
1529
|
+
this.localStream = stream;
|
|
1530
|
+
this.localStreamSubject.next(stream);
|
|
1531
|
+
callObject = Daily__default["default"].createCallObject({
|
|
1532
|
+
videoSource: false,
|
|
1533
|
+
audioSource: audioTrack,
|
|
1534
|
+
});
|
|
1535
|
+
this.callObject = callObject;
|
|
1536
|
+
this.setupEventHandlers(callObject);
|
|
1537
|
+
joinOptions = { url: roomUrl };
|
|
1538
|
+
if (typeof token === 'string' && token.trim() !== '') {
|
|
1539
|
+
joinOptions.token = token;
|
|
1540
|
+
}
|
|
1541
|
+
return [4 /*yield*/, callObject.join(joinOptions)];
|
|
1542
|
+
case 4:
|
|
1543
|
+
_e.sent();
|
|
1544
|
+
console.log("[VoiceDebug] Room connected (Daily join complete) \u2014 " + new Date().toISOString());
|
|
1545
|
+
participants = callObject.participants();
|
|
1546
|
+
if (participants === null || participants === void 0 ? void 0 : participants.local) {
|
|
1547
|
+
this.localSessionId = participants.local.session_id;
|
|
1548
|
+
}
|
|
1549
|
+
// Start with mic muted; VoiceAgentService auto-unmutes after first remote audio frame.
|
|
1550
|
+
callObject.setLocalAudio(false);
|
|
1551
|
+
this.micMutedSubject.next(true);
|
|
1552
|
+
return [3 /*break*/, 6];
|
|
1553
|
+
case 5:
|
|
1554
|
+
err_1 = _e.sent();
|
|
1555
|
+
this.cleanup();
|
|
1556
|
+
throw err_1;
|
|
1557
|
+
case 6: return [2 /*return*/];
|
|
1558
|
+
}
|
|
1559
|
+
});
|
|
1560
|
+
});
|
|
1561
|
+
};
|
|
1562
|
+
DailyVoiceClientService.prototype.setupEventHandlers = function (call) {
|
|
1563
|
+
var _this = this;
|
|
1564
|
+
// active-speaker-change: used ONLY for user speaking detection.
|
|
1565
|
+
// Bot speaking is detected by our own AnalyserNode (instant, no debounce).
|
|
1566
|
+
call.on('active-speaker-change', function (event) {
|
|
1567
|
+
_this.ngZone.run(function () {
|
|
1568
|
+
var _a;
|
|
1569
|
+
var peerId = (_a = event === null || event === void 0 ? void 0 : event.activeSpeaker) === null || _a === void 0 ? void 0 : _a.peerId;
|
|
1570
|
+
if (!peerId || !_this.localSessionId) {
|
|
1571
|
+
_this.userSpeakingSubject.next(false);
|
|
1572
|
+
return;
|
|
1573
|
+
}
|
|
1574
|
+
var isLocal = peerId === _this.localSessionId;
|
|
1575
|
+
_this.userSpeakingSubject.next(isLocal);
|
|
1576
|
+
});
|
|
1577
|
+
});
|
|
1578
|
+
// track-started / track-stopped: set up remote audio playback + AnalyserNode monitor.
|
|
1579
|
+
call.on('track-started', function (event) {
|
|
1580
|
+
_this.ngZone.run(function () {
|
|
1581
|
+
var _a, _b, _c, _d;
|
|
1582
|
+
var p = event === null || event === void 0 ? void 0 : event.participant;
|
|
1583
|
+
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;
|
|
1584
|
+
var track = event === null || event === void 0 ? void 0 : event.track;
|
|
1585
|
+
if (p && !p.local && type === 'audio') {
|
|
1586
|
+
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());
|
|
1587
|
+
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;
|
|
1588
|
+
if (audioTrack && typeof audioTrack === 'object') {
|
|
1589
|
+
_this.playRemoteTrack(audioTrack);
|
|
1590
|
+
_this.monitorRemoteAudio(audioTrack);
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
});
|
|
1594
|
+
});
|
|
1595
|
+
call.on('track-stopped', function (event) {
|
|
1596
|
+
_this.ngZone.run(function () {
|
|
1597
|
+
var _a, _b;
|
|
1598
|
+
var p = event === null || event === void 0 ? void 0 : event.participant;
|
|
1599
|
+
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;
|
|
1600
|
+
if (p && !p.local && type === 'audio') {
|
|
1601
|
+
_this.stopRemoteAudioMonitor();
|
|
1602
|
+
_this.stopRemoteAudio();
|
|
1603
|
+
}
|
|
1604
|
+
});
|
|
1605
|
+
});
|
|
1606
|
+
call.on('left-meeting', function () {
|
|
1607
|
+
_this.ngZone.run(function () { return _this.cleanup(); });
|
|
1608
|
+
});
|
|
1609
|
+
call.on('error', function (event) {
|
|
1610
|
+
_this.ngZone.run(function () {
|
|
1611
|
+
var _a;
|
|
1612
|
+
console.error('DailyVoiceClient: Daily error', (_a = event === null || event === void 0 ? void 0 : event.errorMsg) !== null && _a !== void 0 ? _a : event);
|
|
1613
|
+
_this.cleanup();
|
|
1614
|
+
});
|
|
1615
|
+
});
|
|
1616
|
+
};
|
|
1617
|
+
/**
|
|
1618
|
+
* Play remote (bot) audio track via a dedicated audio element.
|
|
1619
|
+
* Required in many browsers where Daily's internal playback does not output to speakers.
|
|
1620
|
+
*/
|
|
1621
|
+
DailyVoiceClientService.prototype.playRemoteTrack = function (track) {
|
|
1622
|
+
var _this = this;
|
|
1623
|
+
this.stopRemoteAudio();
|
|
1624
|
+
try {
|
|
1625
|
+
console.log("[VoiceDebug] playRemoteTrack called \u2014 track.readyState=" + track.readyState + ", track.muted=" + track.muted + " \u2014 " + new Date().toISOString());
|
|
1626
|
+
track.onunmute = function () {
|
|
1627
|
+
console.log("[VoiceDebug] Remote audio track UNMUTED (audio data arriving) \u2014 " + new Date().toISOString());
|
|
1628
|
+
};
|
|
1629
|
+
var stream = new MediaStream([track]);
|
|
1630
|
+
var audio = new Audio();
|
|
1631
|
+
audio.autoplay = true;
|
|
1632
|
+
audio.srcObject = stream;
|
|
1633
|
+
this.remoteAudioElement = audio;
|
|
1634
|
+
audio.onplaying = function () {
|
|
1635
|
+
console.log("[VoiceDebug] Audio element PLAYING (browser started playback) \u2014 " + new Date().toISOString());
|
|
1636
|
+
};
|
|
1637
|
+
var firstTimeUpdate_1 = true;
|
|
1638
|
+
audio.ontimeupdate = function () {
|
|
1639
|
+
if (firstTimeUpdate_1) {
|
|
1640
|
+
firstTimeUpdate_1 = false;
|
|
1641
|
+
console.log("[VoiceDebug] Audio element first TIMEUPDATE (actual audio output) \u2014 " + new Date().toISOString());
|
|
1642
|
+
_this.firstRemoteAudioFrameSubject.next(true);
|
|
1643
|
+
}
|
|
1644
|
+
};
|
|
1645
|
+
var p = audio.play();
|
|
1646
|
+
if (p && typeof p.then === 'function') {
|
|
1647
|
+
p.then(function () {
|
|
1648
|
+
console.log("[VoiceDebug] audio.play() resolved \u2014 " + new Date().toISOString());
|
|
1649
|
+
_this.firstRemoteAudioFrameSubject.next(true);
|
|
1650
|
+
}).catch(function (err) {
|
|
1651
|
+
console.warn('DailyVoiceClient: remote audio play failed (may need user gesture)', err);
|
|
1652
|
+
});
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
catch (err) {
|
|
1656
|
+
console.warn('DailyVoiceClient: failed to create remote audio element', err);
|
|
1657
|
+
}
|
|
1658
|
+
};
|
|
1659
|
+
/**
|
|
1660
|
+
* Monitor remote audio track energy via AnalyserNode.
|
|
1661
|
+
* Polls at ~60fps and flips speakingSubject based on actual audio energy.
|
|
1662
|
+
*/
|
|
1663
|
+
DailyVoiceClientService.prototype.monitorRemoteAudio = function (track) {
|
|
1664
|
+
var _this = this;
|
|
1665
|
+
this.stopRemoteAudioMonitor();
|
|
1666
|
+
try {
|
|
1667
|
+
var ctx = new AudioContext();
|
|
1668
|
+
var source = ctx.createMediaStreamSource(new MediaStream([track]));
|
|
1669
|
+
var analyser_1 = ctx.createAnalyser();
|
|
1670
|
+
analyser_1.fftSize = 256;
|
|
1671
|
+
source.connect(analyser_1);
|
|
1672
|
+
this.remoteAudioContext = ctx;
|
|
1673
|
+
var dataArray_1 = new Uint8Array(analyser_1.frequencyBinCount);
|
|
1674
|
+
var THRESHOLD_1 = 5;
|
|
1675
|
+
var SILENCE_MS_1 = 1500;
|
|
1676
|
+
var lastSoundTime_1 = 0;
|
|
1677
|
+
var isSpeaking_1 = false;
|
|
1678
|
+
var poll_1 = function () {
|
|
1679
|
+
if (!_this.remoteAudioContext)
|
|
1680
|
+
return;
|
|
1681
|
+
analyser_1.getByteFrequencyData(dataArray_1);
|
|
1682
|
+
var sum = 0;
|
|
1683
|
+
for (var i = 0; i < dataArray_1.length; i++) {
|
|
1684
|
+
sum += dataArray_1[i];
|
|
1685
|
+
}
|
|
1686
|
+
var avg = sum / dataArray_1.length;
|
|
1687
|
+
var now = Date.now();
|
|
1688
|
+
if (avg > THRESHOLD_1) {
|
|
1689
|
+
lastSoundTime_1 = now;
|
|
1690
|
+
if (!isSpeaking_1) {
|
|
1691
|
+
isSpeaking_1 = true;
|
|
1692
|
+
console.log("[VoiceDebug] Bot audio energy detected (speaking=true) \u2014 avg=" + avg.toFixed(1) + " \u2014 " + new Date().toISOString());
|
|
1693
|
+
_this.ngZone.run(function () {
|
|
1694
|
+
_this.userSpeakingSubject.next(false);
|
|
1695
|
+
_this.speakingSubject.next(true);
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
else if (isSpeaking_1 && now - lastSoundTime_1 > SILENCE_MS_1) {
|
|
1700
|
+
isSpeaking_1 = false;
|
|
1701
|
+
console.log("[VoiceDebug] Bot audio silence detected (speaking=false) \u2014 " + new Date().toISOString());
|
|
1702
|
+
_this.ngZone.run(function () { return _this.speakingSubject.next(false); });
|
|
1703
|
+
}
|
|
1704
|
+
_this.remoteSpeakingRAF = requestAnimationFrame(poll_1);
|
|
1705
|
+
};
|
|
1706
|
+
this.remoteSpeakingRAF = requestAnimationFrame(poll_1);
|
|
1707
|
+
}
|
|
1708
|
+
catch (err) {
|
|
1709
|
+
console.warn('DailyVoiceClient: failed to create remote audio monitor', err);
|
|
1710
|
+
}
|
|
1711
|
+
};
|
|
1712
|
+
DailyVoiceClientService.prototype.stopRemoteAudioMonitor = function () {
|
|
1713
|
+
if (this.remoteSpeakingRAF) {
|
|
1714
|
+
cancelAnimationFrame(this.remoteSpeakingRAF);
|
|
1715
|
+
this.remoteSpeakingRAF = null;
|
|
1716
|
+
}
|
|
1717
|
+
if (this.remoteAudioContext) {
|
|
1718
|
+
this.remoteAudioContext.close().catch(function () { });
|
|
1719
|
+
this.remoteAudioContext = null;
|
|
1720
|
+
}
|
|
1721
|
+
};
|
|
1722
|
+
DailyVoiceClientService.prototype.stopRemoteAudio = function () {
|
|
1723
|
+
if (this.remoteAudioElement) {
|
|
1724
|
+
try {
|
|
1725
|
+
this.remoteAudioElement.pause();
|
|
1726
|
+
this.remoteAudioElement.srcObject = null;
|
|
1727
|
+
}
|
|
1728
|
+
catch (_) { }
|
|
1729
|
+
this.remoteAudioElement = null;
|
|
1730
|
+
}
|
|
1731
|
+
};
|
|
1732
|
+
/** Set mic muted state. */
|
|
1733
|
+
DailyVoiceClientService.prototype.setMuted = function (muted) {
|
|
1734
|
+
if (!this.callObject)
|
|
1735
|
+
return;
|
|
1736
|
+
this.callObject.setLocalAudio(!muted);
|
|
1737
|
+
this.micMutedSubject.next(muted);
|
|
1738
|
+
};
|
|
1739
|
+
/** Disconnect and cleanup. */
|
|
1740
|
+
DailyVoiceClientService.prototype.disconnect = function () {
|
|
1741
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
1742
|
+
var e_1;
|
|
1743
|
+
return __generator(this, function (_e) {
|
|
1744
|
+
switch (_e.label) {
|
|
1745
|
+
case 0:
|
|
1746
|
+
if (!this.callObject) {
|
|
1747
|
+
this.cleanup();
|
|
1748
|
+
return [2 /*return*/];
|
|
1749
|
+
}
|
|
1750
|
+
_e.label = 1;
|
|
1751
|
+
case 1:
|
|
1752
|
+
_e.trys.push([1, 3, , 4]);
|
|
1753
|
+
return [4 /*yield*/, this.callObject.leave()];
|
|
1754
|
+
case 2:
|
|
1755
|
+
_e.sent();
|
|
1756
|
+
return [3 /*break*/, 4];
|
|
1757
|
+
case 3:
|
|
1758
|
+
e_1 = _e.sent();
|
|
1759
|
+
return [3 /*break*/, 4];
|
|
1760
|
+
case 4:
|
|
1761
|
+
this.cleanup();
|
|
1762
|
+
return [2 /*return*/];
|
|
1763
|
+
}
|
|
1764
|
+
});
|
|
1765
|
+
});
|
|
1766
|
+
};
|
|
1767
|
+
DailyVoiceClientService.prototype.cleanup = function () {
|
|
1768
|
+
this.stopRemoteAudioMonitor();
|
|
1769
|
+
this.stopRemoteAudio();
|
|
1770
|
+
if (this.callObject) {
|
|
1771
|
+
this.callObject.destroy().catch(function () { });
|
|
1772
|
+
this.callObject = null;
|
|
1773
|
+
}
|
|
1774
|
+
if (this.localStream) {
|
|
1775
|
+
this.localStream.getTracks().forEach(function (t) { return t.stop(); });
|
|
1776
|
+
this.localStream = null;
|
|
1777
|
+
}
|
|
1778
|
+
this.localSessionId = null;
|
|
1779
|
+
this.speakingSubject.next(false);
|
|
1780
|
+
this.userSpeakingSubject.next(false);
|
|
1781
|
+
this.localStreamSubject.next(null);
|
|
1782
|
+
this.firstRemoteAudioFrameSubject.next(false);
|
|
1783
|
+
// Keep last micMuted state; will reset on next connect
|
|
1784
|
+
};
|
|
1785
|
+
return DailyVoiceClientService;
|
|
1786
|
+
}());
|
|
1787
|
+
DailyVoiceClientService.ɵprov = i0__namespace.ɵɵdefineInjectable({ factory: function DailyVoiceClientService_Factory() { return new DailyVoiceClientService(i0__namespace.ɵɵinject(i0__namespace.NgZone)); }, token: DailyVoiceClientService, providedIn: "root" });
|
|
1788
|
+
DailyVoiceClientService.decorators = [
|
|
1789
|
+
{ type: i0.Injectable, args: [{
|
|
1790
|
+
providedIn: 'root',
|
|
1791
|
+
},] }
|
|
1792
|
+
];
|
|
1793
|
+
DailyVoiceClientService.ctorParameters = function () { return [
|
|
1794
|
+
{ type: i0.NgZone }
|
|
1795
|
+
]; };
|
|
1796
|
+
|
|
1797
|
+
/**
|
|
1798
|
+
* Voice agent orchestrator. Coordinates WebSocket (signaling) and Daily.js (WebRTC audio).
|
|
1343
1799
|
*
|
|
1344
|
-
*
|
|
1345
|
-
*
|
|
1346
|
-
*
|
|
1347
|
-
*
|
|
1348
|
-
*
|
|
1349
|
-
*
|
|
1350
|
-
*
|
|
1800
|
+
* CRITICAL: This service must NEVER use Socket.IO or ngx-socket-io. Voice flow uses only:
|
|
1801
|
+
* - Native WebSocket (WebSocketVoiceClientService) for signaling (room_created, transcripts)
|
|
1802
|
+
* - Daily.js (DailyVoiceClientService) for WebRTC audio. Audio does NOT flow over WebSocket.
|
|
1803
|
+
*
|
|
1804
|
+
* - Maintains callState, statusText, duration, isMicMuted, isUserSpeaking, audioLevels
|
|
1805
|
+
* - Uses WebSocket for room_created and transcripts only (no audio)
|
|
1806
|
+
* - Uses Daily.js for all audio, mic, and real-time speaking detection
|
|
1351
1807
|
*/
|
|
1352
1808
|
var VoiceAgentService = /** @class */ (function () {
|
|
1353
|
-
function VoiceAgentService(audioAnalyzer,
|
|
1809
|
+
function VoiceAgentService(audioAnalyzer, wsClient, dailyClient, platformTokenRefresh,
|
|
1354
1810
|
/** `Object` not `object` — ngc metadata collection rejects the `object` type in DI params. */
|
|
1355
1811
|
platformId) {
|
|
1356
1812
|
var _this = this;
|
|
1357
1813
|
this.audioAnalyzer = audioAnalyzer;
|
|
1814
|
+
this.wsClient = wsClient;
|
|
1815
|
+
this.dailyClient = dailyClient;
|
|
1358
1816
|
this.platformTokenRefresh = platformTokenRefresh;
|
|
1359
|
-
this.ngZone = ngZone;
|
|
1360
1817
|
this.platformId = platformId;
|
|
1361
1818
|
this.callStateSubject = new rxjs.BehaviorSubject('idle');
|
|
1362
1819
|
this.statusTextSubject = new rxjs.BehaviorSubject('');
|
|
@@ -1368,10 +1825,9 @@
|
|
|
1368
1825
|
this.botTranscriptSubject = new rxjs.Subject();
|
|
1369
1826
|
this.callStartTime = 0;
|
|
1370
1827
|
this.durationInterval = null;
|
|
1371
|
-
this.pcClient = null;
|
|
1372
|
-
this.botAudioElement = null;
|
|
1373
1828
|
this.subscriptions = new rxjs.Subscription();
|
|
1374
1829
|
this.destroy$ = new rxjs.Subject();
|
|
1830
|
+
this.hasAutoUnmutedAfterFirstAudio = false;
|
|
1375
1831
|
this.callState$ = this.callStateSubject.asObservable();
|
|
1376
1832
|
this.statusText$ = this.statusTextSubject.asObservable();
|
|
1377
1833
|
this.duration$ = this.durationSubject.asObservable();
|
|
@@ -1380,279 +1836,271 @@
|
|
|
1380
1836
|
this.audioLevels$ = this.audioLevelsSubject.asObservable();
|
|
1381
1837
|
this.userTranscript$ = this.userTranscriptSubject.asObservable();
|
|
1382
1838
|
this.botTranscript$ = this.botTranscriptSubject.asObservable();
|
|
1839
|
+
// Waveform visualization only - do NOT use for speaking state
|
|
1383
1840
|
this.subscriptions.add(this.audioAnalyzer.audioLevels$.subscribe(function (levels) { return _this.audioLevelsSubject.next(levels); }));
|
|
1384
1841
|
}
|
|
1385
1842
|
VoiceAgentService.prototype.ngOnDestroy = function () {
|
|
1386
1843
|
this.destroy$.next();
|
|
1387
1844
|
this.subscriptions.unsubscribe();
|
|
1388
|
-
|
|
1845
|
+
this.disconnect();
|
|
1389
1846
|
};
|
|
1390
|
-
/** Reset to idle (e.g. when modal
|
|
1847
|
+
/** Reset to idle state (e.g. when modal opens so user can click Start Call). */
|
|
1391
1848
|
VoiceAgentService.prototype.resetToIdle = function () {
|
|
1392
1849
|
if (this.callStateSubject.value === 'idle')
|
|
1393
1850
|
return;
|
|
1394
|
-
|
|
1851
|
+
this.stopDurationTimer();
|
|
1852
|
+
this.audioAnalyzer.stop();
|
|
1853
|
+
this.wsClient.disconnect();
|
|
1854
|
+
// Fire-and-forget: Daily disconnect is async; connect() will await if needed
|
|
1855
|
+
void this.dailyClient.disconnect();
|
|
1395
1856
|
this.callStateSubject.next('idle');
|
|
1396
1857
|
this.statusTextSubject.next('');
|
|
1397
1858
|
this.durationSubject.next('0:00');
|
|
1859
|
+
this.hasAutoUnmutedAfterFirstAudio = false;
|
|
1398
1860
|
};
|
|
1399
1861
|
VoiceAgentService.prototype.connect = function (apiUrl, token, botId, conversationId, apiKey, eventToken, eventId, eventUrl, domainAuthority, usersApiUrl) {
|
|
1400
1862
|
return __awaiter(this, void 0, void 0, function () {
|
|
1401
|
-
var accessToken, ensured, e_1, baseUrl,
|
|
1863
|
+
var accessToken, ensured, e_1, baseUrl, postUrl, headers, res, json, wsUrl, error_1;
|
|
1402
1864
|
var _this = this;
|
|
1403
|
-
return __generator(this, function (
|
|
1404
|
-
switch (
|
|
1865
|
+
return __generator(this, function (_a) {
|
|
1866
|
+
switch (_a.label) {
|
|
1405
1867
|
case 0:
|
|
1406
1868
|
if (this.callStateSubject.value !== 'idle') {
|
|
1407
|
-
console.warn('
|
|
1869
|
+
console.warn('Call already in progress');
|
|
1408
1870
|
return [2 /*return*/];
|
|
1409
1871
|
}
|
|
1410
|
-
|
|
1872
|
+
_a.label = 1;
|
|
1411
1873
|
case 1:
|
|
1412
|
-
|
|
1874
|
+
_a.trys.push([1, 9, , 13]);
|
|
1413
1875
|
this.callStateSubject.next('connecting');
|
|
1414
1876
|
this.statusTextSubject.next('Connecting...');
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
_d.label = 2;
|
|
1877
|
+
// Request mic permission before provisioning backend room flow.
|
|
1878
|
+
return [4 /*yield*/, this.dailyClient.ensureMicrophoneAccess()];
|
|
1418
1879
|
case 2:
|
|
1419
|
-
|
|
1880
|
+
// Request mic permission before provisioning backend room flow.
|
|
1881
|
+
_a.sent();
|
|
1882
|
+
accessToken = token;
|
|
1883
|
+
if (!(usersApiUrl && common.isPlatformBrowser(this.platformId))) return [3 /*break*/, 6];
|
|
1884
|
+
_a.label = 3;
|
|
1885
|
+
case 3:
|
|
1886
|
+
_a.trys.push([3, 5, , 6]);
|
|
1420
1887
|
return [4 /*yield*/, this.platformTokenRefresh
|
|
1421
1888
|
.ensureValidAccessToken(token, usersApiUrl)
|
|
1422
1889
|
.pipe(operators.take(1))
|
|
1423
1890
|
.toPromise()];
|
|
1424
|
-
case 3:
|
|
1425
|
-
ensured = _d.sent();
|
|
1426
|
-
if (ensured === null || ensured === void 0 ? void 0 : ensured.accessToken)
|
|
1427
|
-
accessToken = ensured.accessToken;
|
|
1428
|
-
return [3 /*break*/, 5];
|
|
1429
1891
|
case 4:
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1892
|
+
ensured = _a.sent();
|
|
1893
|
+
if (ensured === null || ensured === void 0 ? void 0 : ensured.accessToken) {
|
|
1894
|
+
accessToken = ensured.accessToken;
|
|
1895
|
+
}
|
|
1896
|
+
return [3 /*break*/, 6];
|
|
1433
1897
|
case 5:
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
enableMic: true,
|
|
1438
|
-
enableCam: false,
|
|
1439
|
-
callbacks: {
|
|
1440
|
-
onConnected: function () { return _this.ngZone.run(function () { return _this.onPipecatConnected(); }); },
|
|
1441
|
-
onDisconnected: function () { return _this.ngZone.run(function () { return _this.onPipecatDisconnected(); }); },
|
|
1442
|
-
onBotReady: function () { return _this.ngZone.run(function () { return _this.onBotReady(); }); },
|
|
1443
|
-
onUserTranscript: function (data) { return _this.ngZone.run(function () { return _this.userTranscriptSubject.next({ text: data.text, final: !!data.final }); }); },
|
|
1444
|
-
onBotTranscript: function (data) { return _this.ngZone.run(function () { return _this.botTranscriptSubject.next(data.text); }); },
|
|
1445
|
-
onError: function (err) {
|
|
1446
|
-
_this.ngZone.run(function () {
|
|
1447
|
-
console.error('[HiveGpt Voice] PipecatClient error', err);
|
|
1448
|
-
_this.callStateSubject.next('ended');
|
|
1449
|
-
_this.statusTextSubject.next('Connection failed');
|
|
1450
|
-
});
|
|
1451
|
-
},
|
|
1452
|
-
},
|
|
1453
|
-
});
|
|
1454
|
-
this.pcClient = pcClient;
|
|
1455
|
-
// Bot audio arrives as a MediaStreamTrack — wire to a hidden <audio> element
|
|
1456
|
-
pcClient.on(clientJs.RTVIEvent.TrackStarted, function (track, participant) {
|
|
1457
|
-
if (!(participant === null || participant === void 0 ? void 0 : participant.local) && track.kind === 'audio') {
|
|
1458
|
-
_this.ngZone.run(function () { return _this.setupBotAudioTrack(track); });
|
|
1459
|
-
}
|
|
1460
|
-
});
|
|
1461
|
-
// Speaking state comes straight from RTVI events
|
|
1462
|
-
pcClient.on(clientJs.RTVIEvent.BotStartedSpeaking, function () { return _this.ngZone.run(function () { return _this.onBotStartedSpeaking(); }); });
|
|
1463
|
-
pcClient.on(clientJs.RTVIEvent.BotStoppedSpeaking, function () { return _this.ngZone.run(function () { return _this.onBotStoppedSpeaking(); }); });
|
|
1464
|
-
pcClient.on(clientJs.RTVIEvent.UserStartedSpeaking, function () { return _this.ngZone.run(function () {
|
|
1465
|
-
_this.isUserSpeakingSubject.next(true);
|
|
1466
|
-
_this.callStateSubject.next('listening');
|
|
1467
|
-
_this.statusTextSubject.next('Listening...');
|
|
1468
|
-
}); });
|
|
1469
|
-
pcClient.on(clientJs.RTVIEvent.UserStoppedSpeaking, function () { return _this.ngZone.run(function () {
|
|
1470
|
-
_this.isUserSpeakingSubject.next(false);
|
|
1471
|
-
if (_this.callStateSubject.value === 'listening') {
|
|
1472
|
-
// Brief 'Processing...' while we wait for the bot to respond.
|
|
1473
|
-
_this.callStateSubject.next('connected');
|
|
1474
|
-
_this.statusTextSubject.next('Processing...');
|
|
1475
|
-
}
|
|
1476
|
-
}); });
|
|
1477
|
-
// Acquire mic (triggers browser permission prompt)
|
|
1478
|
-
return [4 /*yield*/, pcClient.initDevices()];
|
|
1898
|
+
e_1 = _a.sent();
|
|
1899
|
+
console.warn('[HiveGpt Voice] Token refresh before connect failed', e_1);
|
|
1900
|
+
return [3 /*break*/, 6];
|
|
1479
1901
|
case 6:
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1902
|
+
baseUrl = apiUrl.replace(/\/$/, '');
|
|
1903
|
+
postUrl = baseUrl + "/ai/ask-voice";
|
|
1904
|
+
headers = {
|
|
1905
|
+
'Content-Type': 'application/json',
|
|
1906
|
+
Authorization: "Bearer " + accessToken,
|
|
1907
|
+
'x-api-key': apiKey,
|
|
1908
|
+
'hive-bot-id': botId,
|
|
1909
|
+
'domain-authority': domainAuthority,
|
|
1910
|
+
eventUrl: eventUrl,
|
|
1911
|
+
eventId: eventId,
|
|
1912
|
+
eventToken: eventToken,
|
|
1913
|
+
'ngrok-skip-browser-warning': 'true',
|
|
1914
|
+
};
|
|
1915
|
+
return [4 /*yield*/, fetch(postUrl, {
|
|
1916
|
+
method: 'POST',
|
|
1917
|
+
headers: headers,
|
|
1918
|
+
body: JSON.stringify({
|
|
1496
1919
|
bot_id: botId,
|
|
1497
1920
|
conversation_id: conversationId,
|
|
1498
1921
|
voice: 'alloy',
|
|
1499
|
-
},
|
|
1922
|
+
}),
|
|
1500
1923
|
})];
|
|
1501
1924
|
case 7:
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1925
|
+
res = _a.sent();
|
|
1926
|
+
if (!res.ok) {
|
|
1927
|
+
throw new Error("HTTP " + res.status);
|
|
1928
|
+
}
|
|
1929
|
+
return [4 /*yield*/, res.json()];
|
|
1505
1930
|
case 8:
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1931
|
+
json = _a.sent();
|
|
1932
|
+
wsUrl = json === null || json === void 0 ? void 0 : json.rn_ws_url;
|
|
1933
|
+
if (!wsUrl || typeof wsUrl !== 'string') {
|
|
1934
|
+
throw new Error('No ws_url in response');
|
|
1935
|
+
}
|
|
1936
|
+
// Subscribe to room_created BEFORE connecting to avoid race
|
|
1937
|
+
this.wsClient.roomCreated$
|
|
1938
|
+
.pipe(operators.take(1), operators.takeUntil(this.destroy$))
|
|
1939
|
+
.subscribe(function (roomUrl) { return __awaiter(_this, void 0, void 0, function () {
|
|
1940
|
+
var err_1;
|
|
1941
|
+
return __generator(this, function (_a) {
|
|
1942
|
+
switch (_a.label) {
|
|
1943
|
+
case 0:
|
|
1944
|
+
_a.trys.push([0, 2, , 6]);
|
|
1945
|
+
return [4 /*yield*/, this.onRoomCreated(roomUrl)];
|
|
1946
|
+
case 1:
|
|
1947
|
+
_a.sent();
|
|
1948
|
+
return [3 /*break*/, 6];
|
|
1949
|
+
case 2:
|
|
1950
|
+
err_1 = _a.sent();
|
|
1951
|
+
console.error('Daily join failed:', err_1);
|
|
1952
|
+
if (!this.isMicPermissionError(err_1)) return [3 /*break*/, 4];
|
|
1953
|
+
return [4 /*yield*/, this.cleanupMediaAndSignal()];
|
|
1954
|
+
case 3:
|
|
1955
|
+
_a.sent();
|
|
1956
|
+
this.callStateSubject.next('idle');
|
|
1957
|
+
this.durationSubject.next('0:00');
|
|
1958
|
+
this.statusTextSubject.next('Microphone permission is required');
|
|
1959
|
+
return [2 /*return*/];
|
|
1960
|
+
case 4:
|
|
1961
|
+
this.callStateSubject.next('ended');
|
|
1962
|
+
this.statusTextSubject.next('Connection failed');
|
|
1963
|
+
return [4 /*yield*/, this.disconnect()];
|
|
1964
|
+
case 5:
|
|
1965
|
+
_a.sent();
|
|
1966
|
+
return [3 /*break*/, 6];
|
|
1967
|
+
case 6: return [2 /*return*/];
|
|
1968
|
+
}
|
|
1969
|
+
});
|
|
1970
|
+
}); });
|
|
1971
|
+
// Forward transcripts from WebSocket
|
|
1972
|
+
this.subscriptions.add(this.wsClient.userTranscript$
|
|
1973
|
+
.pipe(operators.takeUntil(this.destroy$))
|
|
1974
|
+
.subscribe(function (t) { return _this.userTranscriptSubject.next(t); }));
|
|
1975
|
+
this.subscriptions.add(this.wsClient.botTranscript$
|
|
1976
|
+
.pipe(operators.takeUntil(this.destroy$))
|
|
1977
|
+
.subscribe(function (t) { return _this.botTranscriptSubject.next(t); }));
|
|
1978
|
+
// Connect signaling WebSocket (no audio over WS)
|
|
1979
|
+
this.wsClient.connect(wsUrl);
|
|
1980
|
+
return [3 /*break*/, 13];
|
|
1510
1981
|
case 9:
|
|
1511
|
-
|
|
1982
|
+
error_1 = _a.sent();
|
|
1983
|
+
console.error('Error connecting voice agent:', error_1);
|
|
1984
|
+
if (!this.isMicPermissionError(error_1)) return [3 /*break*/, 11];
|
|
1985
|
+
return [4 /*yield*/, this.cleanupMediaAndSignal()];
|
|
1986
|
+
case 10:
|
|
1987
|
+
_a.sent();
|
|
1988
|
+
this.callStateSubject.next('idle');
|
|
1989
|
+
this.durationSubject.next('0:00');
|
|
1990
|
+
this.statusTextSubject.next('Microphone permission is required');
|
|
1991
|
+
return [2 /*return*/];
|
|
1992
|
+
case 11:
|
|
1993
|
+
this.callStateSubject.next('ended');
|
|
1994
|
+
return [4 /*yield*/, this.disconnect()];
|
|
1995
|
+
case 12:
|
|
1996
|
+
_a.sent();
|
|
1512
1997
|
this.statusTextSubject.next('Connection failed');
|
|
1513
1998
|
throw error_1;
|
|
1514
|
-
case
|
|
1999
|
+
case 13: return [2 /*return*/];
|
|
1515
2000
|
}
|
|
1516
2001
|
});
|
|
1517
2002
|
});
|
|
1518
2003
|
};
|
|
1519
|
-
VoiceAgentService.prototype.
|
|
1520
|
-
// Start the duration timer from the moment the session is live.
|
|
1521
|
-
this.callStartTime = Date.now();
|
|
1522
|
-
this.startDurationTimer();
|
|
1523
|
-
this.callStateSubject.next('connected');
|
|
1524
|
-
this.statusTextSubject.next('Connected');
|
|
1525
|
-
this.isMicMutedSubject.next(false);
|
|
1526
|
-
this.startLocalMicAnalyzer();
|
|
1527
|
-
};
|
|
1528
|
-
VoiceAgentService.prototype.onPipecatDisconnected = function () {
|
|
1529
|
-
this.stopDurationTimer();
|
|
1530
|
-
this.callStartTime = 0;
|
|
1531
|
-
this.audioAnalyzer.stop();
|
|
1532
|
-
this.stopBotAudio();
|
|
1533
|
-
this.callStateSubject.next('ended');
|
|
1534
|
-
this.statusTextSubject.next('Call Ended');
|
|
1535
|
-
};
|
|
1536
|
-
VoiceAgentService.prototype.onBotReady = function () {
|
|
1537
|
-
var _a, _b, _c;
|
|
1538
|
-
// Retry track wiring in case tracks weren't ready at onConnected.
|
|
1539
|
-
this.startLocalMicAnalyzer();
|
|
1540
|
-
var botTrack = (_c = (_b = (_a = this.pcClient) === null || _a === void 0 ? void 0 : _a.tracks()) === null || _b === void 0 ? void 0 : _b.bot) === null || _c === void 0 ? void 0 : _c.audio;
|
|
1541
|
-
if (botTrack)
|
|
1542
|
-
this.setupBotAudioTrack(botTrack);
|
|
1543
|
-
// Bot is initialised — signal that we're now waiting for user input.
|
|
1544
|
-
this.statusTextSubject.next('Listening...');
|
|
1545
|
-
};
|
|
1546
|
-
VoiceAgentService.prototype.startLocalMicAnalyzer = function () {
|
|
1547
|
-
var _a, _b, _c;
|
|
1548
|
-
var localTrack = (_c = (_b = (_a = this.pcClient) === null || _a === void 0 ? void 0 : _a.tracks()) === null || _b === void 0 ? void 0 : _b.local) === null || _c === void 0 ? void 0 : _c.audio;
|
|
1549
|
-
if (localTrack) {
|
|
1550
|
-
this.audioAnalyzer.start(new MediaStream([localTrack]));
|
|
1551
|
-
}
|
|
1552
|
-
};
|
|
1553
|
-
VoiceAgentService.prototype.onBotStartedSpeaking = function () {
|
|
1554
|
-
this.callStateSubject.next('talking');
|
|
1555
|
-
this.statusTextSubject.next('Talking...');
|
|
1556
|
-
// Mark user as no longer speaking when bot takes the turn.
|
|
1557
|
-
this.isUserSpeakingSubject.next(false);
|
|
1558
|
-
};
|
|
1559
|
-
VoiceAgentService.prototype.onBotStoppedSpeaking = function () {
|
|
1560
|
-
if (this.callStateSubject.value === 'talking') {
|
|
1561
|
-
this.callStateSubject.next('connected');
|
|
1562
|
-
this.statusTextSubject.next('Listening...');
|
|
1563
|
-
}
|
|
1564
|
-
};
|
|
1565
|
-
VoiceAgentService.prototype.setupBotAudioTrack = function (track) {
|
|
1566
|
-
var _a;
|
|
1567
|
-
if (!this.botAudioElement) {
|
|
1568
|
-
this.botAudioElement = new Audio();
|
|
1569
|
-
this.botAudioElement.autoplay = true;
|
|
1570
|
-
}
|
|
1571
|
-
var existing = (_a = this.botAudioElement.srcObject) === null || _a === void 0 ? void 0 : _a.getAudioTracks()[0];
|
|
1572
|
-
if ((existing === null || existing === void 0 ? void 0 : existing.id) === track.id)
|
|
1573
|
-
return;
|
|
1574
|
-
this.botAudioElement.srcObject = new MediaStream([track]);
|
|
1575
|
-
this.botAudioElement.play().catch(function (err) { return console.warn('[HiveGpt Voice] Bot audio play blocked', err); });
|
|
1576
|
-
};
|
|
1577
|
-
VoiceAgentService.prototype.stopBotAudio = function () {
|
|
1578
|
-
var _a;
|
|
1579
|
-
if (this.botAudioElement) {
|
|
1580
|
-
try {
|
|
1581
|
-
this.botAudioElement.pause();
|
|
1582
|
-
(_a = this.botAudioElement.srcObject) === null || _a === void 0 ? void 0 : _a.getAudioTracks().forEach(function (t) { return t.stop(); });
|
|
1583
|
-
this.botAudioElement.srcObject = null;
|
|
1584
|
-
}
|
|
1585
|
-
catch (_b) {
|
|
1586
|
-
// ignore
|
|
1587
|
-
}
|
|
1588
|
-
this.botAudioElement = null;
|
|
1589
|
-
}
|
|
1590
|
-
};
|
|
1591
|
-
VoiceAgentService.prototype.disconnect = function () {
|
|
2004
|
+
VoiceAgentService.prototype.onRoomCreated = function (roomUrl) {
|
|
1592
2005
|
return __awaiter(this, void 0, void 0, function () {
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
this.stopBotAudio();
|
|
1600
|
-
return [4 /*yield*/, this.cleanupPipecatClient()];
|
|
2006
|
+
var _this = this;
|
|
2007
|
+
return __generator(this, function (_a) {
|
|
2008
|
+
switch (_a.label) {
|
|
2009
|
+
case 0:
|
|
2010
|
+
// Connect Daily.js for WebRTC audio
|
|
2011
|
+
return [4 /*yield*/, this.dailyClient.connect(roomUrl)];
|
|
1601
2012
|
case 1:
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
this.
|
|
2013
|
+
// Connect Daily.js for WebRTC audio
|
|
2014
|
+
_a.sent();
|
|
2015
|
+
this.hasAutoUnmutedAfterFirstAudio = false;
|
|
2016
|
+
// Waveform: use local mic stream from Daily client
|
|
2017
|
+
this.dailyClient.localStream$
|
|
2018
|
+
.pipe(operators.filter(function (s) { return s != null; }), operators.take(1))
|
|
2019
|
+
.subscribe(function (stream) {
|
|
2020
|
+
_this.audioAnalyzer.start(stream);
|
|
2021
|
+
});
|
|
2022
|
+
this.subscriptions.add(this.dailyClient.userSpeaking$.subscribe(function (s) { return _this.isUserSpeakingSubject.next(s); }));
|
|
2023
|
+
this.subscriptions.add(rxjs.combineLatest([
|
|
2024
|
+
this.dailyClient.speaking$,
|
|
2025
|
+
this.dailyClient.userSpeaking$,
|
|
2026
|
+
]).subscribe(function (_a) {
|
|
2027
|
+
var _b = __read(_a, 2), bot = _b[0], user = _b[1];
|
|
2028
|
+
var current = _this.callStateSubject.value;
|
|
2029
|
+
if (current === 'connecting' && !bot) {
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
2032
|
+
if (current === 'connecting' && bot) {
|
|
2033
|
+
_this.callStartTime = Date.now();
|
|
2034
|
+
_this.startDurationTimer();
|
|
2035
|
+
_this.callStateSubject.next('talking');
|
|
2036
|
+
return;
|
|
2037
|
+
}
|
|
2038
|
+
if (user) {
|
|
2039
|
+
_this.callStateSubject.next('listening');
|
|
2040
|
+
}
|
|
2041
|
+
else if (bot) {
|
|
2042
|
+
_this.callStateSubject.next('talking');
|
|
2043
|
+
}
|
|
2044
|
+
else if (current === 'talking' || current === 'listening') {
|
|
2045
|
+
_this.callStateSubject.next('connected');
|
|
2046
|
+
}
|
|
2047
|
+
}));
|
|
2048
|
+
this.subscriptions.add(this.dailyClient.micMuted$.subscribe(function (muted) { return _this.isMicMutedSubject.next(muted); }));
|
|
2049
|
+
// One-time auto-unmute after first remote audio frame starts playing.
|
|
2050
|
+
// This keeps initial capture muted until bot audio is heard, then restores normal mic flow.
|
|
2051
|
+
this.subscriptions.add(this.dailyClient.firstRemoteAudioFrame$
|
|
2052
|
+
.pipe(operators.filter(function (hasFirstFrame) { return hasFirstFrame; }), operators.take(1))
|
|
2053
|
+
.subscribe(function () {
|
|
2054
|
+
if (_this.hasAutoUnmutedAfterFirstAudio)
|
|
2055
|
+
return;
|
|
2056
|
+
_this.hasAutoUnmutedAfterFirstAudio = true;
|
|
2057
|
+
if (_this.isMicMutedSubject.value) {
|
|
2058
|
+
_this.dailyClient.setMuted(false);
|
|
2059
|
+
}
|
|
2060
|
+
}));
|
|
2061
|
+
this.statusTextSubject.next('Connecting...');
|
|
1605
2062
|
return [2 /*return*/];
|
|
1606
2063
|
}
|
|
1607
2064
|
});
|
|
1608
2065
|
});
|
|
1609
2066
|
};
|
|
1610
|
-
VoiceAgentService.prototype.
|
|
2067
|
+
VoiceAgentService.prototype.disconnect = function () {
|
|
1611
2068
|
return __awaiter(this, void 0, void 0, function () {
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
switch (_d.label) {
|
|
2069
|
+
return __generator(this, function (_a) {
|
|
2070
|
+
switch (_a.label) {
|
|
1615
2071
|
case 0:
|
|
1616
|
-
|
|
1617
|
-
|
|
2072
|
+
this.stopDurationTimer();
|
|
2073
|
+
this.audioAnalyzer.stop();
|
|
2074
|
+
// Daily first, then WebSocket
|
|
2075
|
+
return [4 /*yield*/, this.dailyClient.disconnect()];
|
|
1618
2076
|
case 1:
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
return [3 /*break*/, 4];
|
|
1627
|
-
case 4:
|
|
1628
|
-
this.pcClient = null;
|
|
1629
|
-
_d.label = 5;
|
|
1630
|
-
case 5: return [2 /*return*/];
|
|
2077
|
+
// Daily first, then WebSocket
|
|
2078
|
+
_a.sent();
|
|
2079
|
+
this.wsClient.disconnect();
|
|
2080
|
+
this.callStateSubject.next('ended');
|
|
2081
|
+
this.statusTextSubject.next('Call Ended');
|
|
2082
|
+
this.hasAutoUnmutedAfterFirstAudio = false;
|
|
2083
|
+
return [2 /*return*/];
|
|
1631
2084
|
}
|
|
1632
2085
|
});
|
|
1633
2086
|
});
|
|
1634
2087
|
};
|
|
1635
2088
|
VoiceAgentService.prototype.toggleMic = function () {
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
var nextMuted = !this.isMicMutedSubject.value;
|
|
1639
|
-
this.pcClient.enableMic(!nextMuted);
|
|
1640
|
-
this.isMicMutedSubject.next(nextMuted);
|
|
1641
|
-
if (nextMuted)
|
|
1642
|
-
this.isUserSpeakingSubject.next(false);
|
|
2089
|
+
var current = this.isMicMutedSubject.value;
|
|
2090
|
+
this.dailyClient.setMuted(!current);
|
|
1643
2091
|
};
|
|
1644
2092
|
VoiceAgentService.prototype.startDurationTimer = function () {
|
|
1645
2093
|
var _this = this;
|
|
1646
|
-
var
|
|
2094
|
+
var updateDuration = function () {
|
|
1647
2095
|
if (_this.callStartTime > 0) {
|
|
1648
2096
|
var elapsed = Math.floor((Date.now() - _this.callStartTime) / 1000);
|
|
1649
|
-
var
|
|
1650
|
-
var
|
|
1651
|
-
_this.durationSubject.next(
|
|
2097
|
+
var minutes = Math.floor(elapsed / 60);
|
|
2098
|
+
var seconds = elapsed % 60;
|
|
2099
|
+
_this.durationSubject.next(minutes + ":" + String(seconds).padStart(2, '0'));
|
|
1652
2100
|
}
|
|
1653
2101
|
};
|
|
1654
|
-
|
|
1655
|
-
this.durationInterval = setInterval(
|
|
2102
|
+
updateDuration();
|
|
2103
|
+
this.durationInterval = setInterval(updateDuration, 1000);
|
|
1656
2104
|
};
|
|
1657
2105
|
VoiceAgentService.prototype.stopDurationTimer = function () {
|
|
1658
2106
|
if (this.durationInterval) {
|
|
@@ -1660,9 +2108,34 @@
|
|
|
1660
2108
|
this.durationInterval = null;
|
|
1661
2109
|
}
|
|
1662
2110
|
};
|
|
2111
|
+
VoiceAgentService.prototype.isMicPermissionError = function (error) {
|
|
2112
|
+
if (!error || typeof error !== 'object')
|
|
2113
|
+
return false;
|
|
2114
|
+
var maybe = error;
|
|
2115
|
+
return (maybe.name === 'NotAllowedError' ||
|
|
2116
|
+
maybe.name === 'PermissionDeniedError' ||
|
|
2117
|
+
maybe.name === 'SecurityError');
|
|
2118
|
+
};
|
|
2119
|
+
VoiceAgentService.prototype.cleanupMediaAndSignal = function () {
|
|
2120
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
2121
|
+
return __generator(this, function (_a) {
|
|
2122
|
+
switch (_a.label) {
|
|
2123
|
+
case 0:
|
|
2124
|
+
this.stopDurationTimer();
|
|
2125
|
+
this.audioAnalyzer.stop();
|
|
2126
|
+
return [4 /*yield*/, this.dailyClient.disconnect()];
|
|
2127
|
+
case 1:
|
|
2128
|
+
_a.sent();
|
|
2129
|
+
this.wsClient.disconnect();
|
|
2130
|
+
this.hasAutoUnmutedAfterFirstAudio = false;
|
|
2131
|
+
return [2 /*return*/];
|
|
2132
|
+
}
|
|
2133
|
+
});
|
|
2134
|
+
});
|
|
2135
|
+
};
|
|
1663
2136
|
return VoiceAgentService;
|
|
1664
2137
|
}());
|
|
1665
|
-
VoiceAgentService.ɵprov = i0__namespace.ɵɵdefineInjectable({ factory: function VoiceAgentService_Factory() { return new VoiceAgentService(i0__namespace.ɵɵinject(AudioAnalyzerService), i0__namespace.ɵɵinject(
|
|
2138
|
+
VoiceAgentService.ɵprov = i0__namespace.ɵɵdefineInjectable({ factory: function VoiceAgentService_Factory() { return new VoiceAgentService(i0__namespace.ɵɵinject(AudioAnalyzerService), i0__namespace.ɵɵinject(WebSocketVoiceClientService), i0__namespace.ɵɵinject(DailyVoiceClientService), i0__namespace.ɵɵinject(PlatformTokenRefreshService), i0__namespace.ɵɵinject(i0__namespace.PLATFORM_ID)); }, token: VoiceAgentService, providedIn: "root" });
|
|
1666
2139
|
VoiceAgentService.decorators = [
|
|
1667
2140
|
{ type: i0.Injectable, args: [{
|
|
1668
2141
|
providedIn: 'root',
|
|
@@ -1670,8 +2143,9 @@
|
|
|
1670
2143
|
];
|
|
1671
2144
|
VoiceAgentService.ctorParameters = function () { return [
|
|
1672
2145
|
{ type: AudioAnalyzerService },
|
|
2146
|
+
{ type: WebSocketVoiceClientService },
|
|
2147
|
+
{ type: DailyVoiceClientService },
|
|
1673
2148
|
{ type: PlatformTokenRefreshService },
|
|
1674
|
-
{ type: i0.NgZone },
|
|
1675
2149
|
{ type: Object, decorators: [{ type: i0.Inject, args: [i0.PLATFORM_ID,] }] }
|
|
1676
2150
|
]; };
|
|
1677
2151
|
|
|
@@ -1702,27 +2176,12 @@
|
|
|
1702
2176
|
this.isMicMuted = false;
|
|
1703
2177
|
this.isUserSpeaking = false;
|
|
1704
2178
|
this.audioLevels = [];
|
|
2179
|
+
this.isSpeaking = false;
|
|
2180
|
+
/** Track whether call has transitioned out of initial connected state. */
|
|
2181
|
+
this.hasLeftConnectedOnce = false;
|
|
1705
2182
|
this.subscriptions = [];
|
|
1706
2183
|
this.isConnecting = false;
|
|
1707
2184
|
}
|
|
1708
|
-
Object.defineProperty(VoiceAgentModalComponent.prototype, "isBotTalking", {
|
|
1709
|
-
/** True while the bot is speaking — drives avatar pulse animation and voice visualizer. */
|
|
1710
|
-
get: function () { return this.callState === 'talking'; },
|
|
1711
|
-
enumerable: false,
|
|
1712
|
-
configurable: true
|
|
1713
|
-
});
|
|
1714
|
-
Object.defineProperty(VoiceAgentModalComponent.prototype, "isUserActive", {
|
|
1715
|
-
/** True while the user is actively speaking — drives waveform active color. */
|
|
1716
|
-
get: function () { return this.callState === 'listening' && this.isUserSpeaking && !this.isMicMuted; },
|
|
1717
|
-
enumerable: false,
|
|
1718
|
-
configurable: true
|
|
1719
|
-
});
|
|
1720
|
-
Object.defineProperty(VoiceAgentModalComponent.prototype, "isProcessing", {
|
|
1721
|
-
/** True during the brief processing pause between user speech and bot response. */
|
|
1722
|
-
get: function () { return this.callState === 'connected' && this.statusText === 'Processing...'; },
|
|
1723
|
-
enumerable: false,
|
|
1724
|
-
configurable: true
|
|
1725
|
-
});
|
|
1726
2185
|
VoiceAgentModalComponent.prototype.ngOnInit = function () {
|
|
1727
2186
|
var _this = this;
|
|
1728
2187
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
@@ -1744,8 +2203,16 @@
|
|
|
1744
2203
|
this.agentAvatar = this.injectedConfig.agentAvatar;
|
|
1745
2204
|
this.usersApiUrl = (_h = this.injectedConfig.usersApiUrl) !== null && _h !== void 0 ? _h : this.usersApiUrl;
|
|
1746
2205
|
}
|
|
2206
|
+
// Subscribe to observables
|
|
1747
2207
|
this.subscriptions.push(this.voiceAgentService.callState$.subscribe(function (state) {
|
|
1748
2208
|
_this.callState = state;
|
|
2209
|
+
_this.isSpeaking = state === 'talking';
|
|
2210
|
+
if (state === 'listening' || state === 'talking') {
|
|
2211
|
+
_this.hasLeftConnectedOnce = true;
|
|
2212
|
+
}
|
|
2213
|
+
if (state === 'idle' || state === 'ended') {
|
|
2214
|
+
_this.hasLeftConnectedOnce = false;
|
|
2215
|
+
}
|
|
1749
2216
|
}));
|
|
1750
2217
|
this.subscriptions.push(this.voiceAgentService.statusText$.subscribe(function (text) {
|
|
1751
2218
|
_this.statusText = text;
|
|
@@ -1830,16 +2297,18 @@
|
|
|
1830
2297
|
return minHeight + (n / 100) * (maxHeight - minHeight);
|
|
1831
2298
|
};
|
|
1832
2299
|
Object.defineProperty(VoiceAgentModalComponent.prototype, "statusLabel", {
|
|
1833
|
-
/** Status label for active call
|
|
2300
|
+
/** Status label for active call. */
|
|
1834
2301
|
get: function () {
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
2302
|
+
if (this.callState === 'connecting')
|
|
2303
|
+
return this.statusText || 'Connecting...';
|
|
2304
|
+
if (this.callState === 'talking')
|
|
2305
|
+
return 'Talking...';
|
|
2306
|
+
if (this.callState === 'listening')
|
|
2307
|
+
return 'Listening';
|
|
2308
|
+
if (this.callState === 'connected') {
|
|
2309
|
+
return this.hasLeftConnectedOnce ? 'Talking...' : 'Connected';
|
|
1842
2310
|
}
|
|
2311
|
+
return this.statusText || '';
|
|
1843
2312
|
},
|
|
1844
2313
|
enumerable: false,
|
|
1845
2314
|
configurable: true
|
|
@@ -1868,8 +2337,8 @@
|
|
|
1868
2337
|
VoiceAgentModalComponent.decorators = [
|
|
1869
2338
|
{ type: i0.Component, args: [{
|
|
1870
2339
|
selector: 'hivegpt-voice-agent-modal',
|
|
1871
|
-
template: "<div class=\"voice-agent-modal-overlay\" (click)=\"endCall()\">\n <div\n class=\"voice-container voice-agent-modal\"\n (click)=\"$event.stopPropagation()\"\n >\n <!-- Header -->\n <div class=\"header\">\n <div class=\"header-left\">\n <div class=\"header-icon\">\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M12 1C8.13 1 5 4.13 5 8V14C5 17.87 8.13 21 12 21C15.87 21 19 17.87 19 14V8C19 4.13 15.87 1 12 1Z\"\n fill=\"currentColor\"\n />\n <path\n d=\"M12 23C10.34 23 9 21.66 9 20H15C15 21.66 13.66 23 12 23Z\"\n fill=\"currentColor\"\n />\n </svg>\n </div>\n <span class=\"header-title\">Voice</span>\n </div>\n <button\n class=\"close-button\"\n (click)=\"endCall()\"\n type=\"button\"\n aria-label=\"Close\"\n >\n <svg\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M18 6L6 18M6 6L18 18\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n </div>\n\n <!-- Avatar Section with glow -->\n <div class=\"avatar-section\">\n <div class=\"avatar-glow\" [class.glow-talking]=\"isBotTalking\" [class.glow-listening]=\"callState === 'listening'\"></div>\n\n <!-- Particle ring \u2014 visible while bot is talking -->\n <div *ngIf=\"isBotTalking\" class=\"particles-container\">\n <span *ngFor=\"let i of [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]\"\n class=\"particle\"\n [style.--i]=\"i\"\n [style.animationDelay]=\"(i * 0.15) + 's'\">\n </span>\n </div>\n\n <div class=\"avatar-wrapper\" [class.speaking]=\"isBotTalking\" [class.listening]=\"callState === 'listening'\">\n <img class=\"avatar-image\" [src]=\"displayAvatarUrl\" alt=\"Nia\" />\n </div>\n </div>\n\n <!-- Agent Info: Nia + Collaboration Manager AI Agent Specialist -->\n <div class=\"agent-info\">\n <div class=\"agent-name\">\n Nia\n <span class=\"ai-badge\">AI</span>\n </div>\n <p class=\"agent-role\">COP30 AI Agent </p>\n </div>\n\n <!-- Start Call (when idle only) -->\n <div *ngIf=\"callState === 'idle'\" class=\"start-call-section\">\n <p *ngIf=\"statusText === 'Connection failed'\" class=\"error-message\">\n {{ statusText }}\n </p>\n <button\n class=\"start-call-button\"\n type=\"button\"\n [disabled]=\"isConnecting\"\n (click)=\"startCall()\"\n >\n <span *ngIf=\"isConnecting\">Connecting...</span>\n <span *ngIf=\"!isConnecting && statusText === 'Connection failed'\"\n >Retry</span\n >\n <span *ngIf=\"!isConnecting && statusText !== 'Connection failed'\"\n >Start Call</span\n >\n </button>\n </div>\n\n <!-- Call Ended: status + Call Again / Back to Chat -->\n <div *ngIf=\"callState === 'ended'\" class=\"call-ended-section\">\n <p class=\"call-ended-status\">\n <span class=\"status-text\">Call Ended</span>\n <span class=\"status-timer\">{{ duration }}</span>\n </p>\n <div class=\"call-ended-controls\">\n <button\n class=\"action-btn\"\n type=\"button\"\n (click)=\"callAgain()\"\n title=\"Call Again\"\n >\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path d=\"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\" />\n <path d=\"M3 3v5h5\" />\n </svg>\n Call Again\n </button>\n <button\n class=\"action-btn\"\n type=\"button\"\n (click)=\"backToChat()\"\n title=\"Back to Chat\"\n >\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\" />\n </svg>\n Back to Chat\n </button>\n </div>\n </div>\n\n <!-- Status (when connecting or in-call: Talking... / Listening / Connected + timer) -->\n <div\n class=\"status-indicator status-inline\"\n *ngIf=\"callState !== 'idle' && callState !== 'ended'\"\n >\n <div *ngIf=\"callState === 'connecting'\" class=\"status-connecting\">\n <svg\n class=\"spinner\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <circle\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-dasharray=\"31.416\"\n stroke-dashoffset=\"31.416\"\n >\n <animate\n attributeName=\"stroke-dasharray\"\n dur=\"2s\"\n values=\"0 31.416;15.708 15.708;0 31.416;0 31.416\"\n repeatCount=\"indefinite\"\n />\n <animate\n attributeName=\"stroke-dashoffset\"\n dur=\"2s\"\n values=\"0;-15.708;-31.416;-31.416\"\n repeatCount=\"indefinite\"\n />\n </circle>\n </svg>\n <span class=\"status-text\">{{ statusText }}</span>\n </div>\n <div\n *ngIf=\"callState !== 'connecting'\"\n class=\"status-connected status-inline-row\"\n >\n <span class=\"status-text\" [class.status-talking]=\"isBotTalking\" [class.status-listening]=\"callState === 'listening'\" [class.status-processing]=\"isProcessing\">\n {{ statusLabel }}\n </span>\n\n <!-- Animated bars \u2014 visible while bot is talking -->\n <div *ngIf=\"isBotTalking\" class=\"voice-visualizer\">\n <div class=\"vbar\"></div>\n <div class=\"vbar\"></div>\n <div class=\"vbar\"></div>\n <div class=\"vbar\"></div>\n </div>\n\n <!-- Bouncing dots \u2014 visible during processing pause -->\n <div *ngIf=\"isProcessing\" class=\"processing-dots\">\n <span></span><span></span><span></span>\n </div>\n\n <span class=\"status-timer\">{{ duration }}</span>\n </div>\n </div>\n\n <!-- Waveform: always visible during an active call, active (coloured) when user speaks -->\n <div\n *ngIf=\"callState === 'connected' || callState === 'listening' || callState === 'talking'\"\n class=\"waveform-container\"\n >\n <div class=\"waveform-bars\">\n <div\n *ngFor=\"let level of audioLevels; let i = index\"\n class=\"waveform-bar\"\n [class.active]=\"isUserActive\"\n [style.height.px]=\"getWaveformHeight(level, i)\"\n ></div>\n </div>\n </div>\n\n <!-- Call Controls (when connected) -->\n <div\n class=\"controls\"\n *ngIf=\"\n callState === 'connected' ||\n callState === 'listening' ||\n callState === 'talking'\n \"\n >\n <div\n style=\"\n display: flex;\n align-items: center;\n gap: 2px;\n flex-direction: column;\n \"\n >\n <button\n class=\"control-btn mic-btn\"\n [class.muted]=\"isMicMuted\"\n (click)=\"toggleMic()\"\n type=\"button\"\n [title]=\"isMicMuted ? 'Unmute' : 'Mute'\"\n >\n <!-- Microphone icon (unmuted) -->\n <svg\n *ngIf=\"!isMicMuted\"\n width=\"24\"\n height=\"24\"\n viewBox=\"-5 0 32 32\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"currentColor\"\n >\n <g transform=\"translate(-105, -307)\">\n <path\n d=\"M111,314 C111,311.238 113.239,309 116,309 C118.761,309 121,311.238 121,314 L121,324 C121,326.762 118.761,329 116,329 C113.239,329 111,326.762 111,324 L111,314 L111,314 Z M116,331 C119.866,331 123,327.866 123,324 L123,314 C123,310.134 119.866,307 116,307 C112.134,307 109,310.134 109,314 L109,324 C109,327.866 112.134,331 116,331 L116,331 Z M127,326 L125,326 C124.089,330.007 120.282,333 116,333 C111.718,333 107.911,330.007 107,326 L105,326 C105.883,330.799 110.063,334.51 115,334.955 L115,337 L114,337 C113.448,337 113,337.448 113,338 C113,338.553 113.448,339 114,339 L118,339 C118.552,339 119,338.553 119,338 C119,337.448 118.552,337 118,337 L117,337 L117,334.955 C121.937,334.51 126.117,330.799 127,326 L127,326 Z\"\n />\n </g>\n </svg>\n <!-- Microphone icon (muted) -->\n <svg\n *ngIf=\"isMicMuted\"\n width=\"24\"\n height=\"24\"\n viewBox=\"-5 0 32 32\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"currentColor\"\n >\n <g transform=\"translate(-105, -307)\">\n <path\n d=\"M111,314 C111,311.238 113.239,309 116,309 C118.761,309 121,311.238 121,314 L121,324 C121,326.762 118.761,329 116,329 C113.239,329 111,326.762 111,324 L111,314 L111,314 Z M116,331 C119.866,331 123,327.866 123,324 L123,314 C123,310.134 119.866,307 116,307 C112.134,307 109,310.134 109,314 L109,324 C109,327.866 112.134,331 116,331 L116,331 Z M127,326 L125,326 C124.089,330.007 120.282,333 116,333 C111.718,333 107.911,330.007 107,326 L105,326 C105.883,330.799 110.063,334.51 115,334.955 L115,337 L114,337 C113.448,337 113,337.448 113,338 C113,338.553 113.448,339 114,339 L118,339 C118.552,339 119,338.553 119,338 C119,337.448 118.552,337 118,337 L117,337 L117,334.955 C121.937,334.51 126.117,330.799 127,326 L127,326 Z\"\n />\n </g>\n <path\n d=\"M2 2 L30 30\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n <span class=\"control-label\">Mute</span>\n </div>\n\n <div\n style=\"\n display: flex;\n align-items: center;\n gap: 2px;\n flex-direction: column;\n \"\n >\n <button\n class=\"control-btn end-call-btn\"\n (click)=\"hangUp()\"\n type=\"button\"\n title=\"End Call\"\n >\n <svg\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M18 6L6 18M6 6L18 18\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n <span class=\"control-label\">End Call</span>\n </div>\n </div>\n </div>\n</div>\n",
|
|
1872
|
-
styles: [":host{display:block}.voice-agent-modal-overlay{align-items:flex-end;backdrop-filter:blur(4px);background:rgba(0,0,0,.5);bottom:0;display:flex;font-family:Segoe UI,Tahoma,Geneva,Verdana,sans-serif;justify-content:flex-end;left:0;padding:24px;position:fixed;right:0;top:0;z-index:99999}.voice-container.voice-agent-modal{align-items:center;animation:modalEnter .3s ease-out;background:#fff;border-radius:30px;box-shadow:0 10px 40px rgba(0,0,0,.1);display:flex;flex-direction:column;max-width:440px;min-height:600px;padding:30px;position:relative;text-align:center;width:100%}@keyframes modalEnter{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.header{justify-content:space-between;margin-bottom:5px;width:100%}.header,.header-left{align-items:center;display:flex}.header-left{gap:8px}.header-icon{align-items:center;background:#0f172a;border-radius:50%;color:#fff;display:flex;height:28px;justify-content:center;width:28px}.header-title{color:#0f172a;font-size:18px;font-weight:500}.close-button{align-items:center;background:none;border:none;color:#0f172a;cursor:pointer;display:flex;justify-content:center;padding:8px;transition:color .2s}.close-button:hover{color:#475569}.avatar-section{margin-bottom:24px;position:relative}.avatar-wrapper{align-items:center;background:#0ea5a4;background:linear-gradient(135deg,#ccfbf1,#0ea5a4);border-radius:50%;display:flex;height:180px;justify-content:center;padding:6px;position:relative;width:180px}.avatar-image{-o-object-fit:cover;border:4px solid #fff;border-radius:50%;height:100%;object-fit:cover;width:100%}.avatar-glow{background:radial-gradient(circle,rgba(14,165,164,.2) 0,transparent 70%);height:240px;left:50%;pointer-events:none;position:absolute;top:50%;transform:translate(-50%,-50%);
|
|
2340
|
+
template: "<div class=\"voice-agent-modal-overlay\" (click)=\"endCall()\">\n <div\n class=\"voice-container voice-agent-modal\"\n (click)=\"$event.stopPropagation()\"\n >\n <!-- Header -->\n <div class=\"header\">\n <div class=\"header-left\">\n <div class=\"header-icon\">\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M12 1C8.13 1 5 4.13 5 8V14C5 17.87 8.13 21 12 21C15.87 21 19 17.87 19 14V8C19 4.13 15.87 1 12 1Z\"\n fill=\"currentColor\"\n />\n <path\n d=\"M12 23C10.34 23 9 21.66 9 20H15C15 21.66 13.66 23 12 23Z\"\n fill=\"currentColor\"\n />\n </svg>\n </div>\n <span class=\"header-title\">Voice</span>\n </div>\n <button\n class=\"close-button\"\n (click)=\"endCall()\"\n type=\"button\"\n aria-label=\"Close\"\n >\n <svg\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M18 6L6 18M6 6L18 18\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n </div>\n\n <!-- Avatar Section with glow -->\n <div class=\"avatar-section\">\n <div class=\"avatar-glow\"></div>\n <div class=\"avatar-wrapper\" [class.speaking]=\"isSpeaking\">\n <img class=\"avatar-image\" [src]=\"displayAvatarUrl\" alt=\"Nia\" />\n </div>\n </div>\n\n <!-- Agent Info: Nia + Collaboration Manager AI Agent Specialist -->\n <div class=\"agent-info\">\n <div class=\"agent-name\">\n Nia\n <span class=\"ai-badge\">AI</span>\n </div>\n <p class=\"agent-role\">COP30 AI Agent </p>\n </div>\n\n <!-- Start Call (when idle only) -->\n <div *ngIf=\"callState === 'idle'\" class=\"start-call-section\">\n <p *ngIf=\"statusText === 'Connection failed'\" class=\"error-message\">\n {{ statusText }}\n </p>\n <button\n class=\"start-call-button\"\n type=\"button\"\n [disabled]=\"isConnecting\"\n (click)=\"startCall()\"\n >\n <span *ngIf=\"isConnecting\">Connecting...</span>\n <span *ngIf=\"!isConnecting && statusText === 'Connection failed'\"\n >Retry</span\n >\n <span *ngIf=\"!isConnecting && statusText !== 'Connection failed'\"\n >Start Call</span\n >\n </button>\n </div>\n\n <!-- Call Ended: status + Call Again / Back to Chat -->\n <div *ngIf=\"callState === 'ended'\" class=\"call-ended-section\">\n <p class=\"call-ended-status\">\n <span class=\"status-text\">Call Ended</span>\n <span class=\"status-timer\">{{ duration }}</span>\n </p>\n <div class=\"call-ended-controls\">\n <button\n class=\"action-btn\"\n type=\"button\"\n (click)=\"callAgain()\"\n title=\"Call Again\"\n >\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path d=\"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\" />\n <path d=\"M3 3v5h5\" />\n </svg>\n Call Again\n </button>\n <button\n class=\"action-btn\"\n type=\"button\"\n (click)=\"backToChat()\"\n title=\"Back to Chat\"\n >\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\" />\n </svg>\n Back to Chat\n </button>\n </div>\n </div>\n\n <!-- Status (when connecting or in-call: Talking... / Listening / Connected + timer) -->\n <div\n class=\"status-indicator status-inline\"\n *ngIf=\"callState !== 'idle' && callState !== 'ended'\"\n >\n <div *ngIf=\"callState === 'connecting'\" class=\"status-connecting\">\n <svg\n class=\"spinner\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <circle\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-dasharray=\"31.416\"\n stroke-dashoffset=\"31.416\"\n >\n <animate\n attributeName=\"stroke-dasharray\"\n dur=\"2s\"\n values=\"0 31.416;15.708 15.708;0 31.416;0 31.416\"\n repeatCount=\"indefinite\"\n />\n <animate\n attributeName=\"stroke-dashoffset\"\n dur=\"2s\"\n values=\"0;-15.708;-31.416;-31.416\"\n repeatCount=\"indefinite\"\n />\n </circle>\n </svg>\n <span class=\"status-text\">{{ statusText }}</span>\n </div>\n <div\n *ngIf=\"callState !== 'connecting'\"\n class=\"status-connected status-inline-row\"\n >\n <span class=\"status-text\">{{ statusLabel }}</span>\n <span class=\"status-timer\">{{ duration }}</span>\n </div>\n </div>\n\n <!-- Waveform -->\n <div\n *ngIf=\"callState === 'listening'\"\n class=\"waveform-container\"\n >\n <div class=\"waveform-bars\">\n <div\n *ngFor=\"let level of audioLevels; let i = index\"\n class=\"waveform-bar\"\n [class.active]=\"isUserSpeaking\"\n [style.height.px]=\"getWaveformHeight(level, i)\"\n ></div>\n </div>\n </div>\n\n <!-- Call Controls (when connected) -->\n <div\n class=\"controls\"\n *ngIf=\"\n callState === 'connected' ||\n callState === 'listening' ||\n callState === 'talking'\n \"\n >\n <div\n style=\"\n display: flex;\n align-items: center;\n gap: 2px;\n flex-direction: column;\n \"\n >\n <button\n class=\"control-btn mic-btn\"\n [class.muted]=\"isMicMuted\"\n (click)=\"toggleMic()\"\n type=\"button\"\n [title]=\"isMicMuted ? 'Unmute' : 'Mute'\"\n >\n <!-- Microphone icon (unmuted) -->\n <svg\n *ngIf=\"!isMicMuted\"\n width=\"24\"\n height=\"24\"\n viewBox=\"-5 0 32 32\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"currentColor\"\n >\n <g transform=\"translate(-105, -307)\">\n <path\n d=\"M111,314 C111,311.238 113.239,309 116,309 C118.761,309 121,311.238 121,314 L121,324 C121,326.762 118.761,329 116,329 C113.239,329 111,326.762 111,324 L111,314 L111,314 Z M116,331 C119.866,331 123,327.866 123,324 L123,314 C123,310.134 119.866,307 116,307 C112.134,307 109,310.134 109,314 L109,324 C109,327.866 112.134,331 116,331 L116,331 Z M127,326 L125,326 C124.089,330.007 120.282,333 116,333 C111.718,333 107.911,330.007 107,326 L105,326 C105.883,330.799 110.063,334.51 115,334.955 L115,337 L114,337 C113.448,337 113,337.448 113,338 C113,338.553 113.448,339 114,339 L118,339 C118.552,339 119,338.553 119,338 C119,337.448 118.552,337 118,337 L117,337 L117,334.955 C121.937,334.51 126.117,330.799 127,326 L127,326 Z\"\n />\n </g>\n </svg>\n <!-- Microphone icon (muted) -->\n <svg\n *ngIf=\"isMicMuted\"\n width=\"24\"\n height=\"24\"\n viewBox=\"-5 0 32 32\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"currentColor\"\n >\n <g transform=\"translate(-105, -307)\">\n <path\n d=\"M111,314 C111,311.238 113.239,309 116,309 C118.761,309 121,311.238 121,314 L121,324 C121,326.762 118.761,329 116,329 C113.239,329 111,326.762 111,324 L111,314 L111,314 Z M116,331 C119.866,331 123,327.866 123,324 L123,314 C123,310.134 119.866,307 116,307 C112.134,307 109,310.134 109,314 L109,324 C109,327.866 112.134,331 116,331 L116,331 Z M127,326 L125,326 C124.089,330.007 120.282,333 116,333 C111.718,333 107.911,330.007 107,326 L105,326 C105.883,330.799 110.063,334.51 115,334.955 L115,337 L114,337 C113.448,337 113,337.448 113,338 C113,338.553 113.448,339 114,339 L118,339 C118.552,339 119,338.553 119,338 C119,337.448 118.552,337 118,337 L117,337 L117,334.955 C121.937,334.51 126.117,330.799 127,326 L127,326 Z\"\n />\n </g>\n <path\n d=\"M2 2 L30 30\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n <span class=\"control-label\">Mute</span>\n </div>\n\n <div\n style=\"\n display: flex;\n align-items: center;\n gap: 2px;\n flex-direction: column;\n \"\n >\n <button\n class=\"control-btn end-call-btn\"\n (click)=\"hangUp()\"\n type=\"button\"\n title=\"End Call\"\n >\n <svg\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M18 6L6 18M6 6L18 18\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n <span class=\"control-label\">End Call</span>\n </div>\n </div>\n </div>\n</div>\n",
|
|
2341
|
+
styles: [":host{display:block}.voice-agent-modal-overlay{align-items:flex-end;backdrop-filter:blur(4px);background:rgba(0,0,0,.5);bottom:0;display:flex;font-family:Segoe UI,Tahoma,Geneva,Verdana,sans-serif;justify-content:flex-end;left:0;padding:24px;position:fixed;right:0;top:0;z-index:99999}.voice-container.voice-agent-modal{align-items:center;animation:modalEnter .3s ease-out;background:#fff;border-radius:30px;box-shadow:0 10px 40px rgba(0,0,0,.1);display:flex;flex-direction:column;max-width:440px;min-height:600px;padding:30px;position:relative;text-align:center;width:100%}@keyframes modalEnter{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.header{justify-content:space-between;margin-bottom:5px;width:100%}.header,.header-left{align-items:center;display:flex}.header-left{gap:8px}.header-icon{align-items:center;background:#0f172a;border-radius:50%;color:#fff;display:flex;height:28px;justify-content:center;width:28px}.header-title{color:#0f172a;font-size:18px;font-weight:500}.close-button{align-items:center;background:none;border:none;color:#0f172a;cursor:pointer;display:flex;justify-content:center;padding:8px;transition:color .2s}.close-button:hover{color:#475569}.avatar-section{margin-bottom:24px;position:relative}.avatar-wrapper{align-items:center;background:#0ea5a4;background:linear-gradient(135deg,#ccfbf1,#0ea5a4);border-radius:50%;display:flex;height:180px;justify-content:center;padding:6px;position:relative;width:180px}.avatar-image{-o-object-fit:cover;border:4px solid #fff;border-radius:50%;height:100%;object-fit:cover;width:100%}.avatar-glow{background:radial-gradient(circle,rgba(14,165,164,.2) 0,transparent 70%);height:240px;left:50%;pointer-events:none;position:absolute;top:50%;transform:translate(-50%,-50%);width:240px;z-index:-1}.avatar-wrapper.speaking{animation:avatarPulse 2s ease-in-out infinite}@keyframes avatarPulse{0%,to{box-shadow:0 0 0 0 rgba(14,165,164,.4)}50%{box-shadow:0 0 0 15px rgba(14,165,164,0)}}.agent-info{margin-bottom:40px}.agent-name{align-items:center;color:#0f172a;display:flex;font-size:24px;font-weight:700;gap:8px;justify-content:center;margin-bottom:8px}.ai-badge{background:#0ea5a4;border-radius:6px;color:#fff;font-size:10px;font-weight:700;padding:2px 6px}.agent-role{color:#0f172a;font-size:16px;font-weight:500;margin:0}.start-call-section{align-items:center;display:flex;flex-direction:column;gap:16px;margin-bottom:24px}.error-message{color:#dc2626;font-size:14px;margin:0}.start-call-button{background:#0ea5a4;border:none;border-radius:12px;color:#fff;cursor:pointer;font-size:16px;font-weight:600;padding:14px 32px;transition:background .2s}.start-call-button:hover:not(:disabled){background:#0d9488}.start-call-button:disabled{cursor:not-allowed!important;opacity:.7}.status-indicator{justify-content:center;margin-bottom:10px}.status-connecting,.status-indicator{align-items:center;display:flex;gap:12px}.spinner{animation:spin 1s linear infinite;color:#0ea5a4}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.status-text{font-weight:400}.status-text,.status-timer{color:#0f172a;font-size:16px}.status-timer{font-weight:500}.status-connected{align-items:center;display:flex;flex-direction:column;gap:4px}.status-inline .status-inline-row{align-items:center;flex-direction:row;gap:8px}.call-ended-section{align-items:center;display:flex;flex-direction:column;gap:16px;margin-bottom:24px}.call-ended-status{align-items:center;color:#0f172a;display:flex;font-size:16px;gap:8px;justify-content:center;margin:0}.call-ended-status .status-text{font-weight:400}.call-ended-status .status-timer{font-weight:500}.call-ended-controls{align-items:center;display:flex;flex-wrap:wrap;gap:16px;justify-content:center}.action-btn{align-items:center;background:#fff;border:1px solid #e2e8f0;border-radius:24px;color:#0f172a;cursor:pointer;display:flex;font-size:14px;font-weight:500;gap:8px;padding:12px 24px;transition:background .2s ease}.action-btn:hover{background:#f8fafc}.waveform-container{margin-bottom:10px;padding:0 8px}.waveform-bars,.waveform-container{align-items:center;display:flex;height:56px;justify-content:center;width:100%}.waveform-bars{gap:2px}.waveform-bar{background:#cbd5e1;border-radius:99px;flex:0 0 2px;min-height:2px;transition:height .1s ease-out;width:2px}.waveform-bar.active{background:linear-gradient(180deg,#0ea5a4,#0d9488);box-shadow:0 0 4px rgba(14,165,164,.5)}.controls{gap:24px;width:100%}.control-btn,.controls{align-items:center;display:flex;justify-content:center}.control-btn{border:none;border-radius:50%;cursor:pointer;flex-direction:column;gap:4px;height:60px;transition:transform .2s ease;width:60px}.control-btn:hover{transform:scale(1.05)}.control-btn:active{transform:scale(.95)}.control-label{color:#0f172a;font-size:12px;font-weight:500}.mic-btn{background:#e2e8f0}.mic-btn,.mic-btn .control-label{color:#475569}.mic-btn.muted{background:#e2e8f0;color:#475569}.end-call-btn{background:#ef4444;color:#fff}.end-call-btn .control-label{color:#fff}.end-call-btn:hover{background:#dc2626}"]
|
|
1873
2342
|
},] }
|
|
1874
2343
|
];
|
|
1875
2344
|
VoiceAgentModalComponent.ctorParameters = function () { return [
|
|
@@ -5857,8 +6326,8 @@
|
|
|
5857
6326
|
};
|
|
5858
6327
|
|
|
5859
6328
|
/**
|
|
5860
|
-
* Voice agent module. Uses
|
|
5861
|
-
*
|
|
6329
|
+
* Voice agent module. Uses native WebSocket + Daily.js only.
|
|
6330
|
+
* Does NOT use Socket.IO or ngx-socket-io.
|
|
5862
6331
|
*/
|
|
5863
6332
|
var VoiceAgentModule = /** @class */ (function () {
|
|
5864
6333
|
function VoiceAgentModule() {
|
|
@@ -5876,6 +6345,8 @@
|
|
|
5876
6345
|
providers: [
|
|
5877
6346
|
VoiceAgentService,
|
|
5878
6347
|
AudioAnalyzerService,
|
|
6348
|
+
WebSocketVoiceClientService,
|
|
6349
|
+
DailyVoiceClientService
|
|
5879
6350
|
],
|
|
5880
6351
|
exports: [
|
|
5881
6352
|
VoiceAgentModalComponent
|
|
@@ -6176,9 +6647,11 @@
|
|
|
6176
6647
|
exports["ɵc"] = ConversationService;
|
|
6177
6648
|
exports["ɵd"] = NotificationSocket;
|
|
6178
6649
|
exports["ɵe"] = TranslationService;
|
|
6179
|
-
exports["ɵf"] =
|
|
6180
|
-
exports["ɵg"] =
|
|
6181
|
-
exports["ɵh"] =
|
|
6650
|
+
exports["ɵf"] = WebSocketVoiceClientService;
|
|
6651
|
+
exports["ɵg"] = DailyVoiceClientService;
|
|
6652
|
+
exports["ɵh"] = VideoPlayerComponent;
|
|
6653
|
+
exports["ɵi"] = SafeHtmlPipe;
|
|
6654
|
+
exports["ɵj"] = BotHtmlEditorComponent;
|
|
6182
6655
|
|
|
6183
6656
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
6184
6657
|
|