@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.
@@ -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, merge, concat, timer, combineLatest } from 'rxjs';
9
- import { switchMap, catchError, filter, take, map, takeUntil, distinctUntilChanged, startWith, tap } from 'rxjs/operators';
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
- * Native WebSocket client for voice session (signaling, transcripts, speaking hints).
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
- * Connects to `ws_url` from `POST {baseUrl}/ai/ask-voice-socket`.
812
- * Parses JSON messages for transcripts and optional assistant/user speaking flags.
813
- */
814
- class WebSocketVoiceClientService {
815
- constructor(ngZone) {
816
- this.ngZone = ngZone;
817
- this.ws = null;
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, wsClient, platformTokenRefresh,
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.localMicStream = null;
991
- this.endCall$ = new Subject();
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 state (e.g. when modal opens so user can click Start Call). */
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.endCall$.next();
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 before connect failed', e);
886
+ console.warn('[HiveGpt Voice] Token refresh failed', e);
1048
887
  }
1049
888
  }
1050
889
  const baseUrl = apiUrl.replace(/\/$/, '');
1051
- const postUrl = `${baseUrl}/ai/ask-voice-socket`;
1052
- const headers = {
1053
- 'Content-Type': 'application/json',
1054
- Authorization: `Bearer ${accessToken}`,
1055
- 'x-api-key': apiKey,
1056
- 'hive-bot-id': botId,
1057
- 'domain-authority': domainAuthority,
1058
- eventUrl,
1059
- eventId,
1060
- eventToken,
1061
- 'ngrok-skip-browser-warning': 'true',
1062
- };
1063
- const res = yield fetch(postUrl, {
1064
- method: 'POST',
1065
- headers,
1066
- body: JSON.stringify({
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('Error connecting voice agent:', error);
953
+ console.error('[HiveGpt Voice] connect failed', error);
1095
954
  this.callStateSubject.next('ended');
1096
- yield this.disconnect();
955
+ yield this.cleanupPipecatClient();
1097
956
  this.statusTextSubject.next('Connection failed');
1098
957
  throw error;
1099
958
  }
1100
959
  });
1101
960
  }
1102
- onWebsocketOpened() {
1103
- return __awaiter(this, void 0, void 0, function* () {
1104
- if (this.callStateSubject.value !== 'connecting') {
1105
- return;
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
- wireSpeakingState() {
1122
- const untilCallEnds$ = merge(this.destroy$, this.endCall$);
1123
- const transcriptDrivenAssistant$ = this.wsClient.botTranscript$.pipe(switchMap(() => concat(of(true), timer(800).pipe(map(() => false)))), distinctUntilChanged());
1124
- const assistantTalking$ = merge(this.wsClient.assistantSpeaking$, transcriptDrivenAssistant$).pipe(distinctUntilChanged(), startWith(false));
1125
- const userTalking$ = combineLatest([
1126
- this.audioAnalyzer.isUserSpeaking$,
1127
- this.wsClient.serverUserSpeaking$.pipe(startWith(false)),
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
- startLocalMic() {
1156
- return __awaiter(this, void 0, void 0, function* () {
1157
- this.stopLocalMic();
1158
- const stream = yield navigator.mediaDevices.getUserMedia({ audio: true });
1159
- const track = stream.getAudioTracks()[0];
1160
- if (!track) {
1161
- stream.getTracks().forEach((t) => t.stop());
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
- stopLocalMic() {
1170
- if (this.localMicStream) {
1171
- this.localMicStream.getTracks().forEach((t) => t.stop());
1172
- this.localMicStream = null;
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
- handleRemoteClose() {
1176
- return __awaiter(this, void 0, void 0, function* () {
1177
- const state = this.callStateSubject.value;
1178
- if (state === 'idle' || state === 'ended')
1179
- return;
1180
- this.endCall$.next();
1181
- this.stopDurationTimer();
1182
- this.callStartTime = 0;
1183
- this.audioAnalyzer.stop();
1184
- this.stopLocalMic();
1185
- this.callStateSubject.next('ended');
1186
- this.statusTextSubject.next('Connection lost');
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.stopLocalMic();
1196
- this.wsClient.disconnect();
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
- var _a;
1053
+ if (!this.pcClient)
1054
+ return;
1203
1055
  const nextMuted = !this.isMicMutedSubject.value;
1204
- const track = (_a = this.localMicStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks()[0];
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 updateDuration = () => {
1062
+ const tick = () => {
1212
1063
  if (this.callStartTime > 0) {
1213
1064
  const elapsed = Math.floor((Date.now() - this.callStartTime) / 1000);
1214
- const minutes = Math.floor(elapsed / 60);
1215
- const seconds = elapsed % 60;
1216
- this.durationSubject.next(`${minutes}:${String(seconds).padStart(2, '0')}`);
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
- updateDuration();
1220
- this.durationInterval = setInterval(updateDuration, 1000);
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(WebSocketVoiceClientService), i0.ɵɵinject(PlatformTokenRefreshService), i0.ɵɵinject(i0.PLATFORM_ID)); }, token: VoiceAgentService, providedIn: "root" });
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 native WebSocket for the voice session.
5278
- * Does NOT use Socket.IO or ngx-socket-io.
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, WebSocketVoiceClientService as ɵf, VideoPlayerComponent as ɵg, SafeHtmlPipe as ɵh, BotHtmlEditorComponent as ɵi };
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