@hivegpt/hiveai-angular 0.0.580 → 0.0.582
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 +302 -509
- package/bundles/hivegpt-hiveai-angular.umd.js.map +1 -1
- package/bundles/hivegpt-hiveai-angular.umd.min.js +1 -1
- package/bundles/hivegpt-hiveai-angular.umd.min.js.map +1 -1
- package/esm2015/hivegpt-hiveai-angular.js +4 -5
- package/esm2015/lib/components/voice-agent/components/voice-agent-modal/voice-agent-modal.component.js +17 -6
- package/esm2015/lib/components/voice-agent/services/audio-analyzer.service.js +4 -4
- package/esm2015/lib/components/voice-agent/services/voice-agent.service.js +118 -85
- package/esm2015/lib/components/voice-agent/services/websocket-voice-client.service.js +114 -46
- package/esm2015/lib/components/voice-agent/voice-agent.module.js +3 -5
- package/fesm2015/hivegpt-hiveai-angular.js +240 -428
- package/fesm2015/hivegpt-hiveai-angular.js.map +1 -1
- package/hivegpt-hiveai-angular.d.ts +3 -4
- package/hivegpt-hiveai-angular.d.ts.map +1 -1
- package/hivegpt-hiveai-angular.metadata.json +1 -1
- package/lib/components/voice-agent/components/voice-agent-modal/voice-agent-modal.component.d.ts +6 -2
- 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/voice-agent.service.d.ts +11 -13
- package/lib/components/voice-agent/services/voice-agent.service.d.ts.map +1 -1
- package/lib/components/voice-agent/services/websocket-voice-client.service.d.ts +23 -20
- package/lib/components/voice-agent/services/websocket-voice-client.service.d.ts.map +1 -1
- package/lib/components/voice-agent/voice-agent.module.d.ts +1 -1
- package/lib/components/voice-agent/voice-agent.module.d.ts.map +1 -1
- package/package.json +1 -1
- package/esm2015/lib/components/voice-agent/services/daily-voice-client.service.js +0 -305
- package/lib/components/voice-agent/services/daily-voice-client.service.d.ts +0 -62
- package/lib/components/voice-agent/services/daily-voice-client.service.d.ts.map +0 -1
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
(function (global, factory) {
|
|
2
|
-
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/cdk/overlay'), require('@angular/cdk/portal'), require('@angular/common/http'), require('@angular/core'), require('@angular/platform-browser'), require('rxjs'), require('rxjs/operators'), require('@angular/common'), require('ngx-socket-io'), require('@angular/forms'), require('microsoft-cognitiveservices-speech-sdk'), require('marked'), require('@
|
|
3
|
-
typeof define === 'function' && define.amd ? define('@hivegpt/hiveai-angular', ['exports', '@angular/cdk/overlay', '@angular/cdk/portal', '@angular/common/http', '@angular/core', '@angular/platform-browser', 'rxjs', 'rxjs/operators', '@angular/common', 'ngx-socket-io', '@angular/forms', 'microsoft-cognitiveservices-speech-sdk', 'marked', '@
|
|
4
|
-
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.hivegpt = global.hivegpt || {}, global.hivegpt["hiveai-angular"] = {}), global.ng.cdk.overlay, global.ng.cdk.portal, global.ng.common.http, global.ng.core, global.ng.platformBrowser, global.rxjs, global.rxjs.operators, global.ng.common, global.ngxSocketIo, global.ng.forms, global.SpeechSDK, global.marked, global.
|
|
5
|
-
})(this, (function (exports, overlay, portal, i1, i0, platformBrowser, rxjs, operators, common, ngxSocketIo, forms, SpeechSDK, marked,
|
|
6
|
-
|
|
7
|
-
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/cdk/overlay'), require('@angular/cdk/portal'), require('@angular/common/http'), require('@angular/core'), require('@angular/platform-browser'), require('rxjs'), require('rxjs/operators'), require('@angular/common'), require('ngx-socket-io'), require('@angular/forms'), require('microsoft-cognitiveservices-speech-sdk'), require('marked'), require('@angular/material/icon'), require('@angular/material/sidenav'), require('ngx-quill')) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define('@hivegpt/hiveai-angular', ['exports', '@angular/cdk/overlay', '@angular/cdk/portal', '@angular/common/http', '@angular/core', '@angular/platform-browser', 'rxjs', 'rxjs/operators', '@angular/common', 'ngx-socket-io', '@angular/forms', 'microsoft-cognitiveservices-speech-sdk', 'marked', '@angular/material/icon', '@angular/material/sidenav', 'ngx-quill'], factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.hivegpt = global.hivegpt || {}, global.hivegpt["hiveai-angular"] = {}), global.ng.cdk.overlay, global.ng.cdk.portal, global.ng.common.http, global.ng.core, global.ng.platformBrowser, global.rxjs, global.rxjs.operators, global.ng.common, global.ngxSocketIo, global.ng.forms, global.SpeechSDK, global.marked, global.ng.material.icon, global.ng.material.sidenav, global.ngxQuill));
|
|
5
|
+
})(this, (function (exports, overlay, portal, i1, i0, platformBrowser, rxjs, operators, common, ngxSocketIo, forms, SpeechSDK, marked, icon, sidenav, ngxQuill) { 'use strict';
|
|
8
6
|
|
|
9
7
|
function _interopNamespace(e) {
|
|
10
8
|
if (e && e.__esModule) return e;
|
|
@@ -28,7 +26,6 @@
|
|
|
28
26
|
var i0__namespace = /*#__PURE__*/_interopNamespace(i0);
|
|
29
27
|
var SpeechSDK__namespace = /*#__PURE__*/_interopNamespace(SpeechSDK);
|
|
30
28
|
var marked__namespace = /*#__PURE__*/_interopNamespace(marked);
|
|
31
|
-
var Daily__default = /*#__PURE__*/_interopDefaultLegacy(Daily);
|
|
32
29
|
|
|
33
30
|
/******************************************************************************
|
|
34
31
|
Copyright (c) Microsoft Corporation.
|
|
@@ -1218,8 +1215,8 @@
|
|
|
1218
1215
|
]; };
|
|
1219
1216
|
|
|
1220
1217
|
/**
|
|
1221
|
-
* Audio analyzer for waveform visualization
|
|
1222
|
-
*
|
|
1218
|
+
* Audio analyzer for waveform visualization and local (mic) speaking detection.
|
|
1219
|
+
* VoiceAgentService may combine this with WebSocket server events for call state.
|
|
1223
1220
|
*/
|
|
1224
1221
|
var AudioAnalyzerService = /** @class */ (function () {
|
|
1225
1222
|
function AudioAnalyzerService() {
|
|
@@ -1235,7 +1232,7 @@
|
|
|
1235
1232
|
this.noiseFloorSamples = [];
|
|
1236
1233
|
this.NOISE_FLOOR_SAMPLE_COUNT = 30;
|
|
1237
1234
|
this.SPEAKING_THRESHOLD_MULTIPLIER = 2.5;
|
|
1238
|
-
this.WAVEFORM_BAR_COUNT =
|
|
1235
|
+
this.WAVEFORM_BAR_COUNT = 60;
|
|
1239
1236
|
// Amplify raw amplitude so normal speech (±10–20 units) maps to visible levels
|
|
1240
1237
|
this.SENSITIVITY_MULTIPLIER = 5;
|
|
1241
1238
|
this.audioLevels$ = this.audioLevelsSubject.asObservable();
|
|
@@ -1342,29 +1339,35 @@
|
|
|
1342
1339
|
];
|
|
1343
1340
|
|
|
1344
1341
|
/**
|
|
1345
|
-
* WebSocket
|
|
1342
|
+
* Native WebSocket client for voice session (signaling, transcripts, speaking hints).
|
|
1346
1343
|
* CRITICAL: Uses native WebSocket only. NO Socket.IO, NO ngx-socket-io.
|
|
1347
1344
|
*
|
|
1348
|
-
*
|
|
1349
|
-
*
|
|
1350
|
-
* - Parse JSON messages (room_created, user_transcript, bot_transcript)
|
|
1351
|
-
* - Emit roomCreated$, userTranscript$, botTranscript$
|
|
1352
|
-
* - NO audio logic, NO mic logic. Audio is handled by Daily.js (WebRTC).
|
|
1345
|
+
* Connects to `ws_url` from `POST {baseUrl}/ai/ask-voice-socket`.
|
|
1346
|
+
* Parses JSON messages for transcripts and optional assistant/user speaking flags.
|
|
1353
1347
|
*/
|
|
1354
1348
|
var WebSocketVoiceClientService = /** @class */ (function () {
|
|
1355
|
-
function WebSocketVoiceClientService() {
|
|
1349
|
+
function WebSocketVoiceClientService(ngZone) {
|
|
1350
|
+
this.ngZone = ngZone;
|
|
1356
1351
|
this.ws = null;
|
|
1357
|
-
|
|
1352
|
+
/** True when {@link disconnect} initiated the close (not counted as remote close). */
|
|
1353
|
+
this.closeInitiatedByClient = false;
|
|
1354
|
+
this.openedSubject = new rxjs.Subject();
|
|
1355
|
+
this.remoteCloseSubject = new rxjs.Subject();
|
|
1358
1356
|
this.userTranscriptSubject = new rxjs.Subject();
|
|
1359
1357
|
this.botTranscriptSubject = new rxjs.Subject();
|
|
1360
|
-
|
|
1361
|
-
this.
|
|
1362
|
-
/**
|
|
1358
|
+
this.assistantSpeakingSubject = new rxjs.Subject();
|
|
1359
|
+
this.serverUserSpeakingSubject = new rxjs.Subject();
|
|
1360
|
+
/** Fires once each time the WebSocket reaches OPEN. */
|
|
1361
|
+
this.opened$ = this.openedSubject.asObservable();
|
|
1362
|
+
/** Fires when the socket closes without a client-initiated {@link disconnect}. */
|
|
1363
|
+
this.remoteClose$ = this.remoteCloseSubject.asObservable();
|
|
1363
1364
|
this.userTranscript$ = this.userTranscriptSubject.asObservable();
|
|
1364
|
-
/** Emits bot transcript updates. */
|
|
1365
1365
|
this.botTranscript$ = this.botTranscriptSubject.asObservable();
|
|
1366
|
+
/** Assistant/bot speaking, when the server sends explicit events (see {@link handleJsonMessage}). */
|
|
1367
|
+
this.assistantSpeaking$ = this.assistantSpeakingSubject.asObservable();
|
|
1368
|
+
/** User speaking from server-side VAD, if provided. */
|
|
1369
|
+
this.serverUserSpeaking$ = this.serverUserSpeakingSubject.asObservable();
|
|
1366
1370
|
}
|
|
1367
|
-
/** Connect to signaling WebSocket. No audio over this connection. */
|
|
1368
1371
|
WebSocketVoiceClientService.prototype.connect = function (wsUrl) {
|
|
1369
1372
|
var _this = this;
|
|
1370
1373
|
var _a;
|
|
@@ -1372,40 +1375,47 @@
|
|
|
1372
1375
|
return;
|
|
1373
1376
|
}
|
|
1374
1377
|
if (this.ws) {
|
|
1378
|
+
this.closeInitiatedByClient = true;
|
|
1375
1379
|
this.ws.close();
|
|
1376
|
-
this.ws = null;
|
|
1377
1380
|
}
|
|
1378
1381
|
try {
|
|
1379
|
-
|
|
1380
|
-
this.ws
|
|
1381
|
-
|
|
1382
|
+
var socket_1 = new WebSocket(wsUrl);
|
|
1383
|
+
this.ws = socket_1;
|
|
1384
|
+
socket_1.onopen = function () {
|
|
1385
|
+
if (_this.ws !== socket_1)
|
|
1386
|
+
return;
|
|
1387
|
+
_this.ngZone.run(function () { return _this.openedSubject.next(); });
|
|
1388
|
+
};
|
|
1389
|
+
socket_1.onmessage = function (event) {
|
|
1390
|
+
if (_this.ws !== socket_1)
|
|
1391
|
+
return;
|
|
1392
|
+
if (typeof event.data !== 'string') {
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1382
1395
|
try {
|
|
1383
|
-
var
|
|
1384
|
-
|
|
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
|
-
}
|
|
1396
|
+
var msg_1 = JSON.parse(event.data);
|
|
1397
|
+
_this.ngZone.run(function () { return _this.handleJsonMessage(msg_1); });
|
|
1399
1398
|
}
|
|
1400
|
-
catch (
|
|
1401
|
-
// Ignore non-JSON
|
|
1399
|
+
catch (_a) {
|
|
1400
|
+
// Ignore non-JSON
|
|
1402
1401
|
}
|
|
1403
1402
|
};
|
|
1404
|
-
|
|
1405
|
-
_this.
|
|
1403
|
+
socket_1.onerror = function () {
|
|
1404
|
+
_this.ngZone.run(function () {
|
|
1405
|
+
if (_this.ws === socket_1 && socket_1.readyState !== WebSocket.CLOSED) {
|
|
1406
|
+
socket_1.close();
|
|
1407
|
+
}
|
|
1408
|
+
});
|
|
1406
1409
|
};
|
|
1407
|
-
|
|
1408
|
-
_this.ws
|
|
1410
|
+
socket_1.onclose = function () {
|
|
1411
|
+
if (_this.ws === socket_1) {
|
|
1412
|
+
_this.ws = null;
|
|
1413
|
+
}
|
|
1414
|
+
var client = _this.closeInitiatedByClient;
|
|
1415
|
+
_this.closeInitiatedByClient = false;
|
|
1416
|
+
if (!client) {
|
|
1417
|
+
_this.ngZone.run(function () { return _this.remoteCloseSubject.next(); });
|
|
1418
|
+
}
|
|
1409
1419
|
};
|
|
1410
1420
|
}
|
|
1411
1421
|
catch (err) {
|
|
@@ -1414,374 +1424,98 @@
|
|
|
1414
1424
|
throw err;
|
|
1415
1425
|
}
|
|
1416
1426
|
};
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1427
|
+
WebSocketVoiceClientService.prototype.handleJsonMessage = function (msg) {
|
|
1428
|
+
var type = msg.type;
|
|
1429
|
+
var typeStr = typeof type === 'string' ? type : '';
|
|
1430
|
+
if (typeStr === 'session_ready' || typeStr === 'connected' || typeStr === 'voice_session_started') {
|
|
1431
|
+
return;
|
|
1422
1432
|
}
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
enumerable: false,
|
|
1431
|
-
configurable: true
|
|
1432
|
-
});
|
|
1433
|
-
return WebSocketVoiceClientService;
|
|
1434
|
-
}());
|
|
1435
|
-
WebSocketVoiceClientService.ɵprov = i0__namespace.ɵɵdefineInjectable({ factory: function WebSocketVoiceClientService_Factory() { return new WebSocketVoiceClientService(); }, token: WebSocketVoiceClientService, providedIn: "root" });
|
|
1436
|
-
WebSocketVoiceClientService.decorators = [
|
|
1437
|
-
{ type: i0.Injectable, args: [{
|
|
1438
|
-
providedIn: 'root',
|
|
1439
|
-
},] }
|
|
1440
|
-
];
|
|
1441
|
-
|
|
1442
|
-
/**
|
|
1443
|
-
* Daily.js WebRTC client for voice agent audio.
|
|
1444
|
-
* Responsibilities:
|
|
1445
|
-
* - Create and manage Daily CallObject
|
|
1446
|
-
* - Join Daily room using room_url
|
|
1447
|
-
* - Handle mic capture + speaker playback
|
|
1448
|
-
* - Bot speaking detection via AnalyserNode on remote track (instant)
|
|
1449
|
-
* - User speaking detection via active-speaker-change
|
|
1450
|
-
* - Expose speaking$ (bot speaking), userSpeaking$ (user speaking), micMuted$
|
|
1451
|
-
* - Expose localStream$ for waveform visualization (AudioAnalyzerService)
|
|
1452
|
-
*/
|
|
1453
|
-
var DailyVoiceClientService = /** @class */ (function () {
|
|
1454
|
-
function DailyVoiceClientService(ngZone) {
|
|
1455
|
-
this.ngZone = ngZone;
|
|
1456
|
-
this.callObject = null;
|
|
1457
|
-
this.localStream = null;
|
|
1458
|
-
this.localSessionId = null;
|
|
1459
|
-
/** Explicit playback of remote (bot) audio; required in some browsers. */
|
|
1460
|
-
this.remoteAudioElement = null;
|
|
1461
|
-
/** AnalyserNode-based remote audio monitor for instant bot speaking detection. */
|
|
1462
|
-
this.remoteAudioContext = null;
|
|
1463
|
-
this.remoteSpeakingRAF = null;
|
|
1464
|
-
this.speakingSubject = new rxjs.BehaviorSubject(false);
|
|
1465
|
-
this.userSpeakingSubject = new rxjs.BehaviorSubject(false);
|
|
1466
|
-
this.micMutedSubject = new rxjs.BehaviorSubject(false);
|
|
1467
|
-
this.localStreamSubject = new rxjs.BehaviorSubject(null);
|
|
1468
|
-
/** True when bot (remote participant) is the active speaker. */
|
|
1469
|
-
this.speaking$ = this.speakingSubject.asObservable();
|
|
1470
|
-
/** True when user (local participant) is the active speaker. */
|
|
1471
|
-
this.userSpeaking$ = this.userSpeakingSubject.asObservable();
|
|
1472
|
-
/** True when mic is muted. */
|
|
1473
|
-
this.micMuted$ = this.micMutedSubject.asObservable();
|
|
1474
|
-
/** Emits local mic stream for waveform visualization. */
|
|
1475
|
-
this.localStream$ = this.localStreamSubject.asObservable();
|
|
1476
|
-
}
|
|
1477
|
-
/**
|
|
1478
|
-
* Connect to Daily room. Acquires mic first for waveform, then joins with audio.
|
|
1479
|
-
* @param roomUrl Daily room URL (from room_created)
|
|
1480
|
-
* @param token Optional meeting token
|
|
1481
|
-
*/
|
|
1482
|
-
DailyVoiceClientService.prototype.connect = function (roomUrl, token) {
|
|
1483
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
1484
|
-
var stream, audioTrack, callObject, joinOptions, participants, err_1;
|
|
1485
|
-
return __generator(this, function (_e) {
|
|
1486
|
-
switch (_e.label) {
|
|
1487
|
-
case 0:
|
|
1488
|
-
if (!this.callObject) return [3 /*break*/, 2];
|
|
1489
|
-
return [4 /*yield*/, this.disconnect()];
|
|
1490
|
-
case 1:
|
|
1491
|
-
_e.sent();
|
|
1492
|
-
_e.label = 2;
|
|
1493
|
-
case 2:
|
|
1494
|
-
_e.trys.push([2, 5, , 6]);
|
|
1495
|
-
return [4 /*yield*/, navigator.mediaDevices.getUserMedia({ audio: true })];
|
|
1496
|
-
case 3:
|
|
1497
|
-
stream = _e.sent();
|
|
1498
|
-
audioTrack = stream.getAudioTracks()[0];
|
|
1499
|
-
if (!audioTrack) {
|
|
1500
|
-
stream.getTracks().forEach(function (t) { return t.stop(); });
|
|
1501
|
-
throw new Error('No audio track');
|
|
1502
|
-
}
|
|
1503
|
-
this.localStream = stream;
|
|
1504
|
-
this.localStreamSubject.next(stream);
|
|
1505
|
-
callObject = Daily__default["default"].createCallObject({
|
|
1506
|
-
videoSource: false,
|
|
1507
|
-
audioSource: audioTrack,
|
|
1508
|
-
});
|
|
1509
|
-
this.callObject = callObject;
|
|
1510
|
-
this.setupEventHandlers(callObject);
|
|
1511
|
-
joinOptions = { url: roomUrl };
|
|
1512
|
-
if (typeof token === 'string' && token.trim() !== '') {
|
|
1513
|
-
joinOptions.token = token;
|
|
1514
|
-
}
|
|
1515
|
-
return [4 /*yield*/, callObject.join(joinOptions)];
|
|
1516
|
-
case 4:
|
|
1517
|
-
_e.sent();
|
|
1518
|
-
console.log("[VoiceDebug] Room connected (Daily join complete) \u2014 " + new Date().toISOString());
|
|
1519
|
-
participants = callObject.participants();
|
|
1520
|
-
if (participants === null || participants === void 0 ? void 0 : participants.local) {
|
|
1521
|
-
this.localSessionId = participants.local.session_id;
|
|
1522
|
-
}
|
|
1523
|
-
// Initial mute state: Daily starts with audio on
|
|
1524
|
-
this.micMutedSubject.next(!callObject.localAudio());
|
|
1525
|
-
return [3 /*break*/, 6];
|
|
1526
|
-
case 5:
|
|
1527
|
-
err_1 = _e.sent();
|
|
1528
|
-
this.cleanup();
|
|
1529
|
-
throw err_1;
|
|
1530
|
-
case 6: return [2 /*return*/];
|
|
1531
|
-
}
|
|
1532
|
-
});
|
|
1533
|
-
});
|
|
1534
|
-
};
|
|
1535
|
-
DailyVoiceClientService.prototype.setupEventHandlers = function (call) {
|
|
1536
|
-
var _this = this;
|
|
1537
|
-
// active-speaker-change: used ONLY for user speaking detection.
|
|
1538
|
-
// Bot speaking is detected by our own AnalyserNode (instant, no debounce).
|
|
1539
|
-
call.on('active-speaker-change', function (event) {
|
|
1540
|
-
_this.ngZone.run(function () {
|
|
1541
|
-
var _a;
|
|
1542
|
-
var peerId = (_a = event === null || event === void 0 ? void 0 : event.activeSpeaker) === null || _a === void 0 ? void 0 : _a.peerId;
|
|
1543
|
-
if (!peerId || !_this.localSessionId) {
|
|
1544
|
-
_this.userSpeakingSubject.next(false);
|
|
1545
|
-
return;
|
|
1546
|
-
}
|
|
1547
|
-
var isLocal = peerId === _this.localSessionId;
|
|
1548
|
-
_this.userSpeakingSubject.next(isLocal);
|
|
1549
|
-
});
|
|
1550
|
-
});
|
|
1551
|
-
// track-started / track-stopped: set up remote audio playback + AnalyserNode monitor.
|
|
1552
|
-
call.on('track-started', function (event) {
|
|
1553
|
-
_this.ngZone.run(function () {
|
|
1554
|
-
var _a, _b, _c, _d;
|
|
1555
|
-
var p = event === null || event === void 0 ? void 0 : event.participant;
|
|
1556
|
-
var type = (_a = event === null || event === void 0 ? void 0 : event.type) !== null && _a !== void 0 ? _a : (_b = event === null || event === void 0 ? void 0 : event.track) === null || _b === void 0 ? void 0 : _b.kind;
|
|
1557
|
-
var track = event === null || event === void 0 ? void 0 : event.track;
|
|
1558
|
-
if (p && !p.local && type === 'audio') {
|
|
1559
|
-
console.log("[VoiceDebug] Got audio track from backend (track-started) \u2014 readyState=" + (track === null || track === void 0 ? void 0 : track.readyState) + ", muted=" + (track === null || track === void 0 ? void 0 : track.muted) + " \u2014 " + new Date().toISOString());
|
|
1560
|
-
var audioTrack = track !== null && track !== void 0 ? track : (_d = (_c = p.tracks) === null || _c === void 0 ? void 0 : _c.audio) === null || _d === void 0 ? void 0 : _d.track;
|
|
1561
|
-
if (audioTrack && typeof audioTrack === 'object') {
|
|
1562
|
-
_this.playRemoteTrack(audioTrack);
|
|
1563
|
-
_this.monitorRemoteAudio(audioTrack);
|
|
1564
|
-
}
|
|
1565
|
-
}
|
|
1566
|
-
});
|
|
1567
|
-
});
|
|
1568
|
-
call.on('track-stopped', function (event) {
|
|
1569
|
-
_this.ngZone.run(function () {
|
|
1570
|
-
var _a, _b;
|
|
1571
|
-
var p = event === null || event === void 0 ? void 0 : event.participant;
|
|
1572
|
-
var type = (_a = event === null || event === void 0 ? void 0 : event.type) !== null && _a !== void 0 ? _a : (_b = event === null || event === void 0 ? void 0 : event.track) === null || _b === void 0 ? void 0 : _b.kind;
|
|
1573
|
-
if (p && !p.local && type === 'audio') {
|
|
1574
|
-
_this.stopRemoteAudioMonitor();
|
|
1575
|
-
_this.stopRemoteAudio();
|
|
1576
|
-
}
|
|
1577
|
-
});
|
|
1578
|
-
});
|
|
1579
|
-
call.on('left-meeting', function () {
|
|
1580
|
-
_this.ngZone.run(function () { return _this.cleanup(); });
|
|
1581
|
-
});
|
|
1582
|
-
call.on('error', function (event) {
|
|
1583
|
-
_this.ngZone.run(function () {
|
|
1584
|
-
var _a;
|
|
1585
|
-
console.error('DailyVoiceClient: Daily error', (_a = event === null || event === void 0 ? void 0 : event.errorMsg) !== null && _a !== void 0 ? _a : event);
|
|
1586
|
-
_this.cleanup();
|
|
1587
|
-
});
|
|
1588
|
-
});
|
|
1589
|
-
};
|
|
1590
|
-
/**
|
|
1591
|
-
* Play remote (bot) audio track via a dedicated audio element.
|
|
1592
|
-
* Required in many browsers where Daily's internal playback does not output to speakers.
|
|
1593
|
-
*/
|
|
1594
|
-
DailyVoiceClientService.prototype.playRemoteTrack = function (track) {
|
|
1595
|
-
this.stopRemoteAudio();
|
|
1596
|
-
try {
|
|
1597
|
-
console.log("[VoiceDebug] playRemoteTrack called \u2014 track.readyState=" + track.readyState + ", track.muted=" + track.muted + " \u2014 " + new Date().toISOString());
|
|
1598
|
-
track.onunmute = function () {
|
|
1599
|
-
console.log("[VoiceDebug] Remote audio track UNMUTED (audio data arriving) \u2014 " + new Date().toISOString());
|
|
1600
|
-
};
|
|
1601
|
-
var stream = new MediaStream([track]);
|
|
1602
|
-
var audio = new Audio();
|
|
1603
|
-
audio.autoplay = true;
|
|
1604
|
-
audio.srcObject = stream;
|
|
1605
|
-
this.remoteAudioElement = audio;
|
|
1606
|
-
audio.onplaying = function () {
|
|
1607
|
-
console.log("[VoiceDebug] Audio element PLAYING (browser started playback) \u2014 " + new Date().toISOString());
|
|
1608
|
-
};
|
|
1609
|
-
var firstTimeUpdate_1 = true;
|
|
1610
|
-
audio.ontimeupdate = function () {
|
|
1611
|
-
if (firstTimeUpdate_1) {
|
|
1612
|
-
firstTimeUpdate_1 = false;
|
|
1613
|
-
console.log("[VoiceDebug] Audio element first TIMEUPDATE (actual audio output) \u2014 " + new Date().toISOString());
|
|
1614
|
-
}
|
|
1615
|
-
};
|
|
1616
|
-
var p = audio.play();
|
|
1617
|
-
if (p && typeof p.then === 'function') {
|
|
1618
|
-
p.then(function () {
|
|
1619
|
-
console.log("[VoiceDebug] audio.play() resolved \u2014 " + new Date().toISOString());
|
|
1620
|
-
}).catch(function (err) {
|
|
1621
|
-
console.warn('DailyVoiceClient: remote audio play failed (may need user gesture)', err);
|
|
1622
|
-
});
|
|
1433
|
+
if (typeStr === 'assistant_speaking' ||
|
|
1434
|
+
typeStr === 'bot_speaking') {
|
|
1435
|
+
if (msg.active === true || msg.speaking === true) {
|
|
1436
|
+
this.assistantSpeakingSubject.next(true);
|
|
1437
|
+
}
|
|
1438
|
+
else if (msg.active === false || msg.speaking === false) {
|
|
1439
|
+
this.assistantSpeakingSubject.next(false);
|
|
1623
1440
|
}
|
|
1441
|
+
return;
|
|
1624
1442
|
}
|
|
1625
|
-
|
|
1626
|
-
|
|
1443
|
+
if (typeStr === 'user_speaking') {
|
|
1444
|
+
if (msg.active === true || msg.speaking === true) {
|
|
1445
|
+
this.serverUserSpeakingSubject.next(true);
|
|
1446
|
+
}
|
|
1447
|
+
else if (msg.active === false || msg.speaking === false) {
|
|
1448
|
+
this.serverUserSpeakingSubject.next(false);
|
|
1449
|
+
}
|
|
1450
|
+
return;
|
|
1627
1451
|
}
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
* Polls at ~60fps and flips speakingSubject based on actual audio energy.
|
|
1632
|
-
*/
|
|
1633
|
-
DailyVoiceClientService.prototype.monitorRemoteAudio = function (track) {
|
|
1634
|
-
var _this = this;
|
|
1635
|
-
this.stopRemoteAudioMonitor();
|
|
1636
|
-
try {
|
|
1637
|
-
var ctx = new AudioContext();
|
|
1638
|
-
var source = ctx.createMediaStreamSource(new MediaStream([track]));
|
|
1639
|
-
var analyser_1 = ctx.createAnalyser();
|
|
1640
|
-
analyser_1.fftSize = 256;
|
|
1641
|
-
source.connect(analyser_1);
|
|
1642
|
-
this.remoteAudioContext = ctx;
|
|
1643
|
-
var dataArray_1 = new Uint8Array(analyser_1.frequencyBinCount);
|
|
1644
|
-
var THRESHOLD_1 = 5;
|
|
1645
|
-
var SILENCE_MS_1 = 1500;
|
|
1646
|
-
var lastSoundTime_1 = 0;
|
|
1647
|
-
var isSpeaking_1 = false;
|
|
1648
|
-
var poll_1 = function () {
|
|
1649
|
-
if (!_this.remoteAudioContext)
|
|
1650
|
-
return;
|
|
1651
|
-
analyser_1.getByteFrequencyData(dataArray_1);
|
|
1652
|
-
var sum = 0;
|
|
1653
|
-
for (var i = 0; i < dataArray_1.length; i++) {
|
|
1654
|
-
sum += dataArray_1[i];
|
|
1655
|
-
}
|
|
1656
|
-
var avg = sum / dataArray_1.length;
|
|
1657
|
-
var now = Date.now();
|
|
1658
|
-
if (avg > THRESHOLD_1) {
|
|
1659
|
-
lastSoundTime_1 = now;
|
|
1660
|
-
if (!isSpeaking_1) {
|
|
1661
|
-
isSpeaking_1 = true;
|
|
1662
|
-
console.log("[VoiceDebug] Bot audio energy detected (speaking=true) \u2014 avg=" + avg.toFixed(1) + " \u2014 " + new Date().toISOString());
|
|
1663
|
-
_this.ngZone.run(function () {
|
|
1664
|
-
_this.userSpeakingSubject.next(false);
|
|
1665
|
-
_this.speakingSubject.next(true);
|
|
1666
|
-
});
|
|
1667
|
-
}
|
|
1668
|
-
}
|
|
1669
|
-
else if (isSpeaking_1 && now - lastSoundTime_1 > SILENCE_MS_1) {
|
|
1670
|
-
isSpeaking_1 = false;
|
|
1671
|
-
console.log("[VoiceDebug] Bot audio silence detected (speaking=false) \u2014 " + new Date().toISOString());
|
|
1672
|
-
_this.ngZone.run(function () { return _this.speakingSubject.next(false); });
|
|
1673
|
-
}
|
|
1674
|
-
_this.remoteSpeakingRAF = requestAnimationFrame(poll_1);
|
|
1675
|
-
};
|
|
1676
|
-
this.remoteSpeakingRAF = requestAnimationFrame(poll_1);
|
|
1452
|
+
if (typeStr === 'input_audio_buffer.speech_started') {
|
|
1453
|
+
this.serverUserSpeakingSubject.next(true);
|
|
1454
|
+
return;
|
|
1677
1455
|
}
|
|
1678
|
-
|
|
1679
|
-
|
|
1456
|
+
if (typeStr === 'input_audio_buffer.speech_stopped') {
|
|
1457
|
+
this.serverUserSpeakingSubject.next(false);
|
|
1458
|
+
return;
|
|
1680
1459
|
}
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
cancelAnimationFrame(this.remoteSpeakingRAF);
|
|
1685
|
-
this.remoteSpeakingRAF = null;
|
|
1460
|
+
if (typeStr === 'response.audio.delta') {
|
|
1461
|
+
this.assistantSpeakingSubject.next(true);
|
|
1462
|
+
return;
|
|
1686
1463
|
}
|
|
1687
|
-
if (
|
|
1688
|
-
|
|
1689
|
-
this.
|
|
1464
|
+
if (typeStr === 'response.audio.done' ||
|
|
1465
|
+
typeStr === 'response.output_audio.done') {
|
|
1466
|
+
this.assistantSpeakingSubject.next(false);
|
|
1467
|
+
return;
|
|
1690
1468
|
}
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
this.
|
|
1469
|
+
if (typeStr === 'user_transcript' && typeof msg.text === 'string') {
|
|
1470
|
+
this.userTranscriptSubject.next({
|
|
1471
|
+
text: msg.text,
|
|
1472
|
+
final: msg.final === true,
|
|
1473
|
+
});
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
if (typeStr === 'bot_transcript' && typeof msg.text === 'string') {
|
|
1477
|
+
this.botTranscriptSubject.next(msg.text);
|
|
1700
1478
|
}
|
|
1701
1479
|
};
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
if (!this.callObject)
|
|
1480
|
+
WebSocketVoiceClientService.prototype.disconnect = function () {
|
|
1481
|
+
if (!this.ws) {
|
|
1705
1482
|
return;
|
|
1706
|
-
this.callObject.setLocalAudio(!muted);
|
|
1707
|
-
this.micMutedSubject.next(muted);
|
|
1708
|
-
};
|
|
1709
|
-
/** Disconnect and cleanup. */
|
|
1710
|
-
DailyVoiceClientService.prototype.disconnect = function () {
|
|
1711
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
1712
|
-
var e_1;
|
|
1713
|
-
return __generator(this, function (_e) {
|
|
1714
|
-
switch (_e.label) {
|
|
1715
|
-
case 0:
|
|
1716
|
-
if (!this.callObject) {
|
|
1717
|
-
this.cleanup();
|
|
1718
|
-
return [2 /*return*/];
|
|
1719
|
-
}
|
|
1720
|
-
_e.label = 1;
|
|
1721
|
-
case 1:
|
|
1722
|
-
_e.trys.push([1, 3, , 4]);
|
|
1723
|
-
return [4 /*yield*/, this.callObject.leave()];
|
|
1724
|
-
case 2:
|
|
1725
|
-
_e.sent();
|
|
1726
|
-
return [3 /*break*/, 4];
|
|
1727
|
-
case 3:
|
|
1728
|
-
e_1 = _e.sent();
|
|
1729
|
-
return [3 /*break*/, 4];
|
|
1730
|
-
case 4:
|
|
1731
|
-
this.cleanup();
|
|
1732
|
-
return [2 /*return*/];
|
|
1733
|
-
}
|
|
1734
|
-
});
|
|
1735
|
-
});
|
|
1736
|
-
};
|
|
1737
|
-
DailyVoiceClientService.prototype.cleanup = function () {
|
|
1738
|
-
this.stopRemoteAudioMonitor();
|
|
1739
|
-
this.stopRemoteAudio();
|
|
1740
|
-
if (this.callObject) {
|
|
1741
|
-
this.callObject.destroy().catch(function () { });
|
|
1742
|
-
this.callObject = null;
|
|
1743
1483
|
}
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
this.localStream = null;
|
|
1747
|
-
}
|
|
1748
|
-
this.localSessionId = null;
|
|
1749
|
-
this.speakingSubject.next(false);
|
|
1750
|
-
this.userSpeakingSubject.next(false);
|
|
1751
|
-
this.localStreamSubject.next(null);
|
|
1752
|
-
// Keep last micMuted state; will reset on next connect
|
|
1484
|
+
this.closeInitiatedByClient = true;
|
|
1485
|
+
this.ws.close();
|
|
1753
1486
|
};
|
|
1754
|
-
|
|
1487
|
+
Object.defineProperty(WebSocketVoiceClientService.prototype, "isConnected", {
|
|
1488
|
+
get: function () {
|
|
1489
|
+
var _a;
|
|
1490
|
+
return ((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN;
|
|
1491
|
+
},
|
|
1492
|
+
enumerable: false,
|
|
1493
|
+
configurable: true
|
|
1494
|
+
});
|
|
1495
|
+
return WebSocketVoiceClientService;
|
|
1755
1496
|
}());
|
|
1756
|
-
|
|
1757
|
-
|
|
1497
|
+
WebSocketVoiceClientService.ɵprov = i0__namespace.ɵɵdefineInjectable({ factory: function WebSocketVoiceClientService_Factory() { return new WebSocketVoiceClientService(i0__namespace.ɵɵinject(i0__namespace.NgZone)); }, token: WebSocketVoiceClientService, providedIn: "root" });
|
|
1498
|
+
WebSocketVoiceClientService.decorators = [
|
|
1758
1499
|
{ type: i0.Injectable, args: [{
|
|
1759
1500
|
providedIn: 'root',
|
|
1760
1501
|
},] }
|
|
1761
1502
|
];
|
|
1762
|
-
|
|
1503
|
+
WebSocketVoiceClientService.ctorParameters = function () { return [
|
|
1763
1504
|
{ type: i0.NgZone }
|
|
1764
1505
|
]; };
|
|
1765
1506
|
|
|
1766
1507
|
/**
|
|
1767
|
-
* Voice agent orchestrator
|
|
1768
|
-
*
|
|
1769
|
-
*
|
|
1770
|
-
* - Native WebSocket (WebSocketVoiceClientService) for signaling (room_created, transcripts)
|
|
1771
|
-
* - Daily.js (DailyVoiceClientService) for WebRTC audio. Audio does NOT flow over WebSocket.
|
|
1772
|
-
*
|
|
1773
|
-
* - Maintains callState, statusText, duration, isMicMuted, isUserSpeaking, audioLevels
|
|
1774
|
-
* - Uses WebSocket for room_created and transcripts only (no audio)
|
|
1775
|
-
* - Uses Daily.js for all audio, mic, and real-time speaking detection
|
|
1508
|
+
* Voice agent orchestrator: single WebSocket (`ws_url` from POST /ai/ask-voice-socket)
|
|
1509
|
+
* for session events, transcripts, and optional speaking hints; local mic for capture
|
|
1510
|
+
* and waveform only (no Daily/WebRTC room).
|
|
1776
1511
|
*/
|
|
1777
1512
|
var VoiceAgentService = /** @class */ (function () {
|
|
1778
|
-
function VoiceAgentService(audioAnalyzer, wsClient,
|
|
1513
|
+
function VoiceAgentService(audioAnalyzer, wsClient, platformTokenRefresh,
|
|
1779
1514
|
/** `Object` not `object` — ngc metadata collection rejects the `object` type in DI params. */
|
|
1780
1515
|
platformId) {
|
|
1781
1516
|
var _this = this;
|
|
1782
1517
|
this.audioAnalyzer = audioAnalyzer;
|
|
1783
1518
|
this.wsClient = wsClient;
|
|
1784
|
-
this.dailyClient = dailyClient;
|
|
1785
1519
|
this.platformTokenRefresh = platformTokenRefresh;
|
|
1786
1520
|
this.platformId = platformId;
|
|
1787
1521
|
this.callStateSubject = new rxjs.BehaviorSubject('idle');
|
|
@@ -1794,6 +1528,8 @@
|
|
|
1794
1528
|
this.botTranscriptSubject = new rxjs.Subject();
|
|
1795
1529
|
this.callStartTime = 0;
|
|
1796
1530
|
this.durationInterval = null;
|
|
1531
|
+
this.localMicStream = null;
|
|
1532
|
+
this.endCall$ = new rxjs.Subject();
|
|
1797
1533
|
this.subscriptions = new rxjs.Subscription();
|
|
1798
1534
|
this.destroy$ = new rxjs.Subject();
|
|
1799
1535
|
this.callState$ = this.callStateSubject.asObservable();
|
|
@@ -1804,8 +1540,10 @@
|
|
|
1804
1540
|
this.audioLevels$ = this.audioLevelsSubject.asObservable();
|
|
1805
1541
|
this.userTranscript$ = this.userTranscriptSubject.asObservable();
|
|
1806
1542
|
this.botTranscript$ = this.botTranscriptSubject.asObservable();
|
|
1807
|
-
// Waveform visualization only - do NOT use for speaking state
|
|
1808
1543
|
this.subscriptions.add(this.audioAnalyzer.audioLevels$.subscribe(function (levels) { return _this.audioLevelsSubject.next(levels); }));
|
|
1544
|
+
this.subscriptions.add(this.wsClient.remoteClose$
|
|
1545
|
+
.pipe(operators.takeUntil(this.destroy$))
|
|
1546
|
+
.subscribe(function () { return void _this.handleRemoteClose(); }));
|
|
1809
1547
|
}
|
|
1810
1548
|
VoiceAgentService.prototype.ngOnDestroy = function () {
|
|
1811
1549
|
this.destroy$.next();
|
|
@@ -1816,53 +1554,54 @@
|
|
|
1816
1554
|
VoiceAgentService.prototype.resetToIdle = function () {
|
|
1817
1555
|
if (this.callStateSubject.value === 'idle')
|
|
1818
1556
|
return;
|
|
1557
|
+
this.endCall$.next();
|
|
1819
1558
|
this.stopDurationTimer();
|
|
1559
|
+
this.callStartTime = 0;
|
|
1820
1560
|
this.audioAnalyzer.stop();
|
|
1561
|
+
this.stopLocalMic();
|
|
1821
1562
|
this.wsClient.disconnect();
|
|
1822
|
-
// Fire-and-forget: Daily disconnect is async; connect() will await if needed
|
|
1823
|
-
void this.dailyClient.disconnect();
|
|
1824
1563
|
this.callStateSubject.next('idle');
|
|
1825
1564
|
this.statusTextSubject.next('');
|
|
1826
1565
|
this.durationSubject.next('0:00');
|
|
1827
1566
|
};
|
|
1828
1567
|
VoiceAgentService.prototype.connect = function (apiUrl, token, botId, conversationId, apiKey, eventToken, eventId, eventUrl, domainAuthority, usersApiUrl) {
|
|
1829
1568
|
return __awaiter(this, void 0, void 0, function () {
|
|
1830
|
-
var accessToken, ensured, e_1, baseUrl, postUrl, headers, res, json, wsUrl, error_1;
|
|
1569
|
+
var accessToken, ensured, e_1, baseUrl, postUrl, headers, res, json, wsUrl, untilCallEnds$, error_1;
|
|
1831
1570
|
var _this = this;
|
|
1832
|
-
return __generator(this, function (
|
|
1833
|
-
switch (
|
|
1571
|
+
return __generator(this, function (_b) {
|
|
1572
|
+
switch (_b.label) {
|
|
1834
1573
|
case 0:
|
|
1835
1574
|
if (this.callStateSubject.value !== 'idle') {
|
|
1836
1575
|
console.warn('Call already in progress');
|
|
1837
1576
|
return [2 /*return*/];
|
|
1838
1577
|
}
|
|
1839
|
-
|
|
1578
|
+
_b.label = 1;
|
|
1840
1579
|
case 1:
|
|
1841
|
-
|
|
1580
|
+
_b.trys.push([1, 8, , 10]);
|
|
1842
1581
|
this.callStateSubject.next('connecting');
|
|
1843
1582
|
this.statusTextSubject.next('Connecting...');
|
|
1844
1583
|
accessToken = token;
|
|
1845
1584
|
if (!(usersApiUrl && common.isPlatformBrowser(this.platformId))) return [3 /*break*/, 5];
|
|
1846
|
-
|
|
1585
|
+
_b.label = 2;
|
|
1847
1586
|
case 2:
|
|
1848
|
-
|
|
1587
|
+
_b.trys.push([2, 4, , 5]);
|
|
1849
1588
|
return [4 /*yield*/, this.platformTokenRefresh
|
|
1850
1589
|
.ensureValidAccessToken(token, usersApiUrl)
|
|
1851
1590
|
.pipe(operators.take(1))
|
|
1852
1591
|
.toPromise()];
|
|
1853
1592
|
case 3:
|
|
1854
|
-
ensured =
|
|
1593
|
+
ensured = _b.sent();
|
|
1855
1594
|
if (ensured === null || ensured === void 0 ? void 0 : ensured.accessToken) {
|
|
1856
1595
|
accessToken = ensured.accessToken;
|
|
1857
1596
|
}
|
|
1858
1597
|
return [3 /*break*/, 5];
|
|
1859
1598
|
case 4:
|
|
1860
|
-
e_1 =
|
|
1599
|
+
e_1 = _b.sent();
|
|
1861
1600
|
console.warn('[HiveGpt Voice] Token refresh before connect failed', e_1);
|
|
1862
1601
|
return [3 /*break*/, 5];
|
|
1863
1602
|
case 5:
|
|
1864
1603
|
baseUrl = apiUrl.replace(/\/$/, '');
|
|
1865
|
-
postUrl = baseUrl + "/ai/ask-voice";
|
|
1604
|
+
postUrl = baseUrl + "/ai/ask-voice-socket";
|
|
1866
1605
|
headers = {
|
|
1867
1606
|
'Content-Type': 'application/json',
|
|
1868
1607
|
Authorization: "Bearer " + accessToken,
|
|
@@ -1884,60 +1623,37 @@
|
|
|
1884
1623
|
}),
|
|
1885
1624
|
})];
|
|
1886
1625
|
case 6:
|
|
1887
|
-
res =
|
|
1626
|
+
res = _b.sent();
|
|
1888
1627
|
if (!res.ok) {
|
|
1889
1628
|
throw new Error("HTTP " + res.status);
|
|
1890
1629
|
}
|
|
1891
1630
|
return [4 /*yield*/, res.json()];
|
|
1892
1631
|
case 7:
|
|
1893
|
-
json =
|
|
1894
|
-
wsUrl = json === null || json === void 0 ? void 0 : json.
|
|
1895
|
-
|
|
1632
|
+
json = _b.sent();
|
|
1633
|
+
wsUrl = (typeof (json === null || json === void 0 ? void 0 : json.ws_url) === 'string' && json.ws_url) ||
|
|
1634
|
+
(typeof (json === null || json === void 0 ? void 0 : json.rn_ws_url) === 'string' && json.rn_ws_url);
|
|
1635
|
+
if (!wsUrl) {
|
|
1896
1636
|
throw new Error('No ws_url in response');
|
|
1897
1637
|
}
|
|
1898
|
-
|
|
1899
|
-
this.wsClient.roomCreated$
|
|
1900
|
-
.pipe(operators.take(1), operators.takeUntil(this.destroy$))
|
|
1901
|
-
.subscribe(function (roomUrl) { return __awaiter(_this, void 0, void 0, function () {
|
|
1902
|
-
var err_1;
|
|
1903
|
-
return __generator(this, function (_a) {
|
|
1904
|
-
switch (_a.label) {
|
|
1905
|
-
case 0:
|
|
1906
|
-
_a.trys.push([0, 2, , 4]);
|
|
1907
|
-
return [4 /*yield*/, this.onRoomCreated(roomUrl)];
|
|
1908
|
-
case 1:
|
|
1909
|
-
_a.sent();
|
|
1910
|
-
return [3 /*break*/, 4];
|
|
1911
|
-
case 2:
|
|
1912
|
-
err_1 = _a.sent();
|
|
1913
|
-
console.error('Daily join failed:', err_1);
|
|
1914
|
-
this.callStateSubject.next('ended');
|
|
1915
|
-
this.statusTextSubject.next('Connection failed');
|
|
1916
|
-
return [4 /*yield*/, this.disconnect()];
|
|
1917
|
-
case 3:
|
|
1918
|
-
_a.sent();
|
|
1919
|
-
throw err_1;
|
|
1920
|
-
case 4: return [2 /*return*/];
|
|
1921
|
-
}
|
|
1922
|
-
});
|
|
1923
|
-
}); });
|
|
1924
|
-
// Forward transcripts from WebSocket
|
|
1638
|
+
untilCallEnds$ = rxjs.merge(this.destroy$, this.endCall$);
|
|
1925
1639
|
this.subscriptions.add(this.wsClient.userTranscript$
|
|
1926
|
-
.pipe(operators.takeUntil(
|
|
1640
|
+
.pipe(operators.takeUntil(untilCallEnds$))
|
|
1927
1641
|
.subscribe(function (t) { return _this.userTranscriptSubject.next(t); }));
|
|
1928
1642
|
this.subscriptions.add(this.wsClient.botTranscript$
|
|
1929
|
-
.pipe(operators.takeUntil(
|
|
1643
|
+
.pipe(operators.takeUntil(untilCallEnds$))
|
|
1930
1644
|
.subscribe(function (t) { return _this.botTranscriptSubject.next(t); }));
|
|
1931
|
-
|
|
1645
|
+
this.subscriptions.add(this.wsClient.opened$
|
|
1646
|
+
.pipe(operators.takeUntil(untilCallEnds$), operators.take(1))
|
|
1647
|
+
.subscribe(function () { return void _this.onWebsocketOpened(); }));
|
|
1932
1648
|
this.wsClient.connect(wsUrl);
|
|
1933
1649
|
return [3 /*break*/, 10];
|
|
1934
1650
|
case 8:
|
|
1935
|
-
error_1 =
|
|
1651
|
+
error_1 = _b.sent();
|
|
1936
1652
|
console.error('Error connecting voice agent:', error_1);
|
|
1937
1653
|
this.callStateSubject.next('ended');
|
|
1938
1654
|
return [4 /*yield*/, this.disconnect()];
|
|
1939
1655
|
case 9:
|
|
1940
|
-
|
|
1656
|
+
_b.sent();
|
|
1941
1657
|
this.statusTextSubject.next('Connection failed');
|
|
1942
1658
|
throw error_1;
|
|
1943
1659
|
case 10: return [2 /*return*/];
|
|
@@ -1945,79 +1661,148 @@
|
|
|
1945
1661
|
});
|
|
1946
1662
|
});
|
|
1947
1663
|
};
|
|
1948
|
-
VoiceAgentService.prototype.
|
|
1664
|
+
VoiceAgentService.prototype.onWebsocketOpened = function () {
|
|
1949
1665
|
return __awaiter(this, void 0, void 0, function () {
|
|
1950
|
-
var
|
|
1951
|
-
return __generator(this, function (
|
|
1952
|
-
switch (
|
|
1953
|
-
case 0:
|
|
1954
|
-
|
|
1955
|
-
|
|
1666
|
+
var err_1;
|
|
1667
|
+
return __generator(this, function (_b) {
|
|
1668
|
+
switch (_b.label) {
|
|
1669
|
+
case 0:
|
|
1670
|
+
if (this.callStateSubject.value !== 'connecting') {
|
|
1671
|
+
return [2 /*return*/];
|
|
1672
|
+
}
|
|
1673
|
+
_b.label = 1;
|
|
1956
1674
|
case 1:
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
if (current === 'connecting' && bot) {
|
|
1976
|
-
_this.callStartTime = Date.now();
|
|
1977
|
-
_this.startDurationTimer();
|
|
1978
|
-
_this.callStateSubject.next('talking');
|
|
1979
|
-
return;
|
|
1980
|
-
}
|
|
1981
|
-
if (user) {
|
|
1982
|
-
_this.callStateSubject.next('listening');
|
|
1983
|
-
}
|
|
1984
|
-
else if (bot) {
|
|
1985
|
-
_this.callStateSubject.next('talking');
|
|
1986
|
-
}
|
|
1987
|
-
else if (current === 'talking' || current === 'listening') {
|
|
1988
|
-
_this.callStateSubject.next('connected');
|
|
1989
|
-
}
|
|
1990
|
-
}));
|
|
1991
|
-
this.subscriptions.add(this.dailyClient.micMuted$.subscribe(function (muted) { return _this.isMicMutedSubject.next(muted); }));
|
|
1992
|
-
this.statusTextSubject.next('Connecting...');
|
|
1993
|
-
return [2 /*return*/];
|
|
1675
|
+
_b.trys.push([1, 3, , 5]);
|
|
1676
|
+
return [4 /*yield*/, this.startLocalMic()];
|
|
1677
|
+
case 2:
|
|
1678
|
+
_b.sent();
|
|
1679
|
+
this.statusTextSubject.next('Connected');
|
|
1680
|
+
this.callStateSubject.next('connected');
|
|
1681
|
+
this.wireSpeakingState();
|
|
1682
|
+
return [3 /*break*/, 5];
|
|
1683
|
+
case 3:
|
|
1684
|
+
err_1 = _b.sent();
|
|
1685
|
+
console.error('[HiveGpt Voice] Mic or session setup failed', err_1);
|
|
1686
|
+
this.callStateSubject.next('ended');
|
|
1687
|
+
this.statusTextSubject.next('Microphone unavailable');
|
|
1688
|
+
return [4 /*yield*/, this.disconnect()];
|
|
1689
|
+
case 4:
|
|
1690
|
+
_b.sent();
|
|
1691
|
+
return [3 /*break*/, 5];
|
|
1692
|
+
case 5: return [2 /*return*/];
|
|
1994
1693
|
}
|
|
1995
1694
|
});
|
|
1996
1695
|
});
|
|
1997
1696
|
};
|
|
1998
|
-
VoiceAgentService.prototype.
|
|
1697
|
+
VoiceAgentService.prototype.wireSpeakingState = function () {
|
|
1698
|
+
var _this = this;
|
|
1699
|
+
var untilCallEnds$ = rxjs.merge(this.destroy$, this.endCall$);
|
|
1700
|
+
var transcriptDrivenAssistant$ = this.wsClient.botTranscript$.pipe(operators.switchMap(function () { return rxjs.concat(rxjs.of(true), rxjs.timer(800).pipe(operators.map(function () { return false; }))); }), operators.distinctUntilChanged());
|
|
1701
|
+
var assistantTalking$ = rxjs.merge(this.wsClient.assistantSpeaking$, transcriptDrivenAssistant$).pipe(operators.distinctUntilChanged(), operators.startWith(false));
|
|
1702
|
+
var userTalking$ = rxjs.combineLatest([
|
|
1703
|
+
this.audioAnalyzer.isUserSpeaking$,
|
|
1704
|
+
this.wsClient.serverUserSpeaking$.pipe(operators.startWith(false)),
|
|
1705
|
+
]).pipe(operators.map(function (_b) {
|
|
1706
|
+
var _c = __read(_b, 2), local = _c[0], server = _c[1];
|
|
1707
|
+
return local || server;
|
|
1708
|
+
}), operators.distinctUntilChanged(), operators.startWith(false));
|
|
1709
|
+
this.subscriptions.add(rxjs.combineLatest([assistantTalking$, userTalking$])
|
|
1710
|
+
.pipe(operators.takeUntil(untilCallEnds$))
|
|
1711
|
+
.subscribe(function (_b) {
|
|
1712
|
+
var _c = __read(_b, 2), bot = _c[0], user = _c[1];
|
|
1713
|
+
var current = _this.callStateSubject.value;
|
|
1714
|
+
if (user) {
|
|
1715
|
+
_this.isUserSpeakingSubject.next(true);
|
|
1716
|
+
_this.callStateSubject.next('listening');
|
|
1717
|
+
}
|
|
1718
|
+
else {
|
|
1719
|
+
_this.isUserSpeakingSubject.next(false);
|
|
1720
|
+
}
|
|
1721
|
+
if (user) {
|
|
1722
|
+
return;
|
|
1723
|
+
}
|
|
1724
|
+
if (bot) {
|
|
1725
|
+
if (_this.callStartTime === 0) {
|
|
1726
|
+
_this.callStartTime = Date.now();
|
|
1727
|
+
_this.startDurationTimer();
|
|
1728
|
+
}
|
|
1729
|
+
_this.callStateSubject.next('talking');
|
|
1730
|
+
}
|
|
1731
|
+
else if (current === 'talking' || current === 'listening') {
|
|
1732
|
+
_this.callStateSubject.next('connected');
|
|
1733
|
+
}
|
|
1734
|
+
}));
|
|
1735
|
+
};
|
|
1736
|
+
VoiceAgentService.prototype.startLocalMic = function () {
|
|
1999
1737
|
return __awaiter(this, void 0, void 0, function () {
|
|
2000
|
-
|
|
2001
|
-
|
|
1738
|
+
var stream, track;
|
|
1739
|
+
return __generator(this, function (_b) {
|
|
1740
|
+
switch (_b.label) {
|
|
2002
1741
|
case 0:
|
|
2003
|
-
this.
|
|
2004
|
-
|
|
2005
|
-
// Daily first, then WebSocket
|
|
2006
|
-
return [4 /*yield*/, this.dailyClient.disconnect()];
|
|
1742
|
+
this.stopLocalMic();
|
|
1743
|
+
return [4 /*yield*/, navigator.mediaDevices.getUserMedia({ audio: true })];
|
|
2007
1744
|
case 1:
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
1745
|
+
stream = _b.sent();
|
|
1746
|
+
track = stream.getAudioTracks()[0];
|
|
1747
|
+
if (!track) {
|
|
1748
|
+
stream.getTracks().forEach(function (t) { return t.stop(); });
|
|
1749
|
+
throw new Error('No audio track');
|
|
1750
|
+
}
|
|
1751
|
+
this.localMicStream = stream;
|
|
1752
|
+
this.isMicMutedSubject.next(!track.enabled);
|
|
1753
|
+
this.audioAnalyzer.start(stream);
|
|
2013
1754
|
return [2 /*return*/];
|
|
2014
1755
|
}
|
|
2015
1756
|
});
|
|
2016
1757
|
});
|
|
2017
1758
|
};
|
|
1759
|
+
VoiceAgentService.prototype.stopLocalMic = function () {
|
|
1760
|
+
if (this.localMicStream) {
|
|
1761
|
+
this.localMicStream.getTracks().forEach(function (t) { return t.stop(); });
|
|
1762
|
+
this.localMicStream = null;
|
|
1763
|
+
}
|
|
1764
|
+
};
|
|
1765
|
+
VoiceAgentService.prototype.handleRemoteClose = function () {
|
|
1766
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
1767
|
+
var state;
|
|
1768
|
+
return __generator(this, function (_b) {
|
|
1769
|
+
state = this.callStateSubject.value;
|
|
1770
|
+
if (state === 'idle' || state === 'ended')
|
|
1771
|
+
return [2 /*return*/];
|
|
1772
|
+
this.endCall$.next();
|
|
1773
|
+
this.stopDurationTimer();
|
|
1774
|
+
this.callStartTime = 0;
|
|
1775
|
+
this.audioAnalyzer.stop();
|
|
1776
|
+
this.stopLocalMic();
|
|
1777
|
+
this.callStateSubject.next('ended');
|
|
1778
|
+
this.statusTextSubject.next('Connection lost');
|
|
1779
|
+
return [2 /*return*/];
|
|
1780
|
+
});
|
|
1781
|
+
});
|
|
1782
|
+
};
|
|
1783
|
+
VoiceAgentService.prototype.disconnect = function () {
|
|
1784
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
1785
|
+
return __generator(this, function (_b) {
|
|
1786
|
+
this.endCall$.next();
|
|
1787
|
+
this.stopDurationTimer();
|
|
1788
|
+
this.callStartTime = 0;
|
|
1789
|
+
this.audioAnalyzer.stop();
|
|
1790
|
+
this.stopLocalMic();
|
|
1791
|
+
this.wsClient.disconnect();
|
|
1792
|
+
this.callStateSubject.next('ended');
|
|
1793
|
+
this.statusTextSubject.next('Call Ended');
|
|
1794
|
+
return [2 /*return*/];
|
|
1795
|
+
});
|
|
1796
|
+
});
|
|
1797
|
+
};
|
|
2018
1798
|
VoiceAgentService.prototype.toggleMic = function () {
|
|
2019
|
-
var
|
|
2020
|
-
this.
|
|
1799
|
+
var _a;
|
|
1800
|
+
var nextMuted = !this.isMicMutedSubject.value;
|
|
1801
|
+
var track = (_a = this.localMicStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks()[0];
|
|
1802
|
+
if (track) {
|
|
1803
|
+
track.enabled = !nextMuted;
|
|
1804
|
+
}
|
|
1805
|
+
this.isMicMutedSubject.next(nextMuted);
|
|
2021
1806
|
};
|
|
2022
1807
|
VoiceAgentService.prototype.startDurationTimer = function () {
|
|
2023
1808
|
var _this = this;
|
|
@@ -2040,7 +1825,7 @@
|
|
|
2040
1825
|
};
|
|
2041
1826
|
return VoiceAgentService;
|
|
2042
1827
|
}());
|
|
2043
|
-
VoiceAgentService.ɵprov = i0__namespace.ɵɵdefineInjectable({ factory: function VoiceAgentService_Factory() { return new VoiceAgentService(i0__namespace.ɵɵinject(AudioAnalyzerService), i0__namespace.ɵɵinject(WebSocketVoiceClientService), i0__namespace.ɵɵinject(
|
|
1828
|
+
VoiceAgentService.ɵprov = i0__namespace.ɵɵdefineInjectable({ factory: function VoiceAgentService_Factory() { return new VoiceAgentService(i0__namespace.ɵɵinject(AudioAnalyzerService), i0__namespace.ɵɵinject(WebSocketVoiceClientService), i0__namespace.ɵɵinject(PlatformTokenRefreshService), i0__namespace.ɵɵinject(i0__namespace.PLATFORM_ID)); }, token: VoiceAgentService, providedIn: "root" });
|
|
2044
1829
|
VoiceAgentService.decorators = [
|
|
2045
1830
|
{ type: i0.Injectable, args: [{
|
|
2046
1831
|
providedIn: 'root',
|
|
@@ -2049,7 +1834,6 @@
|
|
|
2049
1834
|
VoiceAgentService.ctorParameters = function () { return [
|
|
2050
1835
|
{ type: AudioAnalyzerService },
|
|
2051
1836
|
{ type: WebSocketVoiceClientService },
|
|
2052
|
-
{ type: DailyVoiceClientService },
|
|
2053
1837
|
{ type: PlatformTokenRefreshService },
|
|
2054
1838
|
{ type: Object, decorators: [{ type: i0.Inject, args: [i0.PLATFORM_ID,] }] }
|
|
2055
1839
|
]; };
|
|
@@ -2185,10 +1969,21 @@
|
|
|
2185
1969
|
VoiceAgentModalComponent.prototype.toggleMic = function () {
|
|
2186
1970
|
this.voiceAgentService.toggleMic();
|
|
2187
1971
|
};
|
|
2188
|
-
/**
|
|
2189
|
-
|
|
1972
|
+
/**
|
|
1973
|
+
* Map audio level (0–100) to waveform bar height in px with a gaussian
|
|
1974
|
+
* bell-curve envelope so centre bars are tallest and edge bars appear
|
|
1975
|
+
* as tiny dots — matching the audio-waveform reference design.
|
|
1976
|
+
*/
|
|
1977
|
+
VoiceAgentModalComponent.prototype.getWaveformHeight = function (level, index) {
|
|
2190
1978
|
var n = Math.min(100, Math.max(0, level !== null && level !== void 0 ? level : 0));
|
|
2191
|
-
|
|
1979
|
+
var total = this.audioLevels.length || 60;
|
|
1980
|
+
var center = (total - 1) / 2;
|
|
1981
|
+
var sigma = total / 5;
|
|
1982
|
+
var envelope = Math.exp(-Math.pow(index - center, 2) / (2 * sigma * sigma));
|
|
1983
|
+
// Minimum height scales with envelope so edges show as dots even in silence
|
|
1984
|
+
var minHeight = 2 + envelope * 3;
|
|
1985
|
+
var maxHeight = 4 + envelope * 38;
|
|
1986
|
+
return minHeight + (n / 100) * (maxHeight - minHeight);
|
|
2192
1987
|
};
|
|
2193
1988
|
Object.defineProperty(VoiceAgentModalComponent.prototype, "statusLabel", {
|
|
2194
1989
|
/** Status label for active call. */
|
|
@@ -2231,8 +2026,8 @@
|
|
|
2231
2026
|
VoiceAgentModalComponent.decorators = [
|
|
2232
2027
|
{ type: i0.Component, args: [{
|
|
2233
2028
|
selector: 'hivegpt-voice-agent-modal',
|
|
2234
|
-
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' && isUserSpeaking\"\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 [style.height.px]=\"getWaveformHeight(level)\"\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",
|
|
2235
|
-
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
|
|
2029
|
+
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",
|
|
2030
|
+
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}"]
|
|
2236
2031
|
},] }
|
|
2237
2032
|
];
|
|
2238
2033
|
VoiceAgentModalComponent.ctorParameters = function () { return [
|
|
@@ -6220,7 +6015,7 @@
|
|
|
6220
6015
|
};
|
|
6221
6016
|
|
|
6222
6017
|
/**
|
|
6223
|
-
* Voice agent module. Uses native WebSocket
|
|
6018
|
+
* Voice agent module. Uses native WebSocket for the voice session.
|
|
6224
6019
|
* Does NOT use Socket.IO or ngx-socket-io.
|
|
6225
6020
|
*/
|
|
6226
6021
|
var VoiceAgentModule = /** @class */ (function () {
|
|
@@ -6239,8 +6034,7 @@
|
|
|
6239
6034
|
providers: [
|
|
6240
6035
|
VoiceAgentService,
|
|
6241
6036
|
AudioAnalyzerService,
|
|
6242
|
-
WebSocketVoiceClientService
|
|
6243
|
-
DailyVoiceClientService
|
|
6037
|
+
WebSocketVoiceClientService
|
|
6244
6038
|
],
|
|
6245
6039
|
exports: [
|
|
6246
6040
|
VoiceAgentModalComponent
|
|
@@ -6542,10 +6336,9 @@
|
|
|
6542
6336
|
exports["ɵd"] = NotificationSocket;
|
|
6543
6337
|
exports["ɵe"] = TranslationService;
|
|
6544
6338
|
exports["ɵf"] = WebSocketVoiceClientService;
|
|
6545
|
-
exports["ɵg"] =
|
|
6546
|
-
exports["ɵh"] =
|
|
6547
|
-
exports["ɵi"] =
|
|
6548
|
-
exports["ɵj"] = BotHtmlEditorComponent;
|
|
6339
|
+
exports["ɵg"] = VideoPlayerComponent;
|
|
6340
|
+
exports["ɵh"] = SafeHtmlPipe;
|
|
6341
|
+
exports["ɵi"] = BotHtmlEditorComponent;
|
|
6549
6342
|
|
|
6550
6343
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
6551
6344
|
|