@hivegpt/hiveai-angular 0.0.583 → 0.0.585
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 +239 -547
- 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 -24
- package/esm2015/lib/components/voice-agent/services/voice-agent.service.js +174 -236
- package/esm2015/lib/components/voice-agent/voice-agent.module.js +3 -5
- package/fesm2015/hivegpt-hiveai-angular.js +190 -460
- 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 +7 -4
- package/lib/components/voice-agent/components/voice-agent-modal/voice-agent-modal.component.d.ts.map +1 -1
- package/lib/components/voice-agent/services/voice-agent.service.d.ts +24 -26
- package/lib/components/voice-agent/services/voice-agent.service.d.ts.map +1 -1
- 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
- package/esm2015/lib/components/voice-agent/services/websocket-voice-client.service.js +0 -206
- package/lib/components/voice-agent/services/websocket-voice-client.service.d.ts +0 -59
- package/lib/components/voice-agent/services/websocket-voice-client.service.d.ts.map +0 -1
|
@@ -1,8 +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('@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';
|
|
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('@pipecat-ai/client-js'), require('@pipecat-ai/websocket-transport'), 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', '@pipecat-ai/client-js', '@pipecat-ai/websocket-transport', '@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.clientJs, global.websocketTransport, 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, clientJs, websocketTransport, icon, sidenav, ngxQuill) { 'use strict';
|
|
6
6
|
|
|
7
7
|
function _interopNamespace(e) {
|
|
8
8
|
if (e && e.__esModule) return e;
|
|
@@ -1339,237 +1339,24 @@
|
|
|
1339
1339
|
];
|
|
1340
1340
|
|
|
1341
1341
|
/**
|
|
1342
|
-
*
|
|
1343
|
-
* CRITICAL: Uses native WebSocket only. NO Socket.IO, NO ngx-socket-io.
|
|
1342
|
+
* Voice agent orchestrator using the official PipecatClient SDK.
|
|
1344
1343
|
*
|
|
1345
|
-
*
|
|
1346
|
-
*
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
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();
|
|
1356
|
-
this.userTranscriptSubject = new rxjs.Subject();
|
|
1357
|
-
this.botTranscriptSubject = new rxjs.Subject();
|
|
1358
|
-
this.assistantSpeakingSubject = new rxjs.Subject();
|
|
1359
|
-
this.serverUserSpeakingSubject = new rxjs.Subject();
|
|
1360
|
-
this.audioChunkSubject = new rxjs.Subject();
|
|
1361
|
-
/** Fires once each time the WebSocket reaches OPEN. */
|
|
1362
|
-
this.opened$ = this.openedSubject.asObservable();
|
|
1363
|
-
/** Fires when the socket closes without a client-initiated {@link disconnect}. */
|
|
1364
|
-
this.remoteClose$ = this.remoteCloseSubject.asObservable();
|
|
1365
|
-
this.userTranscript$ = this.userTranscriptSubject.asObservable();
|
|
1366
|
-
this.botTranscript$ = this.botTranscriptSubject.asObservable();
|
|
1367
|
-
/** Assistant/bot speaking, when the server sends explicit events (see {@link handleJsonMessage}). */
|
|
1368
|
-
this.assistantSpeaking$ = this.assistantSpeakingSubject.asObservable();
|
|
1369
|
-
/** User speaking from server-side VAD, if provided. */
|
|
1370
|
-
this.serverUserSpeaking$ = this.serverUserSpeakingSubject.asObservable();
|
|
1371
|
-
/** Binary audio frames from server (when backend streams bot audio over WS). */
|
|
1372
|
-
this.audioChunk$ = this.audioChunkSubject.asObservable();
|
|
1373
|
-
}
|
|
1374
|
-
WebSocketVoiceClientService.prototype.connect = function (wsUrl) {
|
|
1375
|
-
var _this = this;
|
|
1376
|
-
var _a;
|
|
1377
|
-
if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN) {
|
|
1378
|
-
return;
|
|
1379
|
-
}
|
|
1380
|
-
if (this.ws) {
|
|
1381
|
-
this.closeInitiatedByClient = true;
|
|
1382
|
-
this.ws.close();
|
|
1383
|
-
}
|
|
1384
|
-
try {
|
|
1385
|
-
var socket_1 = new WebSocket(wsUrl);
|
|
1386
|
-
this.ws = socket_1;
|
|
1387
|
-
socket_1.onopen = function () {
|
|
1388
|
-
if (_this.ws !== socket_1)
|
|
1389
|
-
return;
|
|
1390
|
-
_this.ngZone.run(function () { return _this.openedSubject.next(); });
|
|
1391
|
-
};
|
|
1392
|
-
socket_1.onmessage = function (event) {
|
|
1393
|
-
if (_this.ws !== socket_1)
|
|
1394
|
-
return;
|
|
1395
|
-
void _this.handleIncomingMessage(event.data);
|
|
1396
|
-
};
|
|
1397
|
-
socket_1.onerror = function () {
|
|
1398
|
-
_this.ngZone.run(function () {
|
|
1399
|
-
if (_this.ws === socket_1 && socket_1.readyState !== WebSocket.CLOSED) {
|
|
1400
|
-
socket_1.close();
|
|
1401
|
-
}
|
|
1402
|
-
});
|
|
1403
|
-
};
|
|
1404
|
-
socket_1.onclose = function () {
|
|
1405
|
-
if (_this.ws === socket_1) {
|
|
1406
|
-
_this.ws = null;
|
|
1407
|
-
}
|
|
1408
|
-
var client = _this.closeInitiatedByClient;
|
|
1409
|
-
_this.closeInitiatedByClient = false;
|
|
1410
|
-
if (!client) {
|
|
1411
|
-
_this.ngZone.run(function () { return _this.remoteCloseSubject.next(); });
|
|
1412
|
-
}
|
|
1413
|
-
};
|
|
1414
|
-
}
|
|
1415
|
-
catch (err) {
|
|
1416
|
-
console.error('WebSocketVoiceClient: connect failed', err);
|
|
1417
|
-
this.ws = null;
|
|
1418
|
-
throw err;
|
|
1419
|
-
}
|
|
1420
|
-
};
|
|
1421
|
-
WebSocketVoiceClientService.prototype.handleIncomingMessage = function (payload) {
|
|
1422
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
1423
|
-
var ab;
|
|
1424
|
-
return __generator(this, function (_b) {
|
|
1425
|
-
switch (_b.label) {
|
|
1426
|
-
case 0:
|
|
1427
|
-
if (typeof payload === 'string') {
|
|
1428
|
-
this.handleJsonString(payload);
|
|
1429
|
-
return [2 /*return*/];
|
|
1430
|
-
}
|
|
1431
|
-
if (payload instanceof ArrayBuffer) {
|
|
1432
|
-
this.handleBinaryMessage(payload);
|
|
1433
|
-
return [2 /*return*/];
|
|
1434
|
-
}
|
|
1435
|
-
if (!(payload instanceof Blob)) return [3 /*break*/, 2];
|
|
1436
|
-
return [4 /*yield*/, payload.arrayBuffer()];
|
|
1437
|
-
case 1:
|
|
1438
|
-
ab = _b.sent();
|
|
1439
|
-
this.handleBinaryMessage(ab);
|
|
1440
|
-
_b.label = 2;
|
|
1441
|
-
case 2: return [2 /*return*/];
|
|
1442
|
-
}
|
|
1443
|
-
});
|
|
1444
|
-
});
|
|
1445
|
-
};
|
|
1446
|
-
WebSocketVoiceClientService.prototype.handleJsonString = function (jsonText) {
|
|
1447
|
-
var _this = this;
|
|
1448
|
-
try {
|
|
1449
|
-
var msg_1 = JSON.parse(jsonText);
|
|
1450
|
-
this.ngZone.run(function () { return _this.handleJsonMessage(msg_1); });
|
|
1451
|
-
}
|
|
1452
|
-
catch (_a) {
|
|
1453
|
-
// Ignore non-JSON
|
|
1454
|
-
}
|
|
1455
|
-
};
|
|
1456
|
-
WebSocketVoiceClientService.prototype.handleBinaryMessage = function (buffer) {
|
|
1457
|
-
var _this = this;
|
|
1458
|
-
// Some backends wrap JSON events inside binary WS frames.
|
|
1459
|
-
var maybeText = this.tryDecodeUtf8(buffer);
|
|
1460
|
-
if (maybeText !== null) {
|
|
1461
|
-
this.handleJsonString(maybeText);
|
|
1462
|
-
return;
|
|
1463
|
-
}
|
|
1464
|
-
// Otherwise treat binary as streamed assistant audio.
|
|
1465
|
-
this.ngZone.run(function () { return _this.audioChunkSubject.next(buffer); });
|
|
1466
|
-
};
|
|
1467
|
-
WebSocketVoiceClientService.prototype.tryDecodeUtf8 = function (buffer) {
|
|
1468
|
-
try {
|
|
1469
|
-
var text = new TextDecoder('utf-8', { fatal: true }).decode(buffer);
|
|
1470
|
-
var trimmed = text.trim();
|
|
1471
|
-
if (!trimmed || (trimmed[0] !== '{' && trimmed[0] !== '[')) {
|
|
1472
|
-
return null;
|
|
1473
|
-
}
|
|
1474
|
-
return trimmed;
|
|
1475
|
-
}
|
|
1476
|
-
catch (_a) {
|
|
1477
|
-
return null;
|
|
1478
|
-
}
|
|
1479
|
-
};
|
|
1480
|
-
WebSocketVoiceClientService.prototype.handleJsonMessage = function (msg) {
|
|
1481
|
-
var type = msg.type;
|
|
1482
|
-
var typeStr = typeof type === 'string' ? type : '';
|
|
1483
|
-
if (typeStr === 'session_ready' || typeStr === 'connected' || typeStr === 'voice_session_started') {
|
|
1484
|
-
return;
|
|
1485
|
-
}
|
|
1486
|
-
if (typeStr === 'assistant_speaking' ||
|
|
1487
|
-
typeStr === 'bot_speaking') {
|
|
1488
|
-
if (msg.active === true || msg.speaking === true) {
|
|
1489
|
-
this.assistantSpeakingSubject.next(true);
|
|
1490
|
-
}
|
|
1491
|
-
else if (msg.active === false || msg.speaking === false) {
|
|
1492
|
-
this.assistantSpeakingSubject.next(false);
|
|
1493
|
-
}
|
|
1494
|
-
return;
|
|
1495
|
-
}
|
|
1496
|
-
if (typeStr === 'user_speaking') {
|
|
1497
|
-
if (msg.active === true || msg.speaking === true) {
|
|
1498
|
-
this.serverUserSpeakingSubject.next(true);
|
|
1499
|
-
}
|
|
1500
|
-
else if (msg.active === false || msg.speaking === false) {
|
|
1501
|
-
this.serverUserSpeakingSubject.next(false);
|
|
1502
|
-
}
|
|
1503
|
-
return;
|
|
1504
|
-
}
|
|
1505
|
-
if (typeStr === 'input_audio_buffer.speech_started') {
|
|
1506
|
-
this.serverUserSpeakingSubject.next(true);
|
|
1507
|
-
return;
|
|
1508
|
-
}
|
|
1509
|
-
if (typeStr === 'input_audio_buffer.speech_stopped') {
|
|
1510
|
-
this.serverUserSpeakingSubject.next(false);
|
|
1511
|
-
return;
|
|
1512
|
-
}
|
|
1513
|
-
if (typeStr === 'response.audio.delta') {
|
|
1514
|
-
this.assistantSpeakingSubject.next(true);
|
|
1515
|
-
return;
|
|
1516
|
-
}
|
|
1517
|
-
if (typeStr === 'response.audio.done' ||
|
|
1518
|
-
typeStr === 'response.output_audio.done') {
|
|
1519
|
-
this.assistantSpeakingSubject.next(false);
|
|
1520
|
-
return;
|
|
1521
|
-
}
|
|
1522
|
-
if (typeStr === 'user_transcript' && typeof msg.text === 'string') {
|
|
1523
|
-
this.userTranscriptSubject.next({
|
|
1524
|
-
text: msg.text,
|
|
1525
|
-
final: msg.final === true,
|
|
1526
|
-
});
|
|
1527
|
-
return;
|
|
1528
|
-
}
|
|
1529
|
-
if (typeStr === 'bot_transcript' && typeof msg.text === 'string') {
|
|
1530
|
-
this.botTranscriptSubject.next(msg.text);
|
|
1531
|
-
}
|
|
1532
|
-
};
|
|
1533
|
-
WebSocketVoiceClientService.prototype.disconnect = function () {
|
|
1534
|
-
if (!this.ws) {
|
|
1535
|
-
return;
|
|
1536
|
-
}
|
|
1537
|
-
this.closeInitiatedByClient = true;
|
|
1538
|
-
this.ws.close();
|
|
1539
|
-
};
|
|
1540
|
-
Object.defineProperty(WebSocketVoiceClientService.prototype, "isConnected", {
|
|
1541
|
-
get: function () {
|
|
1542
|
-
var _a;
|
|
1543
|
-
return ((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN;
|
|
1544
|
-
},
|
|
1545
|
-
enumerable: false,
|
|
1546
|
-
configurable: true
|
|
1547
|
-
});
|
|
1548
|
-
return WebSocketVoiceClientService;
|
|
1549
|
-
}());
|
|
1550
|
-
WebSocketVoiceClientService.ɵprov = i0__namespace.ɵɵdefineInjectable({ factory: function WebSocketVoiceClientService_Factory() { return new WebSocketVoiceClientService(i0__namespace.ɵɵinject(i0__namespace.NgZone)); }, token: WebSocketVoiceClientService, providedIn: "root" });
|
|
1551
|
-
WebSocketVoiceClientService.decorators = [
|
|
1552
|
-
{ type: i0.Injectable, args: [{
|
|
1553
|
-
providedIn: 'root',
|
|
1554
|
-
},] }
|
|
1555
|
-
];
|
|
1556
|
-
WebSocketVoiceClientService.ctorParameters = function () { return [
|
|
1557
|
-
{ type: i0.NgZone }
|
|
1558
|
-
]; };
|
|
1559
|
-
|
|
1560
|
-
/**
|
|
1561
|
-
* Voice agent orchestrator: single WebSocket (`ws_url` from POST /ai/ask-voice-socket)
|
|
1562
|
-
* for session events, transcripts, and optional speaking hints; local mic for capture
|
|
1563
|
-
* and waveform only (no Daily/WebRTC room).
|
|
1344
|
+
* Audio flow (mirrors the React reference implementation):
|
|
1345
|
+
* - Local mic: acquired by PipecatClient.initDevices(); local track fed to
|
|
1346
|
+
* AudioAnalyzerService for waveform visualisation.
|
|
1347
|
+
* - Bot audio: received as a MediaStreamTrack via RTVIEvent.TrackStarted,
|
|
1348
|
+
* played through a hidden <audio> element.
|
|
1349
|
+
* - All binary protobuf framing / RTVI protocol handled by
|
|
1350
|
+
* @pipecat-ai/client-js + @pipecat-ai/websocket-transport.
|
|
1564
1351
|
*/
|
|
1565
1352
|
var VoiceAgentService = /** @class */ (function () {
|
|
1566
|
-
function VoiceAgentService(audioAnalyzer,
|
|
1353
|
+
function VoiceAgentService(audioAnalyzer, platformTokenRefresh, ngZone,
|
|
1567
1354
|
/** `Object` not `object` — ngc metadata collection rejects the `object` type in DI params. */
|
|
1568
1355
|
platformId) {
|
|
1569
1356
|
var _this = this;
|
|
1570
1357
|
this.audioAnalyzer = audioAnalyzer;
|
|
1571
|
-
this.wsClient = wsClient;
|
|
1572
1358
|
this.platformTokenRefresh = platformTokenRefresh;
|
|
1359
|
+
this.ngZone = ngZone;
|
|
1573
1360
|
this.platformId = platformId;
|
|
1574
1361
|
this.callStateSubject = new rxjs.BehaviorSubject('idle');
|
|
1575
1362
|
this.statusTextSubject = new rxjs.BehaviorSubject('');
|
|
@@ -1581,11 +1368,8 @@
|
|
|
1581
1368
|
this.botTranscriptSubject = new rxjs.Subject();
|
|
1582
1369
|
this.callStartTime = 0;
|
|
1583
1370
|
this.durationInterval = null;
|
|
1584
|
-
this.
|
|
1585
|
-
this.
|
|
1586
|
-
this.pendingRemoteAudio = [];
|
|
1587
|
-
this.remoteAudioPlaying = false;
|
|
1588
|
-
this.endCall$ = new rxjs.Subject();
|
|
1371
|
+
this.pcClient = null;
|
|
1372
|
+
this.botAudioElement = null;
|
|
1589
1373
|
this.subscriptions = new rxjs.Subscription();
|
|
1590
1374
|
this.destroy$ = new rxjs.Subject();
|
|
1591
1375
|
this.callState$ = this.callStateSubject.asObservable();
|
|
@@ -1597,123 +1381,134 @@
|
|
|
1597
1381
|
this.userTranscript$ = this.userTranscriptSubject.asObservable();
|
|
1598
1382
|
this.botTranscript$ = this.botTranscriptSubject.asObservable();
|
|
1599
1383
|
this.subscriptions.add(this.audioAnalyzer.audioLevels$.subscribe(function (levels) { return _this.audioLevelsSubject.next(levels); }));
|
|
1600
|
-
this.subscriptions.add(this.wsClient.remoteClose$
|
|
1601
|
-
.pipe(operators.takeUntil(this.destroy$))
|
|
1602
|
-
.subscribe(function () { return void _this.handleRemoteClose(); }));
|
|
1603
|
-
this.subscriptions.add(this.wsClient.audioChunk$
|
|
1604
|
-
.pipe(operators.takeUntil(this.destroy$))
|
|
1605
|
-
.subscribe(function (chunk) { return _this.enqueueRemoteAudio(chunk); }));
|
|
1606
1384
|
}
|
|
1607
1385
|
VoiceAgentService.prototype.ngOnDestroy = function () {
|
|
1608
1386
|
this.destroy$.next();
|
|
1609
1387
|
this.subscriptions.unsubscribe();
|
|
1610
|
-
this.disconnect();
|
|
1388
|
+
void this.disconnect();
|
|
1611
1389
|
};
|
|
1612
|
-
/** Reset to idle
|
|
1390
|
+
/** Reset to idle (e.g. when modal re-opens so user can click Start Call). */
|
|
1613
1391
|
VoiceAgentService.prototype.resetToIdle = function () {
|
|
1614
1392
|
if (this.callStateSubject.value === 'idle')
|
|
1615
1393
|
return;
|
|
1616
|
-
this.
|
|
1617
|
-
this.stopDurationTimer();
|
|
1618
|
-
this.callStartTime = 0;
|
|
1619
|
-
this.audioAnalyzer.stop();
|
|
1620
|
-
this.stopLocalMic();
|
|
1621
|
-
this.resetRemoteAudioPlayback();
|
|
1622
|
-
this.wsClient.disconnect();
|
|
1394
|
+
void this.disconnect();
|
|
1623
1395
|
this.callStateSubject.next('idle');
|
|
1624
1396
|
this.statusTextSubject.next('');
|
|
1625
1397
|
this.durationSubject.next('0:00');
|
|
1626
1398
|
};
|
|
1627
1399
|
VoiceAgentService.prototype.connect = function (apiUrl, token, botId, conversationId, apiKey, eventToken, eventId, eventUrl, domainAuthority, usersApiUrl) {
|
|
1628
1400
|
return __awaiter(this, void 0, void 0, function () {
|
|
1629
|
-
var accessToken, ensured, e_1, baseUrl,
|
|
1401
|
+
var accessToken, ensured, e_1, baseUrl, pcClient, requestHeaders, error_1;
|
|
1630
1402
|
var _this = this;
|
|
1631
|
-
return __generator(this, function (
|
|
1632
|
-
switch (
|
|
1403
|
+
return __generator(this, function (_d) {
|
|
1404
|
+
switch (_d.label) {
|
|
1633
1405
|
case 0:
|
|
1634
1406
|
if (this.callStateSubject.value !== 'idle') {
|
|
1635
|
-
console.warn('Call already in progress');
|
|
1407
|
+
console.warn('[HiveGpt Voice] Call already in progress');
|
|
1636
1408
|
return [2 /*return*/];
|
|
1637
1409
|
}
|
|
1638
|
-
|
|
1410
|
+
_d.label = 1;
|
|
1639
1411
|
case 1:
|
|
1640
|
-
|
|
1412
|
+
_d.trys.push([1, 8, , 10]);
|
|
1641
1413
|
this.callStateSubject.next('connecting');
|
|
1642
1414
|
this.statusTextSubject.next('Connecting...');
|
|
1643
1415
|
accessToken = token;
|
|
1644
1416
|
if (!(usersApiUrl && common.isPlatformBrowser(this.platformId))) return [3 /*break*/, 5];
|
|
1645
|
-
|
|
1417
|
+
_d.label = 2;
|
|
1646
1418
|
case 2:
|
|
1647
|
-
|
|
1419
|
+
_d.trys.push([2, 4, , 5]);
|
|
1648
1420
|
return [4 /*yield*/, this.platformTokenRefresh
|
|
1649
1421
|
.ensureValidAccessToken(token, usersApiUrl)
|
|
1650
1422
|
.pipe(operators.take(1))
|
|
1651
1423
|
.toPromise()];
|
|
1652
1424
|
case 3:
|
|
1653
|
-
ensured =
|
|
1654
|
-
if (ensured === null || ensured === void 0 ? void 0 : ensured.accessToken)
|
|
1425
|
+
ensured = _d.sent();
|
|
1426
|
+
if (ensured === null || ensured === void 0 ? void 0 : ensured.accessToken)
|
|
1655
1427
|
accessToken = ensured.accessToken;
|
|
1656
|
-
}
|
|
1657
1428
|
return [3 /*break*/, 5];
|
|
1658
1429
|
case 4:
|
|
1659
|
-
e_1 =
|
|
1660
|
-
console.warn('[HiveGpt Voice] Token refresh
|
|
1430
|
+
e_1 = _d.sent();
|
|
1431
|
+
console.warn('[HiveGpt Voice] Token refresh failed', e_1);
|
|
1661
1432
|
return [3 /*break*/, 5];
|
|
1662
1433
|
case 5:
|
|
1663
1434
|
baseUrl = apiUrl.replace(/\/$/, '');
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1435
|
+
pcClient = new clientJs.PipecatClient({
|
|
1436
|
+
transport: new websocketTransport.WebSocketTransport(),
|
|
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()];
|
|
1479
|
+
case 6:
|
|
1480
|
+
// Acquire mic (triggers browser permission prompt)
|
|
1481
|
+
_d.sent();
|
|
1482
|
+
requestHeaders = new Headers();
|
|
1483
|
+
requestHeaders.append('Authorization', "Bearer " + accessToken);
|
|
1484
|
+
requestHeaders.append('x-api-key', apiKey);
|
|
1485
|
+
requestHeaders.append('hive-bot-id', botId);
|
|
1486
|
+
requestHeaders.append('domain-authority', domainAuthority);
|
|
1487
|
+
requestHeaders.append('eventUrl', eventUrl);
|
|
1488
|
+
requestHeaders.append('eventId', eventId);
|
|
1489
|
+
requestHeaders.append('eventToken', eventToken);
|
|
1490
|
+
requestHeaders.append('ngrok-skip-browser-warning', 'true');
|
|
1491
|
+
// POST to /ai/ask-voice-socket → receives { ws_url } → WebSocketTransport connects
|
|
1492
|
+
return [4 /*yield*/, pcClient.startBotAndConnect({
|
|
1493
|
+
endpoint: baseUrl + "/ai/ask-voice-socket",
|
|
1494
|
+
headers: requestHeaders,
|
|
1495
|
+
requestData: {
|
|
1680
1496
|
bot_id: botId,
|
|
1681
1497
|
conversation_id: conversationId,
|
|
1682
1498
|
voice: 'alloy',
|
|
1683
|
-
}
|
|
1499
|
+
},
|
|
1684
1500
|
})];
|
|
1685
|
-
case 6:
|
|
1686
|
-
res = _b.sent();
|
|
1687
|
-
if (!res.ok) {
|
|
1688
|
-
throw new Error("HTTP " + res.status);
|
|
1689
|
-
}
|
|
1690
|
-
return [4 /*yield*/, res.json()];
|
|
1691
1501
|
case 7:
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
(typeof (json === null || json === void 0 ? void 0 : json.rn_ws_url) === 'string' && json.rn_ws_url);
|
|
1695
|
-
if (!wsUrl) {
|
|
1696
|
-
throw new Error('No ws_url in response');
|
|
1697
|
-
}
|
|
1698
|
-
untilCallEnds$ = rxjs.merge(this.destroy$, this.endCall$);
|
|
1699
|
-
this.subscriptions.add(this.wsClient.userTranscript$
|
|
1700
|
-
.pipe(operators.takeUntil(untilCallEnds$))
|
|
1701
|
-
.subscribe(function (t) { return _this.userTranscriptSubject.next(t); }));
|
|
1702
|
-
this.subscriptions.add(this.wsClient.botTranscript$
|
|
1703
|
-
.pipe(operators.takeUntil(untilCallEnds$))
|
|
1704
|
-
.subscribe(function (t) { return _this.botTranscriptSubject.next(t); }));
|
|
1705
|
-
this.subscriptions.add(this.wsClient.opened$
|
|
1706
|
-
.pipe(operators.takeUntil(untilCallEnds$), operators.take(1))
|
|
1707
|
-
.subscribe(function () { return void _this.onWebsocketOpened(); }));
|
|
1708
|
-
this.wsClient.connect(wsUrl);
|
|
1502
|
+
// POST to /ai/ask-voice-socket → receives { ws_url } → WebSocketTransport connects
|
|
1503
|
+
_d.sent();
|
|
1709
1504
|
return [3 /*break*/, 10];
|
|
1710
1505
|
case 8:
|
|
1711
|
-
error_1 =
|
|
1712
|
-
console.error('
|
|
1506
|
+
error_1 = _d.sent();
|
|
1507
|
+
console.error('[HiveGpt Voice] connect failed', error_1);
|
|
1713
1508
|
this.callStateSubject.next('ended');
|
|
1714
|
-
return [4 /*yield*/, this.
|
|
1509
|
+
return [4 /*yield*/, this.cleanupPipecatClient()];
|
|
1715
1510
|
case 9:
|
|
1716
|
-
|
|
1511
|
+
_d.sent();
|
|
1717
1512
|
this.statusTextSubject.next('Connection failed');
|
|
1718
1513
|
throw error_1;
|
|
1719
1514
|
case 10: return [2 /*return*/];
|
|
@@ -1721,249 +1516,143 @@
|
|
|
1721
1516
|
});
|
|
1722
1517
|
});
|
|
1723
1518
|
};
|
|
1724
|
-
VoiceAgentService.prototype.
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
}
|
|
1733
|
-
_b.label = 1;
|
|
1734
|
-
case 1:
|
|
1735
|
-
_b.trys.push([1, 3, , 5]);
|
|
1736
|
-
return [4 /*yield*/, this.startLocalMic()];
|
|
1737
|
-
case 2:
|
|
1738
|
-
_b.sent();
|
|
1739
|
-
this.statusTextSubject.next('Connected');
|
|
1740
|
-
this.callStateSubject.next('connected');
|
|
1741
|
-
this.wireSpeakingState();
|
|
1742
|
-
return [3 /*break*/, 5];
|
|
1743
|
-
case 3:
|
|
1744
|
-
err_1 = _b.sent();
|
|
1745
|
-
console.error('[HiveGpt Voice] Mic or session setup failed', err_1);
|
|
1746
|
-
this.callStateSubject.next('ended');
|
|
1747
|
-
this.statusTextSubject.next('Microphone unavailable');
|
|
1748
|
-
return [4 /*yield*/, this.disconnect()];
|
|
1749
|
-
case 4:
|
|
1750
|
-
_b.sent();
|
|
1751
|
-
return [3 /*break*/, 5];
|
|
1752
|
-
case 5: return [2 /*return*/];
|
|
1753
|
-
}
|
|
1754
|
-
});
|
|
1755
|
-
});
|
|
1519
|
+
VoiceAgentService.prototype.onPipecatConnected = function () {
|
|
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();
|
|
1756
1527
|
};
|
|
1757
|
-
VoiceAgentService.prototype.
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
this.
|
|
1770
|
-
|
|
1771
|
-
.
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
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;
|
|
1790
1584
|
}
|
|
1791
|
-
|
|
1792
|
-
|
|
1585
|
+
catch (_b) {
|
|
1586
|
+
// ignore
|
|
1793
1587
|
}
|
|
1794
|
-
|
|
1588
|
+
this.botAudioElement = null;
|
|
1589
|
+
}
|
|
1795
1590
|
};
|
|
1796
|
-
VoiceAgentService.prototype.
|
|
1591
|
+
VoiceAgentService.prototype.disconnect = function () {
|
|
1797
1592
|
return __awaiter(this, void 0, void 0, function () {
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
switch (_b.label) {
|
|
1593
|
+
return __generator(this, function (_d) {
|
|
1594
|
+
switch (_d.label) {
|
|
1801
1595
|
case 0:
|
|
1802
|
-
this.
|
|
1803
|
-
|
|
1596
|
+
this.stopDurationTimer();
|
|
1597
|
+
this.callStartTime = 0;
|
|
1598
|
+
this.audioAnalyzer.stop();
|
|
1599
|
+
this.stopBotAudio();
|
|
1600
|
+
return [4 /*yield*/, this.cleanupPipecatClient()];
|
|
1804
1601
|
case 1:
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
stream.getTracks().forEach(function (t) { return t.stop(); });
|
|
1809
|
-
throw new Error('No audio track');
|
|
1810
|
-
}
|
|
1811
|
-
this.localMicStream = stream;
|
|
1812
|
-
this.isMicMutedSubject.next(!track.enabled);
|
|
1813
|
-
this.audioAnalyzer.start(stream);
|
|
1602
|
+
_d.sent();
|
|
1603
|
+
this.callStateSubject.next('ended');
|
|
1604
|
+
this.statusTextSubject.next('Call Ended');
|
|
1814
1605
|
return [2 /*return*/];
|
|
1815
1606
|
}
|
|
1816
1607
|
});
|
|
1817
1608
|
});
|
|
1818
1609
|
};
|
|
1819
|
-
VoiceAgentService.prototype.
|
|
1820
|
-
if (this.localMicStream) {
|
|
1821
|
-
this.localMicStream.getTracks().forEach(function (t) { return t.stop(); });
|
|
1822
|
-
this.localMicStream = null;
|
|
1823
|
-
}
|
|
1824
|
-
};
|
|
1825
|
-
VoiceAgentService.prototype.enqueueRemoteAudio = function (chunk) {
|
|
1826
|
-
this.pendingRemoteAudio.push(chunk.slice(0));
|
|
1827
|
-
if (!this.remoteAudioPlaying) {
|
|
1828
|
-
void this.playRemoteAudioQueue();
|
|
1829
|
-
}
|
|
1830
|
-
};
|
|
1831
|
-
VoiceAgentService.prototype.playRemoteAudioQueue = function () {
|
|
1610
|
+
VoiceAgentService.prototype.cleanupPipecatClient = function () {
|
|
1832
1611
|
return __awaiter(this, void 0, void 0, function () {
|
|
1833
|
-
var
|
|
1834
|
-
return __generator(this, function (
|
|
1835
|
-
switch (
|
|
1612
|
+
var _a_1;
|
|
1613
|
+
return __generator(this, function (_d) {
|
|
1614
|
+
switch (_d.label) {
|
|
1836
1615
|
case 0:
|
|
1837
|
-
this.
|
|
1838
|
-
|
|
1839
|
-
_b.label = 1;
|
|
1616
|
+
if (!this.pcClient) return [3 /*break*/, 5];
|
|
1617
|
+
_d.label = 1;
|
|
1840
1618
|
case 1:
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
if (!chunk)
|
|
1844
|
-
return [3 /*break*/, 1];
|
|
1845
|
-
_b.label = 2;
|
|
1619
|
+
_d.trys.push([1, 3, , 4]);
|
|
1620
|
+
return [4 /*yield*/, this.pcClient.disconnect()];
|
|
1846
1621
|
case 2:
|
|
1847
|
-
|
|
1848
|
-
return [
|
|
1622
|
+
_d.sent();
|
|
1623
|
+
return [3 /*break*/, 4];
|
|
1849
1624
|
case 3:
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
return [4 /*yield*/, this.playDecodedBuffer(context, decoded)];
|
|
1625
|
+
_a_1 = _d.sent();
|
|
1626
|
+
return [3 /*break*/, 4];
|
|
1853
1627
|
case 4:
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
case 5:
|
|
1857
|
-
_a_1 = _b.sent();
|
|
1858
|
-
return [3 /*break*/, 6];
|
|
1859
|
-
case 6: return [3 /*break*/, 1];
|
|
1860
|
-
case 7:
|
|
1861
|
-
this.remoteAudioPlaying = false;
|
|
1862
|
-
this.assistantAudioStopped();
|
|
1863
|
-
return [2 /*return*/];
|
|
1628
|
+
this.pcClient = null;
|
|
1629
|
+
_d.label = 5;
|
|
1630
|
+
case 5: return [2 /*return*/];
|
|
1864
1631
|
}
|
|
1865
1632
|
});
|
|
1866
1633
|
});
|
|
1867
1634
|
};
|
|
1868
|
-
VoiceAgentService.prototype.getOrCreateRemoteAudioContext = function () {
|
|
1869
|
-
if (!this.remoteAudioContext || this.remoteAudioContext.state === 'closed') {
|
|
1870
|
-
this.remoteAudioContext = new AudioContext();
|
|
1871
|
-
}
|
|
1872
|
-
if (this.remoteAudioContext.state === 'suspended') {
|
|
1873
|
-
void this.remoteAudioContext.resume();
|
|
1874
|
-
}
|
|
1875
|
-
return this.remoteAudioContext;
|
|
1876
|
-
};
|
|
1877
|
-
VoiceAgentService.prototype.decodeAudioChunk = function (context, chunk) {
|
|
1878
|
-
return new Promise(function (resolve, reject) {
|
|
1879
|
-
context.decodeAudioData(chunk.slice(0), resolve, reject);
|
|
1880
|
-
});
|
|
1881
|
-
};
|
|
1882
|
-
VoiceAgentService.prototype.playDecodedBuffer = function (context, buffer) {
|
|
1883
|
-
return new Promise(function (resolve) {
|
|
1884
|
-
var source = context.createBufferSource();
|
|
1885
|
-
source.buffer = buffer;
|
|
1886
|
-
source.connect(context.destination);
|
|
1887
|
-
source.onended = function () { return resolve(); };
|
|
1888
|
-
source.start();
|
|
1889
|
-
});
|
|
1890
|
-
};
|
|
1891
|
-
VoiceAgentService.prototype.assistantAudioStarted = function () {
|
|
1892
|
-
if (this.callStartTime === 0) {
|
|
1893
|
-
this.callStartTime = Date.now();
|
|
1894
|
-
this.startDurationTimer();
|
|
1895
|
-
}
|
|
1896
|
-
this.callStateSubject.next('talking');
|
|
1897
|
-
};
|
|
1898
|
-
VoiceAgentService.prototype.assistantAudioStopped = function () {
|
|
1899
|
-
if (this.callStateSubject.value === 'talking') {
|
|
1900
|
-
this.callStateSubject.next('connected');
|
|
1901
|
-
}
|
|
1902
|
-
};
|
|
1903
|
-
VoiceAgentService.prototype.resetRemoteAudioPlayback = function () {
|
|
1904
|
-
this.pendingRemoteAudio = [];
|
|
1905
|
-
this.remoteAudioPlaying = false;
|
|
1906
|
-
if (this.remoteAudioContext && this.remoteAudioContext.state !== 'closed') {
|
|
1907
|
-
this.remoteAudioContext.close().catch(function () { });
|
|
1908
|
-
}
|
|
1909
|
-
this.remoteAudioContext = null;
|
|
1910
|
-
};
|
|
1911
|
-
VoiceAgentService.prototype.handleRemoteClose = function () {
|
|
1912
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
1913
|
-
var state;
|
|
1914
|
-
return __generator(this, function (_b) {
|
|
1915
|
-
state = this.callStateSubject.value;
|
|
1916
|
-
if (state === 'idle' || state === 'ended')
|
|
1917
|
-
return [2 /*return*/];
|
|
1918
|
-
this.endCall$.next();
|
|
1919
|
-
this.stopDurationTimer();
|
|
1920
|
-
this.callStartTime = 0;
|
|
1921
|
-
this.audioAnalyzer.stop();
|
|
1922
|
-
this.stopLocalMic();
|
|
1923
|
-
this.resetRemoteAudioPlayback();
|
|
1924
|
-
this.callStateSubject.next('ended');
|
|
1925
|
-
this.statusTextSubject.next('Connection lost');
|
|
1926
|
-
return [2 /*return*/];
|
|
1927
|
-
});
|
|
1928
|
-
});
|
|
1929
|
-
};
|
|
1930
|
-
VoiceAgentService.prototype.disconnect = function () {
|
|
1931
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
1932
|
-
return __generator(this, function (_b) {
|
|
1933
|
-
this.endCall$.next();
|
|
1934
|
-
this.stopDurationTimer();
|
|
1935
|
-
this.callStartTime = 0;
|
|
1936
|
-
this.audioAnalyzer.stop();
|
|
1937
|
-
this.stopLocalMic();
|
|
1938
|
-
this.resetRemoteAudioPlayback();
|
|
1939
|
-
this.wsClient.disconnect();
|
|
1940
|
-
this.callStateSubject.next('ended');
|
|
1941
|
-
this.statusTextSubject.next('Call Ended');
|
|
1942
|
-
return [2 /*return*/];
|
|
1943
|
-
});
|
|
1944
|
-
});
|
|
1945
|
-
};
|
|
1946
1635
|
VoiceAgentService.prototype.toggleMic = function () {
|
|
1947
|
-
|
|
1636
|
+
if (!this.pcClient)
|
|
1637
|
+
return;
|
|
1948
1638
|
var nextMuted = !this.isMicMutedSubject.value;
|
|
1949
|
-
|
|
1950
|
-
if (track) {
|
|
1951
|
-
track.enabled = !nextMuted;
|
|
1952
|
-
}
|
|
1639
|
+
this.pcClient.enableMic(!nextMuted);
|
|
1953
1640
|
this.isMicMutedSubject.next(nextMuted);
|
|
1641
|
+
if (nextMuted)
|
|
1642
|
+
this.isUserSpeakingSubject.next(false);
|
|
1954
1643
|
};
|
|
1955
1644
|
VoiceAgentService.prototype.startDurationTimer = function () {
|
|
1956
1645
|
var _this = this;
|
|
1957
|
-
var
|
|
1646
|
+
var tick = function () {
|
|
1958
1647
|
if (_this.callStartTime > 0) {
|
|
1959
1648
|
var elapsed = Math.floor((Date.now() - _this.callStartTime) / 1000);
|
|
1960
|
-
var
|
|
1961
|
-
var
|
|
1962
|
-
_this.durationSubject.next(
|
|
1649
|
+
var m = Math.floor(elapsed / 60);
|
|
1650
|
+
var s = elapsed % 60;
|
|
1651
|
+
_this.durationSubject.next(m + ":" + String(s).padStart(2, '0'));
|
|
1963
1652
|
}
|
|
1964
1653
|
};
|
|
1965
|
-
|
|
1966
|
-
this.durationInterval = setInterval(
|
|
1654
|
+
tick();
|
|
1655
|
+
this.durationInterval = setInterval(tick, 1000);
|
|
1967
1656
|
};
|
|
1968
1657
|
VoiceAgentService.prototype.stopDurationTimer = function () {
|
|
1969
1658
|
if (this.durationInterval) {
|
|
@@ -1973,7 +1662,7 @@
|
|
|
1973
1662
|
};
|
|
1974
1663
|
return VoiceAgentService;
|
|
1975
1664
|
}());
|
|
1976
|
-
VoiceAgentService.ɵprov = i0__namespace.ɵɵdefineInjectable({ factory: function VoiceAgentService_Factory() { return new VoiceAgentService(i0__namespace.ɵɵinject(AudioAnalyzerService), i0__namespace.ɵɵinject(
|
|
1665
|
+
VoiceAgentService.ɵprov = i0__namespace.ɵɵdefineInjectable({ factory: function VoiceAgentService_Factory() { return new VoiceAgentService(i0__namespace.ɵɵinject(AudioAnalyzerService), i0__namespace.ɵɵinject(PlatformTokenRefreshService), i0__namespace.ɵɵinject(i0__namespace.NgZone), i0__namespace.ɵɵinject(i0__namespace.PLATFORM_ID)); }, token: VoiceAgentService, providedIn: "root" });
|
|
1977
1666
|
VoiceAgentService.decorators = [
|
|
1978
1667
|
{ type: i0.Injectable, args: [{
|
|
1979
1668
|
providedIn: 'root',
|
|
@@ -1981,8 +1670,8 @@
|
|
|
1981
1670
|
];
|
|
1982
1671
|
VoiceAgentService.ctorParameters = function () { return [
|
|
1983
1672
|
{ type: AudioAnalyzerService },
|
|
1984
|
-
{ type: WebSocketVoiceClientService },
|
|
1985
1673
|
{ type: PlatformTokenRefreshService },
|
|
1674
|
+
{ type: i0.NgZone },
|
|
1986
1675
|
{ type: Object, decorators: [{ type: i0.Inject, args: [i0.PLATFORM_ID,] }] }
|
|
1987
1676
|
]; };
|
|
1988
1677
|
|
|
@@ -2013,12 +1702,27 @@
|
|
|
2013
1702
|
this.isMicMuted = false;
|
|
2014
1703
|
this.isUserSpeaking = false;
|
|
2015
1704
|
this.audioLevels = [];
|
|
2016
|
-
this.isSpeaking = false;
|
|
2017
|
-
/** Track whether call has transitioned out of initial connected state. */
|
|
2018
|
-
this.hasLeftConnectedOnce = false;
|
|
2019
1705
|
this.subscriptions = [];
|
|
2020
1706
|
this.isConnecting = false;
|
|
2021
1707
|
}
|
|
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
|
+
});
|
|
2022
1726
|
VoiceAgentModalComponent.prototype.ngOnInit = function () {
|
|
2023
1727
|
var _this = this;
|
|
2024
1728
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
@@ -2040,16 +1744,8 @@
|
|
|
2040
1744
|
this.agentAvatar = this.injectedConfig.agentAvatar;
|
|
2041
1745
|
this.usersApiUrl = (_h = this.injectedConfig.usersApiUrl) !== null && _h !== void 0 ? _h : this.usersApiUrl;
|
|
2042
1746
|
}
|
|
2043
|
-
// Subscribe to observables
|
|
2044
1747
|
this.subscriptions.push(this.voiceAgentService.callState$.subscribe(function (state) {
|
|
2045
1748
|
_this.callState = state;
|
|
2046
|
-
_this.isSpeaking = state === 'talking';
|
|
2047
|
-
if (state === 'listening' || state === 'talking') {
|
|
2048
|
-
_this.hasLeftConnectedOnce = true;
|
|
2049
|
-
}
|
|
2050
|
-
if (state === 'idle' || state === 'ended') {
|
|
2051
|
-
_this.hasLeftConnectedOnce = false;
|
|
2052
|
-
}
|
|
2053
1749
|
}));
|
|
2054
1750
|
this.subscriptions.push(this.voiceAgentService.statusText$.subscribe(function (text) {
|
|
2055
1751
|
_this.statusText = text;
|
|
@@ -2134,18 +1830,16 @@
|
|
|
2134
1830
|
return minHeight + (n / 100) * (maxHeight - minHeight);
|
|
2135
1831
|
};
|
|
2136
1832
|
Object.defineProperty(VoiceAgentModalComponent.prototype, "statusLabel", {
|
|
2137
|
-
/** Status label for active call. */
|
|
1833
|
+
/** Status label for active call — driven by callState + service statusText. */
|
|
2138
1834
|
get: function () {
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
return '
|
|
2143
|
-
|
|
2144
|
-
return '
|
|
2145
|
-
|
|
2146
|
-
return this.hasLeftConnectedOnce ? 'Talking...' : 'Connected';
|
|
1835
|
+
switch (this.callState) {
|
|
1836
|
+
case 'connecting': return 'Connecting...';
|
|
1837
|
+
case 'talking': return 'Talking...';
|
|
1838
|
+
case 'listening': return 'Listening...';
|
|
1839
|
+
// 'connected' covers: initial connect, between turns (Listening / Processing)
|
|
1840
|
+
case 'connected': return this.statusText || 'Connected';
|
|
1841
|
+
default: return this.statusText || '';
|
|
2147
1842
|
}
|
|
2148
|
-
return this.statusText || '';
|
|
2149
1843
|
},
|
|
2150
1844
|
enumerable: false,
|
|
2151
1845
|
configurable: true
|
|
@@ -2174,8 +1868,8 @@
|
|
|
2174
1868
|
VoiceAgentModalComponent.decorators = [
|
|
2175
1869
|
{ type: i0.Component, args: [{
|
|
2176
1870
|
selector: 'hivegpt-voice-agent-modal',
|
|
2177
|
-
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",
|
|
2178
|
-
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
|
|
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%);transition:opacity .4s ease;width:240px;z-index:-1}.avatar-glow.glow-talking{animation:glowPulse 1.5s ease-in-out infinite;background:radial-gradient(circle,rgba(14,165,164,.35) 0,transparent 65%);height:280px;width:280px}.avatar-glow.glow-listening{background:radial-gradient(circle,rgba(99,102,241,.25) 0,transparent 65%)}@keyframes glowPulse{0%,to{opacity:.7;transform:translate(-50%,-50%) scale(1)}50%{opacity:1;transform:translate(-50%,-50%) scale(1.08)}}.avatar-wrapper.speaking{animation:avatarPulse 1.4s ease-in-out infinite}.avatar-wrapper.listening{animation:avatarListenPulse 1.8s ease-in-out infinite}@keyframes avatarPulse{0%,to{box-shadow:0 0 0 0 rgba(14,165,164,.5)}50%{box-shadow:0 0 0 18px rgba(14,165,164,0)}}@keyframes avatarListenPulse{0%,to{box-shadow:0 0 0 0 rgba(99,102,241,.4)}50%{box-shadow:0 0 0 14px rgba(99,102,241,0)}}.particles-container{height:0;left:50%;pointer-events:none;position:absolute;top:50%;width:0;z-index:2}.particle{animation:particleOrbit 2.4s ease-in-out infinite;animation-delay:var(--delay,0s);background:#0ea5a4;border-radius:50%;height:7px;opacity:0;position:absolute;transform-origin:0 0;width:7px}@keyframes particleOrbit{0%{opacity:0;transform:rotate(calc(var(--i, 0)*22.5deg)) translateY(-108px) scale(.4)}25%{opacity:.9}75%{opacity:.9}to{opacity:0;transform:rotate(calc(var(--i, 0)*22.5deg + 45deg)) translateY(-108px) scale(.4)}}.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{color:#0f172a;font-size:16px;font-weight:400;transition:color .25s ease}.status-text.status-talking{color:#0ea5a4;font-weight:500}.status-text.status-listening{color:#6366f1;font-weight:500}.status-text.status-processing{color:#94a3b8}.status-timer{color:#0f172a;font-size:16px;font-weight:500}.voice-visualizer{align-items:center;display:flex;gap:3px;height:18px}.vbar{animation:vbarBounce 1s ease-in-out infinite;background:#0ea5a4;border-radius:2px;height:6px;width:3px}.vbar:first-child{animation-delay:0s}.vbar:nth-child(2){animation-delay:.15s}.vbar:nth-child(3){animation-delay:.3s}.vbar:nth-child(4){animation-delay:.45s}@keyframes vbarBounce{0%,to{height:4px;opacity:.5}50%{height:16px;opacity:1}}.processing-dots{align-items:center;display:flex;gap:4px}.processing-dots span{animation:dotFade 1.2s ease-in-out infinite;background:#94a3b8;border-radius:50%;display:inline-block;height:5px;width:5px}.processing-dots span:first-child{animation-delay:0s}.processing-dots span:nth-child(2){animation-delay:.2s}.processing-dots span:nth-child(3){animation-delay:.4s}@keyframes dotFade{0%,80%,to{opacity:.4;transform:scale(.8)}40%{opacity:1;transform:scale(1.2)}}.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}"]
|
|
2179
1873
|
},] }
|
|
2180
1874
|
];
|
|
2181
1875
|
VoiceAgentModalComponent.ctorParameters = function () { return [
|
|
@@ -6163,8 +5857,8 @@
|
|
|
6163
5857
|
};
|
|
6164
5858
|
|
|
6165
5859
|
/**
|
|
6166
|
-
* Voice agent module. Uses
|
|
6167
|
-
*
|
|
5860
|
+
* Voice agent module. Uses @pipecat-ai/client-js + @pipecat-ai/websocket-transport
|
|
5861
|
+
* (peer dependencies) for WebSocket transport, RTVI protocol, and audio.
|
|
6168
5862
|
*/
|
|
6169
5863
|
var VoiceAgentModule = /** @class */ (function () {
|
|
6170
5864
|
function VoiceAgentModule() {
|
|
@@ -6182,7 +5876,6 @@
|
|
|
6182
5876
|
providers: [
|
|
6183
5877
|
VoiceAgentService,
|
|
6184
5878
|
AudioAnalyzerService,
|
|
6185
|
-
WebSocketVoiceClientService
|
|
6186
5879
|
],
|
|
6187
5880
|
exports: [
|
|
6188
5881
|
VoiceAgentModalComponent
|
|
@@ -6483,10 +6176,9 @@
|
|
|
6483
6176
|
exports["ɵc"] = ConversationService;
|
|
6484
6177
|
exports["ɵd"] = NotificationSocket;
|
|
6485
6178
|
exports["ɵe"] = TranslationService;
|
|
6486
|
-
exports["ɵf"] =
|
|
6487
|
-
exports["ɵg"] =
|
|
6488
|
-
exports["ɵh"] =
|
|
6489
|
-
exports["ɵi"] = BotHtmlEditorComponent;
|
|
6179
|
+
exports["ɵf"] = VideoPlayerComponent;
|
|
6180
|
+
exports["ɵg"] = SafeHtmlPipe;
|
|
6181
|
+
exports["ɵh"] = BotHtmlEditorComponent;
|
|
6490
6182
|
|
|
6491
6183
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
6492
6184
|
|