@hivegpt/hiveai-angular 0.0.575 → 0.0.577
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 +195 -328
- package/bundles/hivegpt-hiveai-angular.umd.js.map +1 -1
- package/bundles/hivegpt-hiveai-angular.umd.min.js +1 -1
- package/bundles/hivegpt-hiveai-angular.umd.min.js.map +1 -1
- package/esm2015/lib/components/voice-agent/components/voice-agent-modal/voice-agent-modal.component.js +54 -85
- package/esm2015/lib/components/voice-agent/services/daily-voice-client.service.js +14 -22
- package/esm2015/lib/components/voice-agent/services/voice-agent.service.js +45 -67
- package/esm2015/lib/components/voice-agent/services/websocket-voice-client.service.js +34 -79
- package/fesm2015/hivegpt-hiveai-angular.js +142 -247
- package/fesm2015/hivegpt-hiveai-angular.js.map +1 -1
- package/hivegpt-hiveai-angular.metadata.json +1 -1
- package/lib/components/voice-agent/components/voice-agent-modal/voice-agent-modal.component.d.ts +1 -7
- package/lib/components/voice-agent/components/voice-agent-modal/voice-agent-modal.component.d.ts.map +1 -1
- package/lib/components/voice-agent/services/daily-voice-client.service.d.ts +3 -5
- package/lib/components/voice-agent/services/daily-voice-client.service.d.ts.map +1 -1
- package/lib/components/voice-agent/services/voice-agent.service.d.ts +1 -3
- 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 +12 -5
- package/lib/components/voice-agent/services/websocket-voice-client.service.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -813,7 +813,6 @@ AudioAnalyzerService.decorators = [
|
|
|
813
813
|
* - Emit roomCreated$, userTranscript$, botTranscript$
|
|
814
814
|
* - NO audio logic, NO mic logic. Audio is handled by Daily.js (WebRTC).
|
|
815
815
|
*/
|
|
816
|
-
const WS_CONNECT_TIMEOUT_MS = 10000;
|
|
817
816
|
class WebSocketVoiceClientService {
|
|
818
817
|
constructor() {
|
|
819
818
|
this.ws = null;
|
|
@@ -827,98 +826,54 @@ class WebSocketVoiceClientService {
|
|
|
827
826
|
/** Emits bot transcript updates. */
|
|
828
827
|
this.botTranscript$ = this.botTranscriptSubject.asObservable();
|
|
829
828
|
}
|
|
830
|
-
/**
|
|
831
|
-
* Connect to signaling WebSocket. No audio over this connection.
|
|
832
|
-
* Resolves when the socket is open; rejects if the connection fails.
|
|
833
|
-
*/
|
|
829
|
+
/** Connect to signaling WebSocket. No audio over this connection. */
|
|
834
830
|
connect(wsUrl) {
|
|
835
831
|
var _a;
|
|
836
832
|
if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN) {
|
|
837
|
-
return
|
|
833
|
+
return;
|
|
838
834
|
}
|
|
839
835
|
if (this.ws) {
|
|
840
836
|
this.ws.close();
|
|
841
837
|
this.ws = null;
|
|
842
838
|
}
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
839
|
+
try {
|
|
840
|
+
this.ws = new WebSocket(wsUrl);
|
|
841
|
+
this.ws.onmessage = (event) => {
|
|
846
842
|
var _a;
|
|
847
|
-
if (settled)
|
|
848
|
-
return;
|
|
849
|
-
settled = true;
|
|
850
843
|
try {
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
this.ws = null;
|
|
857
|
-
reject(new Error('WebSocket connection timed out'));
|
|
858
|
-
}, WS_CONNECT_TIMEOUT_MS);
|
|
859
|
-
const clear = () => {
|
|
860
|
-
clearTimeout(timeout);
|
|
861
|
-
};
|
|
862
|
-
try {
|
|
863
|
-
const ws = new WebSocket(wsUrl);
|
|
864
|
-
this.ws = ws;
|
|
865
|
-
ws.onopen = () => {
|
|
866
|
-
if (settled)
|
|
867
|
-
return;
|
|
868
|
-
settled = true;
|
|
869
|
-
clear();
|
|
870
|
-
resolve();
|
|
871
|
-
};
|
|
872
|
-
ws.onmessage = (event) => {
|
|
873
|
-
var _a;
|
|
874
|
-
try {
|
|
875
|
-
const msg = JSON.parse(event.data);
|
|
876
|
-
if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'room_created') {
|
|
877
|
-
const roomUrl = ((_a = msg.room_url) !== null && _a !== void 0 ? _a : msg.roomUrl);
|
|
878
|
-
if (typeof roomUrl === 'string') {
|
|
879
|
-
this.roomCreatedSubject.next(roomUrl);
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
else if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'user_transcript' && typeof msg.text === 'string') {
|
|
883
|
-
this.userTranscriptSubject.next({
|
|
884
|
-
text: msg.text,
|
|
885
|
-
final: msg.final === true,
|
|
886
|
-
});
|
|
844
|
+
const msg = JSON.parse(event.data);
|
|
845
|
+
if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'room_created') {
|
|
846
|
+
const roomUrl = ((_a = msg.room_url) !== null && _a !== void 0 ? _a : msg.roomUrl);
|
|
847
|
+
if (typeof roomUrl === 'string') {
|
|
848
|
+
this.roomCreatedSubject.next(roomUrl);
|
|
887
849
|
}
|
|
888
|
-
else if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'bot_transcript' && typeof msg.text === 'string') {
|
|
889
|
-
this.botTranscriptSubject.next(msg.text);
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
catch (_b) {
|
|
893
|
-
// Ignore non-JSON or unknown messages
|
|
894
850
|
}
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
this.disconnect();
|
|
901
|
-
reject(new Error('WebSocket connection failed'));
|
|
902
|
-
return;
|
|
851
|
+
else if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'user_transcript' && typeof msg.text === 'string') {
|
|
852
|
+
this.userTranscriptSubject.next({
|
|
853
|
+
text: msg.text,
|
|
854
|
+
final: msg.final === true,
|
|
855
|
+
});
|
|
903
856
|
}
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
ws.onclose = () => {
|
|
907
|
-
this.ws = null;
|
|
908
|
-
if (!settled) {
|
|
909
|
-
settled = true;
|
|
910
|
-
clear();
|
|
911
|
-
reject(new Error('WebSocket connection failed'));
|
|
857
|
+
else if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'bot_transcript' && typeof msg.text === 'string') {
|
|
858
|
+
this.botTranscriptSubject.next(msg.text);
|
|
912
859
|
}
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
860
|
+
}
|
|
861
|
+
catch (_b) {
|
|
862
|
+
// Ignore non-JSON or unknown messages
|
|
863
|
+
}
|
|
864
|
+
};
|
|
865
|
+
this.ws.onerror = () => {
|
|
866
|
+
this.disconnect();
|
|
867
|
+
};
|
|
868
|
+
this.ws.onclose = () => {
|
|
918
869
|
this.ws = null;
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
catch (err) {
|
|
873
|
+
console.error('WebSocketVoiceClient: connect failed', err);
|
|
874
|
+
this.ws = null;
|
|
875
|
+
throw err;
|
|
876
|
+
}
|
|
922
877
|
}
|
|
923
878
|
/** Disconnect and cleanup. */
|
|
924
879
|
disconnect() {
|
|
@@ -961,8 +916,7 @@ class DailyVoiceClientService {
|
|
|
961
916
|
this.remoteAudioElement = null;
|
|
962
917
|
/** AnalyserNode-based remote audio monitor for instant bot speaking detection. */
|
|
963
918
|
this.remoteAudioContext = null;
|
|
964
|
-
|
|
965
|
-
this.remoteSpeakingPollId = null;
|
|
919
|
+
this.remoteSpeakingRAF = null;
|
|
966
920
|
this.speakingSubject = new BehaviorSubject(false);
|
|
967
921
|
this.userSpeakingSubject = new BehaviorSubject(false);
|
|
968
922
|
this.micMutedSubject = new BehaviorSubject(false);
|
|
@@ -980,18 +934,15 @@ class DailyVoiceClientService {
|
|
|
980
934
|
* Connect to Daily room. Acquires mic first for waveform, then joins with audio.
|
|
981
935
|
* @param roomUrl Daily room URL (from room_created)
|
|
982
936
|
* @param token Optional meeting token
|
|
983
|
-
* @param existingStream Optional pre-acquired mic (avoids a second getUserMedia / extra prompts on some browsers)
|
|
984
937
|
*/
|
|
985
|
-
connect(roomUrl, token
|
|
938
|
+
connect(roomUrl, token) {
|
|
986
939
|
return __awaiter(this, void 0, void 0, function* () {
|
|
987
940
|
if (this.callObject) {
|
|
988
941
|
yield this.disconnect();
|
|
989
942
|
}
|
|
990
943
|
try {
|
|
991
|
-
|
|
992
|
-
const stream =
|
|
993
|
-
? existingStream
|
|
994
|
-
: yield navigator.mediaDevices.getUserMedia({ audio: true });
|
|
944
|
+
// Get mic stream for both Daily and waveform (single capture)
|
|
945
|
+
const stream = yield navigator.mediaDevices.getUserMedia({ audio: true });
|
|
995
946
|
const audioTrack = stream.getAudioTracks()[0];
|
|
996
947
|
if (!audioTrack) {
|
|
997
948
|
stream.getTracks().forEach((t) => t.stop());
|
|
@@ -1123,7 +1074,7 @@ class DailyVoiceClientService {
|
|
|
1123
1074
|
}
|
|
1124
1075
|
/**
|
|
1125
1076
|
* Monitor remote audio track energy via AnalyserNode.
|
|
1126
|
-
* Polls at ~
|
|
1077
|
+
* Polls at ~60fps and flips speakingSubject based on actual audio energy.
|
|
1127
1078
|
*/
|
|
1128
1079
|
monitorRemoteAudio(track) {
|
|
1129
1080
|
this.stopRemoteAudioMonitor();
|
|
@@ -1137,17 +1088,11 @@ class DailyVoiceClientService {
|
|
|
1137
1088
|
const dataArray = new Uint8Array(analyser.frequencyBinCount);
|
|
1138
1089
|
const THRESHOLD = 5;
|
|
1139
1090
|
const SILENCE_MS = 1500;
|
|
1140
|
-
const POLL_MS = 100;
|
|
1141
1091
|
let lastSoundTime = 0;
|
|
1142
1092
|
let isSpeaking = false;
|
|
1143
|
-
|
|
1144
|
-
if (!this.remoteAudioContext)
|
|
1145
|
-
if (this.remoteSpeakingPollId) {
|
|
1146
|
-
clearInterval(this.remoteSpeakingPollId);
|
|
1147
|
-
this.remoteSpeakingPollId = null;
|
|
1148
|
-
}
|
|
1093
|
+
const poll = () => {
|
|
1094
|
+
if (!this.remoteAudioContext)
|
|
1149
1095
|
return;
|
|
1150
|
-
}
|
|
1151
1096
|
analyser.getByteFrequencyData(dataArray);
|
|
1152
1097
|
let sum = 0;
|
|
1153
1098
|
for (let i = 0; i < dataArray.length; i++) {
|
|
@@ -1171,16 +1116,18 @@ class DailyVoiceClientService {
|
|
|
1171
1116
|
console.log(`[VoiceDebug] Bot audio silence detected (speaking=false) — ${new Date().toISOString()}`);
|
|
1172
1117
|
this.ngZone.run(() => this.speakingSubject.next(false));
|
|
1173
1118
|
}
|
|
1174
|
-
|
|
1119
|
+
this.remoteSpeakingRAF = requestAnimationFrame(poll);
|
|
1120
|
+
};
|
|
1121
|
+
this.remoteSpeakingRAF = requestAnimationFrame(poll);
|
|
1175
1122
|
}
|
|
1176
1123
|
catch (err) {
|
|
1177
1124
|
console.warn('DailyVoiceClient: failed to create remote audio monitor', err);
|
|
1178
1125
|
}
|
|
1179
1126
|
}
|
|
1180
1127
|
stopRemoteAudioMonitor() {
|
|
1181
|
-
if (this.
|
|
1182
|
-
|
|
1183
|
-
this.
|
|
1128
|
+
if (this.remoteSpeakingRAF) {
|
|
1129
|
+
cancelAnimationFrame(this.remoteSpeakingRAF);
|
|
1130
|
+
this.remoteSpeakingRAF = null;
|
|
1184
1131
|
}
|
|
1185
1132
|
if (this.remoteAudioContext) {
|
|
1186
1133
|
this.remoteAudioContext.close().catch(() => { });
|
|
@@ -1279,8 +1226,6 @@ class VoiceAgentService {
|
|
|
1279
1226
|
this.callStartTime = 0;
|
|
1280
1227
|
this.durationInterval = null;
|
|
1281
1228
|
this.subscriptions = new Subscription();
|
|
1282
|
-
/** Per-call only; cleared on disconnect / reset / new room so handlers do not stack. */
|
|
1283
|
-
this.callSubscriptions = new Subscription();
|
|
1284
1229
|
this.destroy$ = new Subject();
|
|
1285
1230
|
this.callState$ = this.callStateSubject.asObservable();
|
|
1286
1231
|
this.statusText$ = this.statusTextSubject.asObservable();
|
|
@@ -1292,15 +1237,6 @@ class VoiceAgentService {
|
|
|
1292
1237
|
this.botTranscript$ = this.botTranscriptSubject.asObservable();
|
|
1293
1238
|
// Waveform visualization only - do NOT use for speaking state
|
|
1294
1239
|
this.subscriptions.add(this.audioAnalyzer.audioLevels$.subscribe((levels) => this.audioLevelsSubject.next(levels)));
|
|
1295
|
-
// Transcripts: single subscription for service lifetime (avoid stacking on each connect()).
|
|
1296
|
-
// WebSocket is disconnected between calls; no replay — new subscribers (setupVoiceTranscripts)
|
|
1297
|
-
// only receive messages from the new WS after connect.
|
|
1298
|
-
this.subscriptions.add(this.wsClient.userTranscript$
|
|
1299
|
-
.pipe(takeUntil(this.destroy$))
|
|
1300
|
-
.subscribe((t) => this.userTranscriptSubject.next(t)));
|
|
1301
|
-
this.subscriptions.add(this.wsClient.botTranscript$
|
|
1302
|
-
.pipe(takeUntil(this.destroy$))
|
|
1303
|
-
.subscribe((t) => this.botTranscriptSubject.next(t)));
|
|
1304
1240
|
}
|
|
1305
1241
|
ngOnDestroy() {
|
|
1306
1242
|
this.destroy$.next();
|
|
@@ -1311,8 +1247,6 @@ class VoiceAgentService {
|
|
|
1311
1247
|
resetToIdle() {
|
|
1312
1248
|
if (this.callStateSubject.value === 'idle')
|
|
1313
1249
|
return;
|
|
1314
|
-
this.callSubscriptions.unsubscribe();
|
|
1315
|
-
this.callSubscriptions = new Subscription();
|
|
1316
1250
|
this.stopDurationTimer();
|
|
1317
1251
|
this.audioAnalyzer.stop();
|
|
1318
1252
|
this.wsClient.disconnect();
|
|
@@ -1322,8 +1256,7 @@ class VoiceAgentService {
|
|
|
1322
1256
|
this.statusTextSubject.next('');
|
|
1323
1257
|
this.durationSubject.next('0:00');
|
|
1324
1258
|
}
|
|
1325
|
-
connect(apiUrl, token, botId, conversationId, apiKey, eventToken, eventId, eventUrl, domainAuthority, usersApiUrl
|
|
1326
|
-
var _a;
|
|
1259
|
+
connect(apiUrl, token, botId, conversationId, apiKey, eventToken, eventId, eventUrl, domainAuthority, usersApiUrl) {
|
|
1327
1260
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1328
1261
|
if (this.callStateSubject.value !== 'idle') {
|
|
1329
1262
|
console.warn('Call already in progress');
|
|
@@ -1332,41 +1265,26 @@ class VoiceAgentService {
|
|
|
1332
1265
|
try {
|
|
1333
1266
|
this.callStateSubject.next('connecting');
|
|
1334
1267
|
this.statusTextSubject.next('Connecting...');
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1268
|
+
let accessToken = token;
|
|
1269
|
+
// Align with chat drawer token handling: always delegate to
|
|
1270
|
+
// PlatformTokenRefreshService when we have a usersApiUrl, so it can
|
|
1271
|
+
// fall back to stored tokens even if the caller passed an empty token.
|
|
1272
|
+
if (usersApiUrl && isPlatformBrowser(this.platformId)) {
|
|
1273
|
+
try {
|
|
1274
|
+
const ensured = yield this.platformTokenRefresh
|
|
1275
|
+
.ensureValidAccessToken(token, usersApiUrl)
|
|
1276
|
+
.pipe(take(1))
|
|
1277
|
+
.toPromise();
|
|
1278
|
+
if (ensured === null || ensured === void 0 ? void 0 : ensured.accessToken) {
|
|
1279
|
+
accessToken = ensured.accessToken;
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
catch (e) {
|
|
1342
1283
|
console.warn('[HiveGpt Voice] Token refresh before connect failed', e);
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
const
|
|
1347
|
-
const baseUrl = apiUrl.replace(/\/$/, '');
|
|
1348
|
-
return {
|
|
1349
|
-
postUrl: `${baseUrl}/ai/ask-voice`,
|
|
1350
|
-
body: JSON.stringify({
|
|
1351
|
-
bot_id: botId,
|
|
1352
|
-
conversation_id: conversationId,
|
|
1353
|
-
voice: 'alloy',
|
|
1354
|
-
}),
|
|
1355
|
-
};
|
|
1356
|
-
});
|
|
1357
|
-
const micPromise = (existingMicStream === null || existingMicStream === void 0 ? void 0 : existingMicStream.getAudioTracks().some((t) => t.readyState === 'live'))
|
|
1358
|
-
? Promise.resolve(existingMicStream)
|
|
1359
|
-
: isPlatformBrowser(this.platformId) &&
|
|
1360
|
-
((_a = navigator.mediaDevices) === null || _a === void 0 ? void 0 : _a.getUserMedia)
|
|
1361
|
-
? navigator.mediaDevices
|
|
1362
|
-
.getUserMedia({ audio: true })
|
|
1363
|
-
.catch(() => undefined)
|
|
1364
|
-
: Promise.resolve(undefined);
|
|
1365
|
-
const [accessToken, { postUrl, body }, micStream] = yield Promise.all([
|
|
1366
|
-
tokenPromise,
|
|
1367
|
-
prepPromise,
|
|
1368
|
-
micPromise,
|
|
1369
|
-
]);
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
const baseUrl = apiUrl.replace(/\/$/, '');
|
|
1287
|
+
const postUrl = `${baseUrl}/ai/ask-voice`;
|
|
1370
1288
|
const headers = {
|
|
1371
1289
|
'Content-Type': 'application/json',
|
|
1372
1290
|
Authorization: `Bearer ${accessToken}`,
|
|
@@ -1382,7 +1300,11 @@ class VoiceAgentService {
|
|
|
1382
1300
|
const res = yield fetch(postUrl, {
|
|
1383
1301
|
method: 'POST',
|
|
1384
1302
|
headers,
|
|
1385
|
-
body
|
|
1303
|
+
body: JSON.stringify({
|
|
1304
|
+
bot_id: botId,
|
|
1305
|
+
conversation_id: conversationId,
|
|
1306
|
+
voice: 'alloy',
|
|
1307
|
+
}),
|
|
1386
1308
|
});
|
|
1387
1309
|
if (!res.ok) {
|
|
1388
1310
|
throw new Error(`HTTP ${res.status}`);
|
|
@@ -1397,7 +1319,7 @@ class VoiceAgentService {
|
|
|
1397
1319
|
.pipe(take(1), takeUntil(this.destroy$))
|
|
1398
1320
|
.subscribe((roomUrl) => __awaiter(this, void 0, void 0, function* () {
|
|
1399
1321
|
try {
|
|
1400
|
-
yield this.onRoomCreated(roomUrl
|
|
1322
|
+
yield this.onRoomCreated(roomUrl);
|
|
1401
1323
|
}
|
|
1402
1324
|
catch (err) {
|
|
1403
1325
|
console.error('Daily join failed:', err);
|
|
@@ -1407,8 +1329,15 @@ class VoiceAgentService {
|
|
|
1407
1329
|
throw err;
|
|
1408
1330
|
}
|
|
1409
1331
|
}));
|
|
1332
|
+
// Forward transcripts from WebSocket
|
|
1333
|
+
this.subscriptions.add(this.wsClient.userTranscript$
|
|
1334
|
+
.pipe(takeUntil(this.destroy$))
|
|
1335
|
+
.subscribe((t) => this.userTranscriptSubject.next(t)));
|
|
1336
|
+
this.subscriptions.add(this.wsClient.botTranscript$
|
|
1337
|
+
.pipe(takeUntil(this.destroy$))
|
|
1338
|
+
.subscribe((t) => this.botTranscriptSubject.next(t)));
|
|
1410
1339
|
// Connect signaling WebSocket (no audio over WS)
|
|
1411
|
-
|
|
1340
|
+
this.wsClient.connect(wsUrl);
|
|
1412
1341
|
}
|
|
1413
1342
|
catch (error) {
|
|
1414
1343
|
console.error('Error connecting voice agent:', error);
|
|
@@ -1419,19 +1348,18 @@ class VoiceAgentService {
|
|
|
1419
1348
|
}
|
|
1420
1349
|
});
|
|
1421
1350
|
}
|
|
1422
|
-
onRoomCreated(roomUrl
|
|
1351
|
+
onRoomCreated(roomUrl) {
|
|
1423
1352
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1424
|
-
|
|
1425
|
-
this.
|
|
1426
|
-
this.callSubscriptions = new Subscription();
|
|
1353
|
+
// Connect Daily.js for WebRTC audio
|
|
1354
|
+
yield this.dailyClient.connect(roomUrl);
|
|
1427
1355
|
// Waveform: use local mic stream from Daily client
|
|
1428
|
-
this.
|
|
1356
|
+
this.dailyClient.localStream$
|
|
1429
1357
|
.pipe(filter((s) => s != null), take(1))
|
|
1430
1358
|
.subscribe((stream) => {
|
|
1431
1359
|
this.audioAnalyzer.start(stream);
|
|
1432
|
-
})
|
|
1433
|
-
this.
|
|
1434
|
-
this.
|
|
1360
|
+
});
|
|
1361
|
+
this.subscriptions.add(this.dailyClient.userSpeaking$.subscribe((s) => this.isUserSpeakingSubject.next(s)));
|
|
1362
|
+
this.subscriptions.add(combineLatest([
|
|
1435
1363
|
this.dailyClient.speaking$,
|
|
1436
1364
|
this.dailyClient.userSpeaking$,
|
|
1437
1365
|
]).subscribe(([bot, user]) => {
|
|
@@ -1451,19 +1379,16 @@ class VoiceAgentService {
|
|
|
1451
1379
|
else if (bot) {
|
|
1452
1380
|
this.callStateSubject.next('talking');
|
|
1453
1381
|
}
|
|
1454
|
-
else {
|
|
1455
|
-
|
|
1456
|
-
this.callStateSubject.next('listening');
|
|
1382
|
+
else if (current === 'talking' || current === 'listening') {
|
|
1383
|
+
this.callStateSubject.next('connected');
|
|
1457
1384
|
}
|
|
1458
1385
|
}));
|
|
1459
|
-
this.
|
|
1386
|
+
this.subscriptions.add(this.dailyClient.micMuted$.subscribe((muted) => this.isMicMutedSubject.next(muted)));
|
|
1460
1387
|
this.statusTextSubject.next('Connecting...');
|
|
1461
1388
|
});
|
|
1462
1389
|
}
|
|
1463
1390
|
disconnect() {
|
|
1464
1391
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1465
|
-
this.callSubscriptions.unsubscribe();
|
|
1466
|
-
this.callSubscriptions = new Subscription();
|
|
1467
1392
|
this.stopDurationTimer();
|
|
1468
1393
|
this.audioAnalyzer.stop();
|
|
1469
1394
|
// Daily first, then WebSocket
|
|
@@ -1514,11 +1439,10 @@ const VOICE_MODAL_CONFIG = new InjectionToken('VOICE_MODAL_CONFIG');
|
|
|
1514
1439
|
const VOICE_MODAL_CLOSE_CALLBACK = new InjectionToken('VOICE_MODAL_CLOSE_CALLBACK');
|
|
1515
1440
|
|
|
1516
1441
|
class VoiceAgentModalComponent {
|
|
1517
|
-
constructor(voiceAgentService, audioAnalyzer, injector
|
|
1442
|
+
constructor(voiceAgentService, audioAnalyzer, injector) {
|
|
1518
1443
|
this.voiceAgentService = voiceAgentService;
|
|
1519
1444
|
this.audioAnalyzer = audioAnalyzer;
|
|
1520
1445
|
this.injector = injector;
|
|
1521
|
-
this.platformId = platformId;
|
|
1522
1446
|
this.close = new EventEmitter();
|
|
1523
1447
|
this.apiKey = '';
|
|
1524
1448
|
this.eventToken = '';
|
|
@@ -1530,8 +1454,6 @@ class VoiceAgentModalComponent {
|
|
|
1530
1454
|
this.usersApiUrl = '';
|
|
1531
1455
|
this.injectedConfig = null;
|
|
1532
1456
|
this.onCloseCallback = null;
|
|
1533
|
-
/** Held until destroy; passed to Daily so we do not stop/re-acquire (avoids extra prompts on some browsers). */
|
|
1534
|
-
this.warmMicStream = null;
|
|
1535
1457
|
/** Hardcoded voice agent avatar (Nia). */
|
|
1536
1458
|
this.displayAvatarUrl = 'https://www.jotform.com/uploads/mehmetkarakasli/form_files/1564593667676a8e85f23758.86945537_icon.png';
|
|
1537
1459
|
this.callState = 'idle';
|
|
@@ -1547,83 +1469,58 @@ class VoiceAgentModalComponent {
|
|
|
1547
1469
|
this.isConnecting = false;
|
|
1548
1470
|
}
|
|
1549
1471
|
ngOnInit() {
|
|
1550
|
-
void this.bootstrap();
|
|
1551
|
-
}
|
|
1552
|
-
bootstrap() {
|
|
1553
1472
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1473
|
+
// When opened via Overlay, config is provided by injection
|
|
1474
|
+
this.injectedConfig = this.injector.get(VOICE_MODAL_CONFIG, null);
|
|
1475
|
+
this.onCloseCallback = this.injector.get(VOICE_MODAL_CLOSE_CALLBACK, null);
|
|
1476
|
+
if (this.injectedConfig) {
|
|
1477
|
+
this.apiUrl = this.injectedConfig.apiUrl;
|
|
1478
|
+
this.token = this.injectedConfig.token;
|
|
1479
|
+
this.botId = this.injectedConfig.botId;
|
|
1480
|
+
this.conversationId = this.injectedConfig.conversationId;
|
|
1481
|
+
this.apiKey = (_a = this.injectedConfig.apiKey) !== null && _a !== void 0 ? _a : '';
|
|
1482
|
+
this.eventToken = (_b = this.injectedConfig.eventToken) !== null && _b !== void 0 ? _b : '';
|
|
1483
|
+
this.eventId = (_c = this.injectedConfig.eventId) !== null && _c !== void 0 ? _c : '';
|
|
1484
|
+
this.eventUrl = (_d = this.injectedConfig.eventUrl) !== null && _d !== void 0 ? _d : '';
|
|
1485
|
+
this.domainAuthority = (_e = this.injectedConfig.domainAuthority) !== null && _e !== void 0 ? _e : 'prod-lite';
|
|
1486
|
+
this.agentName = (_f = this.injectedConfig.agentName) !== null && _f !== void 0 ? _f : this.agentName;
|
|
1487
|
+
this.agentRole = (_g = this.injectedConfig.agentRole) !== null && _g !== void 0 ? _g : this.agentRole;
|
|
1488
|
+
this.agentAvatar = this.injectedConfig.agentAvatar;
|
|
1489
|
+
this.usersApiUrl = (_h = this.injectedConfig.usersApiUrl) !== null && _h !== void 0 ? _h : this.usersApiUrl;
|
|
1490
|
+
}
|
|
1491
|
+
// Subscribe to observables
|
|
1492
|
+
this.subscriptions.push(this.voiceAgentService.callState$.subscribe(state => {
|
|
1493
|
+
this.callState = state;
|
|
1494
|
+
this.isSpeaking = state === 'talking';
|
|
1495
|
+
if (state === 'listening' || state === 'talking') {
|
|
1496
|
+
this.hasLeftConnectedOnce = true;
|
|
1571
1497
|
}
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
this.
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
this.
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
this.
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
}));
|
|
1595
|
-
this.subscriptions.push(this.voiceAgentService.audioLevels$.subscribe(levels => {
|
|
1596
|
-
this.audioLevels = levels;
|
|
1597
|
-
}));
|
|
1598
|
-
this.voiceAgentService.resetToIdle();
|
|
1599
|
-
yield this.startCall();
|
|
1600
|
-
});
|
|
1498
|
+
if (state === 'idle' || state === 'ended') {
|
|
1499
|
+
this.hasLeftConnectedOnce = false;
|
|
1500
|
+
}
|
|
1501
|
+
}));
|
|
1502
|
+
this.subscriptions.push(this.voiceAgentService.statusText$.subscribe(text => {
|
|
1503
|
+
this.statusText = text;
|
|
1504
|
+
}));
|
|
1505
|
+
this.subscriptions.push(this.voiceAgentService.duration$.subscribe(duration => {
|
|
1506
|
+
this.duration = duration;
|
|
1507
|
+
}));
|
|
1508
|
+
this.subscriptions.push(this.voiceAgentService.isMicMuted$.subscribe(muted => {
|
|
1509
|
+
this.isMicMuted = muted;
|
|
1510
|
+
}));
|
|
1511
|
+
this.subscriptions.push(this.voiceAgentService.isUserSpeaking$.subscribe(speaking => {
|
|
1512
|
+
this.isUserSpeaking = speaking;
|
|
1513
|
+
}));
|
|
1514
|
+
this.subscriptions.push(this.voiceAgentService.audioLevels$.subscribe(levels => {
|
|
1515
|
+
this.audioLevels = levels;
|
|
1516
|
+
}));
|
|
1517
|
+
// Modal opens in idle state, then immediately starts connecting.
|
|
1518
|
+
this.voiceAgentService.resetToIdle();
|
|
1519
|
+
void this.startCall();
|
|
1601
1520
|
}
|
|
1602
1521
|
ngOnDestroy() {
|
|
1603
|
-
this.subscriptions.forEach(
|
|
1604
|
-
|
|
1605
|
-
var _a;
|
|
1606
|
-
(_a = this.warmMicStream) === null || _a === void 0 ? void 0 : _a.getTracks().forEach((t) => t.stop());
|
|
1607
|
-
this.warmMicStream = null;
|
|
1608
|
-
});
|
|
1609
|
-
}
|
|
1610
|
-
/** Ensures a live mic stream for this call (re-acquire after Daily stops tracks on hang-up). */
|
|
1611
|
-
ensureMicForCall() {
|
|
1612
|
-
var _a;
|
|
1613
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
1614
|
-
if (!isPlatformBrowser(this.platformId))
|
|
1615
|
-
return undefined;
|
|
1616
|
-
if ((_a = this.warmMicStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks().some((t) => t.readyState === 'live')) {
|
|
1617
|
-
return this.warmMicStream;
|
|
1618
|
-
}
|
|
1619
|
-
try {
|
|
1620
|
-
this.warmMicStream = yield navigator.mediaDevices.getUserMedia({ audio: true });
|
|
1621
|
-
return this.warmMicStream;
|
|
1622
|
-
}
|
|
1623
|
-
catch (_b) {
|
|
1624
|
-
return undefined;
|
|
1625
|
-
}
|
|
1626
|
-
});
|
|
1522
|
+
this.subscriptions.forEach(sub => sub.unsubscribe());
|
|
1523
|
+
this.disconnect();
|
|
1627
1524
|
}
|
|
1628
1525
|
startCall() {
|
|
1629
1526
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -1631,8 +1528,7 @@ class VoiceAgentModalComponent {
|
|
|
1631
1528
|
return;
|
|
1632
1529
|
this.isConnecting = true;
|
|
1633
1530
|
try {
|
|
1634
|
-
|
|
1635
|
-
yield this.voiceAgentService.connect(this.apiUrl, this.token, this.botId, this.conversationId, this.apiKey, this.eventToken, this.eventId, this.eventUrl, this.domainAuthority, this.usersApiUrl || undefined, mic);
|
|
1531
|
+
yield this.voiceAgentService.connect(this.apiUrl, this.token, this.botId, this.conversationId, this.apiKey, this.eventToken, this.eventId, this.eventUrl, this.domainAuthority, this.usersApiUrl || undefined);
|
|
1636
1532
|
}
|
|
1637
1533
|
catch (error) {
|
|
1638
1534
|
console.error('Failed to connect voice agent:', error);
|
|
@@ -1671,7 +1567,7 @@ class VoiceAgentModalComponent {
|
|
|
1671
1567
|
/** Call Again: reset to idle then start a new call. */
|
|
1672
1568
|
callAgain() {
|
|
1673
1569
|
this.voiceAgentService.resetToIdle();
|
|
1674
|
-
|
|
1570
|
+
this.startCall();
|
|
1675
1571
|
}
|
|
1676
1572
|
/** Back to Chat: close modal and disconnect. */
|
|
1677
1573
|
backToChat() {
|
|
@@ -1698,8 +1594,7 @@ VoiceAgentModalComponent.decorators = [
|
|
|
1698
1594
|
VoiceAgentModalComponent.ctorParameters = () => [
|
|
1699
1595
|
{ type: VoiceAgentService },
|
|
1700
1596
|
{ type: AudioAnalyzerService },
|
|
1701
|
-
{ type: Injector }
|
|
1702
|
-
{ type: Object, decorators: [{ type: Inject, args: [PLATFORM_ID,] }] }
|
|
1597
|
+
{ type: Injector }
|
|
1703
1598
|
];
|
|
1704
1599
|
VoiceAgentModalComponent.propDecorators = {
|
|
1705
1600
|
close: [{ type: Output }],
|