@hivegpt/hiveai-angular 0.0.582 → 0.0.584
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 +209 -382
- 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/services/voice-agent.service.js +174 -165
- package/esm2015/lib/components/voice-agent/voice-agent.module.js +3 -5
- package/fesm2015/hivegpt-hiveai-angular.js +173 -323
- 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/services/voice-agent.service.d.ts +24 -15
- 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 -163
- package/lib/components/voice-agent/services/websocket-voice-client.service.d.ts +0 -52
- package/lib/components/voice-agent/services/websocket-voice-client.service.d.ts.map +0 -1
|
@@ -5,14 +5,16 @@ import { HttpClient, HttpHeaders } from '@angular/common/http';
|
|
|
5
5
|
import * as i0 from '@angular/core';
|
|
6
6
|
import { Injectable, InjectionToken, Inject, PLATFORM_ID, Optional, NgZone, EventEmitter, Component, Injector, Output, Input, ElementRef, ChangeDetectionStrategy, ChangeDetectorRef, Renderer2, ViewContainerRef, ViewChild, ViewChildren, NgModule, Pipe } from '@angular/core';
|
|
7
7
|
import { DomSanitizer } from '@angular/platform-browser';
|
|
8
|
-
import { BehaviorSubject, of, throwError, Subject, Subscription
|
|
9
|
-
import { switchMap, catchError, filter, take, map,
|
|
8
|
+
import { BehaviorSubject, of, throwError, Subject, Subscription } from 'rxjs';
|
|
9
|
+
import { switchMap, catchError, filter, take, map, tap } from 'rxjs/operators';
|
|
10
10
|
import { isPlatformBrowser, CommonModule, DOCUMENT } from '@angular/common';
|
|
11
11
|
import { Socket } from 'ngx-socket-io';
|
|
12
12
|
import { Validators, FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
13
13
|
import * as SpeechSDK from 'microsoft-cognitiveservices-speech-sdk';
|
|
14
14
|
import * as marked from 'marked';
|
|
15
15
|
import { __awaiter } from 'tslib';
|
|
16
|
+
import { PipecatClient, RTVIEvent } from '@pipecat-ai/client-js';
|
|
17
|
+
import { WebSocketTransport } from '@pipecat-ai/websocket-transport';
|
|
16
18
|
import { MatIconModule } from '@angular/material/icon';
|
|
17
19
|
import { MatSidenavModule } from '@angular/material/sidenav';
|
|
18
20
|
import { QuillModule } from 'ngx-quill';
|
|
@@ -805,177 +807,23 @@ AudioAnalyzerService.decorators = [
|
|
|
805
807
|
];
|
|
806
808
|
|
|
807
809
|
/**
|
|
808
|
-
*
|
|
809
|
-
* CRITICAL: Uses native WebSocket only. NO Socket.IO, NO ngx-socket-io.
|
|
810
|
+
* Voice agent orchestrator using the official PipecatClient SDK.
|
|
810
811
|
*
|
|
811
|
-
*
|
|
812
|
-
*
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
/** True when {@link disconnect} initiated the close (not counted as remote close). */
|
|
819
|
-
this.closeInitiatedByClient = false;
|
|
820
|
-
this.openedSubject = new Subject();
|
|
821
|
-
this.remoteCloseSubject = new Subject();
|
|
822
|
-
this.userTranscriptSubject = new Subject();
|
|
823
|
-
this.botTranscriptSubject = new Subject();
|
|
824
|
-
this.assistantSpeakingSubject = new Subject();
|
|
825
|
-
this.serverUserSpeakingSubject = new Subject();
|
|
826
|
-
/** Fires once each time the WebSocket reaches OPEN. */
|
|
827
|
-
this.opened$ = this.openedSubject.asObservable();
|
|
828
|
-
/** Fires when the socket closes without a client-initiated {@link disconnect}. */
|
|
829
|
-
this.remoteClose$ = this.remoteCloseSubject.asObservable();
|
|
830
|
-
this.userTranscript$ = this.userTranscriptSubject.asObservable();
|
|
831
|
-
this.botTranscript$ = this.botTranscriptSubject.asObservable();
|
|
832
|
-
/** Assistant/bot speaking, when the server sends explicit events (see {@link handleJsonMessage}). */
|
|
833
|
-
this.assistantSpeaking$ = this.assistantSpeakingSubject.asObservable();
|
|
834
|
-
/** User speaking from server-side VAD, if provided. */
|
|
835
|
-
this.serverUserSpeaking$ = this.serverUserSpeakingSubject.asObservable();
|
|
836
|
-
}
|
|
837
|
-
connect(wsUrl) {
|
|
838
|
-
var _a;
|
|
839
|
-
if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN) {
|
|
840
|
-
return;
|
|
841
|
-
}
|
|
842
|
-
if (this.ws) {
|
|
843
|
-
this.closeInitiatedByClient = true;
|
|
844
|
-
this.ws.close();
|
|
845
|
-
}
|
|
846
|
-
try {
|
|
847
|
-
const socket = new WebSocket(wsUrl);
|
|
848
|
-
this.ws = socket;
|
|
849
|
-
socket.onopen = () => {
|
|
850
|
-
if (this.ws !== socket)
|
|
851
|
-
return;
|
|
852
|
-
this.ngZone.run(() => this.openedSubject.next());
|
|
853
|
-
};
|
|
854
|
-
socket.onmessage = (event) => {
|
|
855
|
-
if (this.ws !== socket)
|
|
856
|
-
return;
|
|
857
|
-
if (typeof event.data !== 'string') {
|
|
858
|
-
return;
|
|
859
|
-
}
|
|
860
|
-
try {
|
|
861
|
-
const msg = JSON.parse(event.data);
|
|
862
|
-
this.ngZone.run(() => this.handleJsonMessage(msg));
|
|
863
|
-
}
|
|
864
|
-
catch (_a) {
|
|
865
|
-
// Ignore non-JSON
|
|
866
|
-
}
|
|
867
|
-
};
|
|
868
|
-
socket.onerror = () => {
|
|
869
|
-
this.ngZone.run(() => {
|
|
870
|
-
if (this.ws === socket && socket.readyState !== WebSocket.CLOSED) {
|
|
871
|
-
socket.close();
|
|
872
|
-
}
|
|
873
|
-
});
|
|
874
|
-
};
|
|
875
|
-
socket.onclose = () => {
|
|
876
|
-
if (this.ws === socket) {
|
|
877
|
-
this.ws = null;
|
|
878
|
-
}
|
|
879
|
-
const client = this.closeInitiatedByClient;
|
|
880
|
-
this.closeInitiatedByClient = false;
|
|
881
|
-
if (!client) {
|
|
882
|
-
this.ngZone.run(() => this.remoteCloseSubject.next());
|
|
883
|
-
}
|
|
884
|
-
};
|
|
885
|
-
}
|
|
886
|
-
catch (err) {
|
|
887
|
-
console.error('WebSocketVoiceClient: connect failed', err);
|
|
888
|
-
this.ws = null;
|
|
889
|
-
throw err;
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
handleJsonMessage(msg) {
|
|
893
|
-
const type = msg.type;
|
|
894
|
-
const typeStr = typeof type === 'string' ? type : '';
|
|
895
|
-
if (typeStr === 'session_ready' || typeStr === 'connected' || typeStr === 'voice_session_started') {
|
|
896
|
-
return;
|
|
897
|
-
}
|
|
898
|
-
if (typeStr === 'assistant_speaking' ||
|
|
899
|
-
typeStr === 'bot_speaking') {
|
|
900
|
-
if (msg.active === true || msg.speaking === true) {
|
|
901
|
-
this.assistantSpeakingSubject.next(true);
|
|
902
|
-
}
|
|
903
|
-
else if (msg.active === false || msg.speaking === false) {
|
|
904
|
-
this.assistantSpeakingSubject.next(false);
|
|
905
|
-
}
|
|
906
|
-
return;
|
|
907
|
-
}
|
|
908
|
-
if (typeStr === 'user_speaking') {
|
|
909
|
-
if (msg.active === true || msg.speaking === true) {
|
|
910
|
-
this.serverUserSpeakingSubject.next(true);
|
|
911
|
-
}
|
|
912
|
-
else if (msg.active === false || msg.speaking === false) {
|
|
913
|
-
this.serverUserSpeakingSubject.next(false);
|
|
914
|
-
}
|
|
915
|
-
return;
|
|
916
|
-
}
|
|
917
|
-
if (typeStr === 'input_audio_buffer.speech_started') {
|
|
918
|
-
this.serverUserSpeakingSubject.next(true);
|
|
919
|
-
return;
|
|
920
|
-
}
|
|
921
|
-
if (typeStr === 'input_audio_buffer.speech_stopped') {
|
|
922
|
-
this.serverUserSpeakingSubject.next(false);
|
|
923
|
-
return;
|
|
924
|
-
}
|
|
925
|
-
if (typeStr === 'response.audio.delta') {
|
|
926
|
-
this.assistantSpeakingSubject.next(true);
|
|
927
|
-
return;
|
|
928
|
-
}
|
|
929
|
-
if (typeStr === 'response.audio.done' ||
|
|
930
|
-
typeStr === 'response.output_audio.done') {
|
|
931
|
-
this.assistantSpeakingSubject.next(false);
|
|
932
|
-
return;
|
|
933
|
-
}
|
|
934
|
-
if (typeStr === 'user_transcript' && typeof msg.text === 'string') {
|
|
935
|
-
this.userTranscriptSubject.next({
|
|
936
|
-
text: msg.text,
|
|
937
|
-
final: msg.final === true,
|
|
938
|
-
});
|
|
939
|
-
return;
|
|
940
|
-
}
|
|
941
|
-
if (typeStr === 'bot_transcript' && typeof msg.text === 'string') {
|
|
942
|
-
this.botTranscriptSubject.next(msg.text);
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
disconnect() {
|
|
946
|
-
if (!this.ws) {
|
|
947
|
-
return;
|
|
948
|
-
}
|
|
949
|
-
this.closeInitiatedByClient = true;
|
|
950
|
-
this.ws.close();
|
|
951
|
-
}
|
|
952
|
-
get isConnected() {
|
|
953
|
-
var _a;
|
|
954
|
-
return ((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN;
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
WebSocketVoiceClientService.ɵprov = i0.ɵɵdefineInjectable({ factory: function WebSocketVoiceClientService_Factory() { return new WebSocketVoiceClientService(i0.ɵɵinject(i0.NgZone)); }, token: WebSocketVoiceClientService, providedIn: "root" });
|
|
958
|
-
WebSocketVoiceClientService.decorators = [
|
|
959
|
-
{ type: Injectable, args: [{
|
|
960
|
-
providedIn: 'root',
|
|
961
|
-
},] }
|
|
962
|
-
];
|
|
963
|
-
WebSocketVoiceClientService.ctorParameters = () => [
|
|
964
|
-
{ type: NgZone }
|
|
965
|
-
];
|
|
966
|
-
|
|
967
|
-
/**
|
|
968
|
-
* Voice agent orchestrator: single WebSocket (`ws_url` from POST /ai/ask-voice-socket)
|
|
969
|
-
* for session events, transcripts, and optional speaking hints; local mic for capture
|
|
970
|
-
* and waveform only (no Daily/WebRTC room).
|
|
812
|
+
* Audio flow (mirrors the React reference implementation):
|
|
813
|
+
* - Local mic: acquired by PipecatClient.initDevices(); local track fed to
|
|
814
|
+
* AudioAnalyzerService for waveform visualisation.
|
|
815
|
+
* - Bot audio: received as a MediaStreamTrack via RTVIEvent.TrackStarted,
|
|
816
|
+
* played through a hidden <audio> element.
|
|
817
|
+
* - All binary protobuf framing / RTVI protocol handled by
|
|
818
|
+
* @pipecat-ai/client-js + @pipecat-ai/websocket-transport.
|
|
971
819
|
*/
|
|
972
820
|
class VoiceAgentService {
|
|
973
|
-
constructor(audioAnalyzer,
|
|
821
|
+
constructor(audioAnalyzer, platformTokenRefresh, ngZone,
|
|
974
822
|
/** `Object` not `object` — ngc metadata collection rejects the `object` type in DI params. */
|
|
975
823
|
platformId) {
|
|
976
824
|
this.audioAnalyzer = audioAnalyzer;
|
|
977
|
-
this.wsClient = wsClient;
|
|
978
825
|
this.platformTokenRefresh = platformTokenRefresh;
|
|
826
|
+
this.ngZone = ngZone;
|
|
979
827
|
this.platformId = platformId;
|
|
980
828
|
this.callStateSubject = new BehaviorSubject('idle');
|
|
981
829
|
this.statusTextSubject = new BehaviorSubject('');
|
|
@@ -987,8 +835,8 @@ class VoiceAgentService {
|
|
|
987
835
|
this.botTranscriptSubject = new Subject();
|
|
988
836
|
this.callStartTime = 0;
|
|
989
837
|
this.durationInterval = null;
|
|
990
|
-
this.
|
|
991
|
-
this.
|
|
838
|
+
this.pcClient = null;
|
|
839
|
+
this.botAudioElement = null;
|
|
992
840
|
this.subscriptions = new Subscription();
|
|
993
841
|
this.destroy$ = new Subject();
|
|
994
842
|
this.callState$ = this.callStateSubject.asObservable();
|
|
@@ -1000,25 +848,17 @@ class VoiceAgentService {
|
|
|
1000
848
|
this.userTranscript$ = this.userTranscriptSubject.asObservable();
|
|
1001
849
|
this.botTranscript$ = this.botTranscriptSubject.asObservable();
|
|
1002
850
|
this.subscriptions.add(this.audioAnalyzer.audioLevels$.subscribe((levels) => this.audioLevelsSubject.next(levels)));
|
|
1003
|
-
this.subscriptions.add(this.wsClient.remoteClose$
|
|
1004
|
-
.pipe(takeUntil(this.destroy$))
|
|
1005
|
-
.subscribe(() => void this.handleRemoteClose()));
|
|
1006
851
|
}
|
|
1007
852
|
ngOnDestroy() {
|
|
1008
853
|
this.destroy$.next();
|
|
1009
854
|
this.subscriptions.unsubscribe();
|
|
1010
|
-
this.disconnect();
|
|
855
|
+
void this.disconnect();
|
|
1011
856
|
}
|
|
1012
|
-
/** Reset to idle
|
|
857
|
+
/** Reset to idle (e.g. when modal re-opens so user can click Start Call). */
|
|
1013
858
|
resetToIdle() {
|
|
1014
859
|
if (this.callStateSubject.value === 'idle')
|
|
1015
860
|
return;
|
|
1016
|
-
this.
|
|
1017
|
-
this.stopDurationTimer();
|
|
1018
|
-
this.callStartTime = 0;
|
|
1019
|
-
this.audioAnalyzer.stop();
|
|
1020
|
-
this.stopLocalMic();
|
|
1021
|
-
this.wsClient.disconnect();
|
|
861
|
+
void this.disconnect();
|
|
1022
862
|
this.callStateSubject.next('idle');
|
|
1023
863
|
this.statusTextSubject.next('');
|
|
1024
864
|
this.durationSubject.next('0:00');
|
|
@@ -1026,7 +866,7 @@ class VoiceAgentService {
|
|
|
1026
866
|
connect(apiUrl, token, botId, conversationId, apiKey, eventToken, eventId, eventUrl, domainAuthority, usersApiUrl) {
|
|
1027
867
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1028
868
|
if (this.callStateSubject.value !== 'idle') {
|
|
1029
|
-
console.warn('Call already in progress');
|
|
869
|
+
console.warn('[HiveGpt Voice] Call already in progress');
|
|
1030
870
|
return;
|
|
1031
871
|
}
|
|
1032
872
|
try {
|
|
@@ -1039,185 +879,196 @@ class VoiceAgentService {
|
|
|
1039
879
|
.ensureValidAccessToken(token, usersApiUrl)
|
|
1040
880
|
.pipe(take(1))
|
|
1041
881
|
.toPromise();
|
|
1042
|
-
if (ensured === null || ensured === void 0 ? void 0 : ensured.accessToken)
|
|
882
|
+
if (ensured === null || ensured === void 0 ? void 0 : ensured.accessToken)
|
|
1043
883
|
accessToken = ensured.accessToken;
|
|
1044
|
-
}
|
|
1045
884
|
}
|
|
1046
885
|
catch (e) {
|
|
1047
|
-
console.warn('[HiveGpt Voice] Token refresh
|
|
886
|
+
console.warn('[HiveGpt Voice] Token refresh failed', e);
|
|
1048
887
|
}
|
|
1049
888
|
}
|
|
1050
889
|
const baseUrl = apiUrl.replace(/\/$/, '');
|
|
1051
|
-
const
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
890
|
+
const pcClient = new PipecatClient({
|
|
891
|
+
transport: new WebSocketTransport(),
|
|
892
|
+
enableMic: true,
|
|
893
|
+
enableCam: false,
|
|
894
|
+
callbacks: {
|
|
895
|
+
onConnected: () => this.ngZone.run(() => this.onPipecatConnected()),
|
|
896
|
+
onDisconnected: () => this.ngZone.run(() => this.onPipecatDisconnected()),
|
|
897
|
+
onBotReady: () => this.ngZone.run(() => this.onBotReady()),
|
|
898
|
+
onUserTranscript: (data) => this.ngZone.run(() => this.userTranscriptSubject.next({ text: data.text, final: !!data.final })),
|
|
899
|
+
onBotTranscript: (data) => this.ngZone.run(() => this.botTranscriptSubject.next(data.text)),
|
|
900
|
+
onError: (err) => {
|
|
901
|
+
this.ngZone.run(() => {
|
|
902
|
+
console.error('[HiveGpt Voice] PipecatClient error', err);
|
|
903
|
+
this.callStateSubject.next('ended');
|
|
904
|
+
this.statusTextSubject.next('Connection failed');
|
|
905
|
+
});
|
|
906
|
+
},
|
|
907
|
+
},
|
|
908
|
+
});
|
|
909
|
+
this.pcClient = pcClient;
|
|
910
|
+
// Bot audio arrives as a MediaStreamTrack — wire to a hidden <audio> element
|
|
911
|
+
pcClient.on(RTVIEvent.TrackStarted, (track, participant) => {
|
|
912
|
+
if (!(participant === null || participant === void 0 ? void 0 : participant.local) && track.kind === 'audio') {
|
|
913
|
+
this.ngZone.run(() => this.setupBotAudioTrack(track));
|
|
914
|
+
}
|
|
915
|
+
});
|
|
916
|
+
// Speaking state comes straight from RTVI events
|
|
917
|
+
pcClient.on(RTVIEvent.BotStartedSpeaking, () => this.ngZone.run(() => this.onBotStartedSpeaking()));
|
|
918
|
+
pcClient.on(RTVIEvent.BotStoppedSpeaking, () => this.ngZone.run(() => this.onBotStoppedSpeaking()));
|
|
919
|
+
pcClient.on(RTVIEvent.UserStartedSpeaking, () => this.ngZone.run(() => {
|
|
920
|
+
this.isUserSpeakingSubject.next(true);
|
|
921
|
+
this.callStateSubject.next('listening');
|
|
922
|
+
}));
|
|
923
|
+
pcClient.on(RTVIEvent.UserStoppedSpeaking, () => this.ngZone.run(() => {
|
|
924
|
+
this.isUserSpeakingSubject.next(false);
|
|
925
|
+
if (this.callStateSubject.value === 'listening') {
|
|
926
|
+
this.callStateSubject.next('connected');
|
|
927
|
+
}
|
|
928
|
+
}));
|
|
929
|
+
// Acquire mic (triggers browser permission prompt)
|
|
930
|
+
yield pcClient.initDevices();
|
|
931
|
+
// Build headers using the browser Headers API (required by pipecat's APIRequest type)
|
|
932
|
+
const requestHeaders = new Headers();
|
|
933
|
+
requestHeaders.append('Authorization', `Bearer ${accessToken}`);
|
|
934
|
+
requestHeaders.append('x-api-key', apiKey);
|
|
935
|
+
requestHeaders.append('hive-bot-id', botId);
|
|
936
|
+
requestHeaders.append('domain-authority', domainAuthority);
|
|
937
|
+
requestHeaders.append('eventUrl', eventUrl);
|
|
938
|
+
requestHeaders.append('eventId', eventId);
|
|
939
|
+
requestHeaders.append('eventToken', eventToken);
|
|
940
|
+
requestHeaders.append('ngrok-skip-browser-warning', 'true');
|
|
941
|
+
// POST to /ai/ask-voice-socket → receives { ws_url } → WebSocketTransport connects
|
|
942
|
+
yield pcClient.startBotAndConnect({
|
|
943
|
+
endpoint: `${baseUrl}/ai/ask-voice-socket`,
|
|
944
|
+
headers: requestHeaders,
|
|
945
|
+
requestData: {
|
|
1067
946
|
bot_id: botId,
|
|
1068
947
|
conversation_id: conversationId,
|
|
1069
948
|
voice: 'alloy',
|
|
1070
|
-
}
|
|
949
|
+
},
|
|
1071
950
|
});
|
|
1072
|
-
if (!res.ok) {
|
|
1073
|
-
throw new Error(`HTTP ${res.status}`);
|
|
1074
|
-
}
|
|
1075
|
-
const json = yield res.json();
|
|
1076
|
-
const wsUrl = (typeof (json === null || json === void 0 ? void 0 : json.ws_url) === 'string' && json.ws_url) ||
|
|
1077
|
-
(typeof (json === null || json === void 0 ? void 0 : json.rn_ws_url) === 'string' && json.rn_ws_url);
|
|
1078
|
-
if (!wsUrl) {
|
|
1079
|
-
throw new Error('No ws_url in response');
|
|
1080
|
-
}
|
|
1081
|
-
const untilCallEnds$ = merge(this.destroy$, this.endCall$);
|
|
1082
|
-
this.subscriptions.add(this.wsClient.userTranscript$
|
|
1083
|
-
.pipe(takeUntil(untilCallEnds$))
|
|
1084
|
-
.subscribe((t) => this.userTranscriptSubject.next(t)));
|
|
1085
|
-
this.subscriptions.add(this.wsClient.botTranscript$
|
|
1086
|
-
.pipe(takeUntil(untilCallEnds$))
|
|
1087
|
-
.subscribe((t) => this.botTranscriptSubject.next(t)));
|
|
1088
|
-
this.subscriptions.add(this.wsClient.opened$
|
|
1089
|
-
.pipe(takeUntil(untilCallEnds$), take(1))
|
|
1090
|
-
.subscribe(() => void this.onWebsocketOpened()));
|
|
1091
|
-
this.wsClient.connect(wsUrl);
|
|
1092
951
|
}
|
|
1093
952
|
catch (error) {
|
|
1094
|
-
console.error('
|
|
953
|
+
console.error('[HiveGpt Voice] connect failed', error);
|
|
1095
954
|
this.callStateSubject.next('ended');
|
|
1096
|
-
yield this.
|
|
955
|
+
yield this.cleanupPipecatClient();
|
|
1097
956
|
this.statusTextSubject.next('Connection failed');
|
|
1098
957
|
throw error;
|
|
1099
958
|
}
|
|
1100
959
|
});
|
|
1101
960
|
}
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
try {
|
|
1108
|
-
yield this.startLocalMic();
|
|
1109
|
-
this.statusTextSubject.next('Connected');
|
|
1110
|
-
this.callStateSubject.next('connected');
|
|
1111
|
-
this.wireSpeakingState();
|
|
1112
|
-
}
|
|
1113
|
-
catch (err) {
|
|
1114
|
-
console.error('[HiveGpt Voice] Mic or session setup failed', err);
|
|
1115
|
-
this.callStateSubject.next('ended');
|
|
1116
|
-
this.statusTextSubject.next('Microphone unavailable');
|
|
1117
|
-
yield this.disconnect();
|
|
1118
|
-
}
|
|
1119
|
-
});
|
|
961
|
+
onPipecatConnected() {
|
|
962
|
+
this.callStateSubject.next('connected');
|
|
963
|
+
this.statusTextSubject.next('Connected');
|
|
964
|
+
this.isMicMutedSubject.next(false);
|
|
965
|
+
this.startLocalMicAnalyzer();
|
|
1120
966
|
}
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
]).pipe(map(([local, server]) => local || server), distinctUntilChanged(), startWith(false));
|
|
1129
|
-
this.subscriptions.add(combineLatest([assistantTalking$, userTalking$])
|
|
1130
|
-
.pipe(takeUntil(untilCallEnds$))
|
|
1131
|
-
.subscribe(([bot, user]) => {
|
|
1132
|
-
const current = this.callStateSubject.value;
|
|
1133
|
-
if (user) {
|
|
1134
|
-
this.isUserSpeakingSubject.next(true);
|
|
1135
|
-
this.callStateSubject.next('listening');
|
|
1136
|
-
}
|
|
1137
|
-
else {
|
|
1138
|
-
this.isUserSpeakingSubject.next(false);
|
|
1139
|
-
}
|
|
1140
|
-
if (user) {
|
|
1141
|
-
return;
|
|
1142
|
-
}
|
|
1143
|
-
if (bot) {
|
|
1144
|
-
if (this.callStartTime === 0) {
|
|
1145
|
-
this.callStartTime = Date.now();
|
|
1146
|
-
this.startDurationTimer();
|
|
1147
|
-
}
|
|
1148
|
-
this.callStateSubject.next('talking');
|
|
1149
|
-
}
|
|
1150
|
-
else if (current === 'talking' || current === 'listening') {
|
|
1151
|
-
this.callStateSubject.next('connected');
|
|
1152
|
-
}
|
|
1153
|
-
}));
|
|
967
|
+
onPipecatDisconnected() {
|
|
968
|
+
this.stopDurationTimer();
|
|
969
|
+
this.callStartTime = 0;
|
|
970
|
+
this.audioAnalyzer.stop();
|
|
971
|
+
this.stopBotAudio();
|
|
972
|
+
this.callStateSubject.next('ended');
|
|
973
|
+
this.statusTextSubject.next('Call Ended');
|
|
1154
974
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
throw new Error('No audio track');
|
|
1163
|
-
}
|
|
1164
|
-
this.localMicStream = stream;
|
|
1165
|
-
this.isMicMutedSubject.next(!track.enabled);
|
|
1166
|
-
this.audioAnalyzer.start(stream);
|
|
1167
|
-
});
|
|
975
|
+
onBotReady() {
|
|
976
|
+
var _a, _b, _c;
|
|
977
|
+
// Retry track wiring in case tracks weren't ready at onConnected
|
|
978
|
+
this.startLocalMicAnalyzer();
|
|
979
|
+
const 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;
|
|
980
|
+
if (botTrack)
|
|
981
|
+
this.setupBotAudioTrack(botTrack);
|
|
1168
982
|
}
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
983
|
+
startLocalMicAnalyzer() {
|
|
984
|
+
var _a, _b, _c;
|
|
985
|
+
const 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;
|
|
986
|
+
if (localTrack) {
|
|
987
|
+
this.audioAnalyzer.start(new MediaStream([localTrack]));
|
|
1173
988
|
}
|
|
1174
989
|
}
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
this.
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
990
|
+
onBotStartedSpeaking() {
|
|
991
|
+
if (this.callStartTime === 0) {
|
|
992
|
+
this.callStartTime = Date.now();
|
|
993
|
+
this.startDurationTimer();
|
|
994
|
+
}
|
|
995
|
+
this.callStateSubject.next('talking');
|
|
996
|
+
}
|
|
997
|
+
onBotStoppedSpeaking() {
|
|
998
|
+
if (this.callStateSubject.value === 'talking') {
|
|
999
|
+
this.callStateSubject.next('connected');
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
setupBotAudioTrack(track) {
|
|
1003
|
+
var _a;
|
|
1004
|
+
if (!this.botAudioElement) {
|
|
1005
|
+
this.botAudioElement = new Audio();
|
|
1006
|
+
this.botAudioElement.autoplay = true;
|
|
1007
|
+
}
|
|
1008
|
+
const existing = (_a = this.botAudioElement.srcObject) === null || _a === void 0 ? void 0 : _a.getAudioTracks()[0];
|
|
1009
|
+
if ((existing === null || existing === void 0 ? void 0 : existing.id) === track.id)
|
|
1010
|
+
return;
|
|
1011
|
+
this.botAudioElement.srcObject = new MediaStream([track]);
|
|
1012
|
+
this.botAudioElement.play().catch((err) => console.warn('[HiveGpt Voice] Bot audio play blocked', err));
|
|
1013
|
+
}
|
|
1014
|
+
stopBotAudio() {
|
|
1015
|
+
var _a;
|
|
1016
|
+
if (this.botAudioElement) {
|
|
1017
|
+
try {
|
|
1018
|
+
this.botAudioElement.pause();
|
|
1019
|
+
(_a = this.botAudioElement.srcObject) === null || _a === void 0 ? void 0 : _a.getAudioTracks().forEach((t) => t.stop());
|
|
1020
|
+
this.botAudioElement.srcObject = null;
|
|
1021
|
+
}
|
|
1022
|
+
catch (_b) {
|
|
1023
|
+
// ignore
|
|
1024
|
+
}
|
|
1025
|
+
this.botAudioElement = null;
|
|
1026
|
+
}
|
|
1188
1027
|
}
|
|
1189
1028
|
disconnect() {
|
|
1190
1029
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1191
|
-
this.endCall$.next();
|
|
1192
1030
|
this.stopDurationTimer();
|
|
1193
1031
|
this.callStartTime = 0;
|
|
1194
1032
|
this.audioAnalyzer.stop();
|
|
1195
|
-
this.
|
|
1196
|
-
this.
|
|
1033
|
+
this.stopBotAudio();
|
|
1034
|
+
yield this.cleanupPipecatClient();
|
|
1197
1035
|
this.callStateSubject.next('ended');
|
|
1198
1036
|
this.statusTextSubject.next('Call Ended');
|
|
1199
1037
|
});
|
|
1200
1038
|
}
|
|
1039
|
+
cleanupPipecatClient() {
|
|
1040
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1041
|
+
if (this.pcClient) {
|
|
1042
|
+
try {
|
|
1043
|
+
yield this.pcClient.disconnect();
|
|
1044
|
+
}
|
|
1045
|
+
catch (_a) {
|
|
1046
|
+
// ignore
|
|
1047
|
+
}
|
|
1048
|
+
this.pcClient = null;
|
|
1049
|
+
}
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1201
1052
|
toggleMic() {
|
|
1202
|
-
|
|
1053
|
+
if (!this.pcClient)
|
|
1054
|
+
return;
|
|
1203
1055
|
const nextMuted = !this.isMicMutedSubject.value;
|
|
1204
|
-
|
|
1205
|
-
if (track) {
|
|
1206
|
-
track.enabled = !nextMuted;
|
|
1207
|
-
}
|
|
1056
|
+
this.pcClient.enableMic(!nextMuted);
|
|
1208
1057
|
this.isMicMutedSubject.next(nextMuted);
|
|
1058
|
+
if (nextMuted)
|
|
1059
|
+
this.isUserSpeakingSubject.next(false);
|
|
1209
1060
|
}
|
|
1210
1061
|
startDurationTimer() {
|
|
1211
|
-
const
|
|
1062
|
+
const tick = () => {
|
|
1212
1063
|
if (this.callStartTime > 0) {
|
|
1213
1064
|
const elapsed = Math.floor((Date.now() - this.callStartTime) / 1000);
|
|
1214
|
-
const
|
|
1215
|
-
const
|
|
1216
|
-
this.durationSubject.next(`${
|
|
1065
|
+
const m = Math.floor(elapsed / 60);
|
|
1066
|
+
const s = elapsed % 60;
|
|
1067
|
+
this.durationSubject.next(`${m}:${String(s).padStart(2, '0')}`);
|
|
1217
1068
|
}
|
|
1218
1069
|
};
|
|
1219
|
-
|
|
1220
|
-
this.durationInterval = setInterval(
|
|
1070
|
+
tick();
|
|
1071
|
+
this.durationInterval = setInterval(tick, 1000);
|
|
1221
1072
|
}
|
|
1222
1073
|
stopDurationTimer() {
|
|
1223
1074
|
if (this.durationInterval) {
|
|
@@ -1226,7 +1077,7 @@ class VoiceAgentService {
|
|
|
1226
1077
|
}
|
|
1227
1078
|
}
|
|
1228
1079
|
}
|
|
1229
|
-
VoiceAgentService.ɵprov = i0.ɵɵdefineInjectable({ factory: function VoiceAgentService_Factory() { return new VoiceAgentService(i0.ɵɵinject(AudioAnalyzerService), i0.ɵɵinject(
|
|
1080
|
+
VoiceAgentService.ɵprov = i0.ɵɵdefineInjectable({ factory: function VoiceAgentService_Factory() { return new VoiceAgentService(i0.ɵɵinject(AudioAnalyzerService), i0.ɵɵinject(PlatformTokenRefreshService), i0.ɵɵinject(i0.NgZone), i0.ɵɵinject(i0.PLATFORM_ID)); }, token: VoiceAgentService, providedIn: "root" });
|
|
1230
1081
|
VoiceAgentService.decorators = [
|
|
1231
1082
|
{ type: Injectable, args: [{
|
|
1232
1083
|
providedIn: 'root',
|
|
@@ -1234,8 +1085,8 @@ VoiceAgentService.decorators = [
|
|
|
1234
1085
|
];
|
|
1235
1086
|
VoiceAgentService.ctorParameters = () => [
|
|
1236
1087
|
{ type: AudioAnalyzerService },
|
|
1237
|
-
{ type: WebSocketVoiceClientService },
|
|
1238
1088
|
{ type: PlatformTokenRefreshService },
|
|
1089
|
+
{ type: NgZone },
|
|
1239
1090
|
{ type: Object, decorators: [{ type: Inject, args: [PLATFORM_ID,] }] }
|
|
1240
1091
|
];
|
|
1241
1092
|
|
|
@@ -5274,8 +5125,8 @@ ChatBotComponent.propDecorators = {
|
|
|
5274
5125
|
};
|
|
5275
5126
|
|
|
5276
5127
|
/**
|
|
5277
|
-
* Voice agent module. Uses
|
|
5278
|
-
*
|
|
5128
|
+
* Voice agent module. Uses @pipecat-ai/client-js + @pipecat-ai/websocket-transport
|
|
5129
|
+
* (peer dependencies) for WebSocket transport, RTVI protocol, and audio.
|
|
5279
5130
|
*/
|
|
5280
5131
|
class VoiceAgentModule {
|
|
5281
5132
|
}
|
|
@@ -5290,7 +5141,6 @@ VoiceAgentModule.decorators = [
|
|
|
5290
5141
|
providers: [
|
|
5291
5142
|
VoiceAgentService,
|
|
5292
5143
|
AudioAnalyzerService,
|
|
5293
|
-
WebSocketVoiceClientService
|
|
5294
5144
|
],
|
|
5295
5145
|
exports: [
|
|
5296
5146
|
VoiceAgentModalComponent
|
|
@@ -5561,5 +5411,5 @@ HiveGptModule.decorators = [
|
|
|
5561
5411
|
* Generated bundle index. Do not edit.
|
|
5562
5412
|
*/
|
|
5563
5413
|
|
|
5564
|
-
export { AudioAnalyzerService, ChatBotComponent, ChatDrawerComponent, HIVEGPT_AUTH_STORAGE_KEY, HiveGptModule, PlatformTokenRefreshService, VOICE_MODAL_CLOSE_CALLBACK, VOICE_MODAL_CONFIG, VoiceAgentModalComponent, VoiceAgentModule, VoiceAgentService, eClassificationType, hiveGptAuthStorageKeyFactory, BotsService as ɵa, SocketService as ɵb, ConversationService as ɵc, NotificationSocket as ɵd, TranslationService as ɵe,
|
|
5414
|
+
export { AudioAnalyzerService, ChatBotComponent, ChatDrawerComponent, HIVEGPT_AUTH_STORAGE_KEY, HiveGptModule, PlatformTokenRefreshService, VOICE_MODAL_CLOSE_CALLBACK, VOICE_MODAL_CONFIG, VoiceAgentModalComponent, VoiceAgentModule, VoiceAgentService, eClassificationType, hiveGptAuthStorageKeyFactory, BotsService as ɵa, SocketService as ɵb, ConversationService as ɵc, NotificationSocket as ɵd, TranslationService as ɵe, VideoPlayerComponent as ɵf, SafeHtmlPipe as ɵg, BotHtmlEditorComponent as ɵh };
|
|
5565
5415
|
//# sourceMappingURL=hivegpt-hiveai-angular.js.map
|