@1interface/voice-core 0.7.1 → 0.8.1

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.
@@ -0,0 +1 @@
1
+ import{getBaseUrl as e,logger as t}from"@1interface/shared-core";import{X as n,Y as a}from"./sessionHelpers-BIwU9Fpt.js";const o=e=>{const t=document.createElement("audio");return t.autoplay=!0,t.style.display="none",t.srcObject=new MediaStream([e]),document.body.appendChild(t),()=>{try{t.pause(),t.srcObject=null,t.remove()}catch{}}};let r=null;function s(){const t=new Audio(`${e()}${n}`);return t.loop=!0,t.volume=a,t.preload="auto",t.load(),t}function i(e){const t=e.volume;e.volume=0,e.muted=!0;const n=()=>{e.pause(),e.currentTime=0,e.volume=t,e.muted=!1},a=e.play();return a&&"function"==typeof a.then?a.then(n,n):(n(),null)}function c(){const e=r;return r=null,e}const u={attachBotAudioTrack:o,requestMicPermission:async function(){try{const e=await(navigator.permissions?.query({name:"microphone"}).catch(()=>null));return t.debug({message:"Voice mic permission probe",api_name:"voice-mic",permission_state:e?.state??"unavailable"}),"granted"===e?.state||(await navigator.mediaDevices.getUserMedia({audio:!0})).getTracks().forEach(e=>e.stop()),{granted:!0}}catch(e){return t.warn({message:"Voice mic permission denied",api_name:"voice-mic",error_message:e instanceof Error?e.message:String(e)}),{granted:!1,error:e}}},primeGestureAudio:function(){if(r)return;const e=s(),t={audio:e,settled:null},n=i(e);t.settled=n?.then(()=>{t.settled=null})??null,r=t}};export{o as a,u as b,s as c,c as t,i as u};
@@ -0,0 +1 @@
1
+ "use strict";const e=require("@1interface/shared-core"),t=require("./sessionHelpers-ALyIwLPk.cjs"),r=e=>{const t=document.createElement("audio");return t.autoplay=!0,t.style.display="none",t.srcObject=new MediaStream([e]),document.body.appendChild(t),()=>{try{t.pause(),t.srcObject=null,t.remove()}catch{}}};let o=null;function n(){const r=new Audio(`${e.getBaseUrl()}${t.VOICE_THINKING_SOUND_PATH}`);return r.loop=!0,r.volume=t.VOICE_THINKING_SOUND_VOLUME,r.preload="auto",r.load(),r}function a(e){const t=e.volume;e.volume=0,e.muted=!0;const r=()=>{e.pause(),e.currentTime=0,e.volume=t,e.muted=!1},o=e.play();return o&&"function"==typeof o.then?o.then(r,r):(r(),null)}const s={attachBotAudioTrack:r,requestMicPermission:async function(){try{const t=await(navigator.permissions?.query({name:"microphone"}).catch(()=>null));return e.logger.debug({message:"Voice mic permission probe",api_name:"voice-mic",permission_state:t?.state??"unavailable"}),"granted"===t?.state||(await navigator.mediaDevices.getUserMedia({audio:!0})).getTracks().forEach(e=>e.stop()),{granted:!0}}catch(t){return e.logger.warn({message:"Voice mic permission denied",api_name:"voice-mic",error_message:t instanceof Error?t.message:String(t)}),{granted:!1,error:t}}},primeGestureAudio:function(){if(o)return;const e=n(),t={audio:e,settled:null},r=a(e);t.settled=r?.then(()=>{t.settled=null})??null,o=t}};exports.attachBrowserBotAudioTrack=r,exports.browserAudioPlatform=s,exports.createChimeElement=n,exports.takePrimedChime=function(){const e=o;return o=null,e},exports.unlockAudioElement=a;
package/dist/index.d.ts CHANGED
@@ -16,6 +16,7 @@ import { ThunkAction } from '@reduxjs/toolkit';
16
16
  import { ThunkDispatch } from '@reduxjs/toolkit';
17
17
  import { Transport } from '@pipecat-ai/client-js';
18
18
  import { UnknownAction } from '@reduxjs/toolkit';
19
+ import { VoiceTransport } from '@1interface/shared-core';
19
20
 
20
21
  export declare const attachBrowserBotAudioTrack: BotAudioTrackAttacher;
21
22
 
@@ -27,6 +28,10 @@ export declare interface AudioDeviceInfo {
27
28
  export declare interface AudioPlatform {
28
29
  attachBotAudioTrack: BotAudioTrackAttacher;
29
30
  requestMicPermission(): Promise<MicPermissionResult>;
31
+ createPcmCapture?(): PcmCapture;
32
+ createPcmPlayer?(): PcmPlayer;
33
+ createThinkingSound?(): ThinkingSound;
34
+ primeGestureAudio?(): void;
30
35
  }
31
36
 
32
37
  export declare const AudioPlatformContext: Context<AudioPlatform>;
@@ -68,6 +73,31 @@ declare interface OrbStateConfig {
68
73
  scale: number;
69
74
  }
70
75
 
76
+ export declare interface PcmCapture {
77
+ open(opts: PcmCaptureOpenOptions): void;
78
+ setMuted(muted: boolean): void;
79
+ close(): void;
80
+ }
81
+
82
+ export declare interface PcmCaptureOpenOptions {
83
+ micDeviceId: string | null;
84
+ initialMuted: boolean;
85
+ onChunk: (buffer: ArrayBuffer) => void;
86
+ onLoadingChange?: (loading: boolean) => void;
87
+ onError?: (error: unknown) => void;
88
+ }
89
+
90
+ export declare interface PcmPlayer {
91
+ play(pcm: ArrayBuffer): void;
92
+ readonly remainingSeconds: number;
93
+ setPrerollProvider(provider: (() => number) | null): void;
94
+ onDrained(callback: () => void): void;
95
+ resetForNewSegment(): void;
96
+ setSinkId(deviceId: string | null): void;
97
+ stop(): void;
98
+ destroy(): void;
99
+ }
100
+
71
101
  export declare const resetIndicatorState: ActionCreatorWithoutPayload<"voice/resetIndicatorState">;
72
102
 
73
103
  export declare const setConnectionStatus: ActionCreatorWithOptionalPayload<VoiceConnectionStatus, "voice/setConnectionStatus">;
@@ -92,6 +122,15 @@ export declare const setServerMuted: ActionCreatorWithOptionalPayload<boolean, "
92
122
 
93
123
  export declare const setStatusText: ActionCreatorWithOptionalPayload<string, "voice/setStatusText">;
94
124
 
125
+ export declare interface ThinkingSound {
126
+ unlock(): void;
127
+ start(remainingAudioSeconds: number): void;
128
+ pause(): void;
129
+ resume(remainingAudioSeconds?: number): void;
130
+ stop(): void;
131
+ destroy(): void;
132
+ }
133
+
95
134
  export declare const upsertBotSegment: ActionCreatorWithOptionalPayload< {
96
135
  id: number;
97
136
  text: string;
@@ -111,10 +150,10 @@ export declare const useVoice: () => VoiceContextValue;
111
150
 
112
151
  export declare function useVoiceDisplayState(): VoiceDisplayState;
113
152
 
114
- export declare const useVoiceProviderValue: ({ conversationId, voiceBridge, transportFactory, assistantStartsConversation, }: VoiceProviderParams) => VoiceContextValue;
115
-
116
153
  export declare const useVoiceUI: () => VoiceUIContextValue;
117
154
 
155
+ export declare const VOICE_CONNECT_ERROR_MESSAGE = "Could not connect voice. Please try again later.";
156
+
118
157
  export declare const VOICE_CONNECT_TIMEOUT_MS = 15000;
119
158
 
120
159
  export declare const VOICE_CONNECTION_STATUSES: {
@@ -146,6 +185,8 @@ export declare const VOICE_PIPELINE_STATUSES: {
146
185
  readonly SPEAKING: "speaking";
147
186
  };
148
187
 
188
+ export declare const VOICE_SAMPLE_RATE = 24000;
189
+
149
190
  export declare const VOICE_STORAGE_KEYS: {
150
191
  readonly SELECTED_MIC_ID: "voice_selectedMicId";
151
192
  readonly SELECTED_SPEAKER_ID: "voice_selectedSpeakerId";
@@ -197,13 +238,6 @@ export declare type VoicePipelineMode = (typeof VOICE_PIPELINE_MODES)[keyof type
197
238
 
198
239
  export declare type VoicePipelineStatus = (typeof VOICE_PIPELINE_STATUSES)[keyof typeof VOICE_PIPELINE_STATUSES];
199
240
 
200
- export declare interface VoiceProviderParams {
201
- conversationId: string | null;
202
- voiceBridge: VoiceChatBridge;
203
- transportFactory: VoiceTransportFactory;
204
- assistantStartsConversation: boolean;
205
- }
206
-
207
241
  export declare const voiceReducer: Reducer<VoiceState>;
208
242
 
209
243
  export declare interface VoiceRootState {
@@ -219,6 +253,16 @@ export declare interface VoiceSendTextOptions {
219
253
  messageType?: string[];
220
254
  }
221
255
 
256
+ export declare interface VoiceSessionEngine {
257
+ connect: () => Promise<void>;
258
+ disconnect: (commitTurn?: boolean) => void;
259
+ sendText: (text: string, options?: VoiceSendTextOptions) => void;
260
+ setMicEnabled: (enabled: boolean) => void;
261
+ setInputDevice: (deviceId: string) => void;
262
+ setOutputDevice: (deviceId: string) => void;
263
+ primeAudio?: () => void;
264
+ }
265
+
222
266
  export declare interface VoiceState {
223
267
  connectionStatus: VoiceConnectionStatus;
224
268
  pipelineStatus: VoicePipelineStatus;
@@ -237,7 +281,7 @@ export declare interface VoiceState {
237
281
  speakers: AudioDeviceInfo[];
238
282
  }
239
283
 
240
- export declare type VoiceTransportFactory = () => Transport;
284
+ export declare type VoiceTransportFactory = () => Transport | Promise<Transport>;
241
285
 
242
286
  export declare const VoiceUIContext: Context<VoiceUIContextValue>;
243
287
 
@@ -254,4 +298,6 @@ export declare interface VoiceUIContextValue {
254
298
  }> | null;
255
299
  }
256
300
 
301
+ export declare type VoiceWsTransport = Exclude<VoiceTransport, 'pipecat_webrtc'>;
302
+
257
303
  export { }
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react"),t=require("react-redux"),r=require("@reduxjs/toolkit"),n=require("@1interface/shared-core"),s={DISCONNECTED:"disconnected",CONNECTING:"connecting",CONNECTED:"connected"},o={IDLE:"idle",LISTENING:"listening",PROCESSING:"processing",SPEAKING:"speaking"},i={CASCADE:"cascade",REALTIME:"realtime"},a={OFFLINE:"offline",CONNECTING:"connecting",READY:"ready",IDLE:"idle",LISTENING:"listening",MUTED:"muted",PROCESSING:"processing",SPEAKING:"speaking"},c=[/rejected/i,/unauthori[sz]ed/i,/forbidden/i,/\b40[13]\b/],u=[/\b402\b/,/\b429\b/,/rate[\s-]?limit/i],l={SELECTED_MIC_ID:"voice_selectedMicId",SELECTED_SPEAKER_ID:"voice_selectedSpeakerId"};function d(){return{connectionStatus:s.DISCONNECTED,pipelineStatus:o.IDLE,listeningEnabled:!1,muted:!1,serverMuted:!1,statusText:null,isAgentWorking:!1,responseStartTime:null,interimTranscript:null,botTurn:{active:!1,chatMsgId:null,segments:[]},sessionEndedSignal:0,selectedMicId:"",selectedSpeakerId:"",microphones:[],speakers:[]}}const g=d(),p=r.createSlice({name:"voice",initialState:g,reducers:{setConnectionStatus(e,t){e.connectionStatus=t.payload},setPipelineStatus(e,t){e.pipelineStatus=t.payload},setListeningEnabled(e,t){e.listeningEnabled=t.payload},setStatusText(e,t){e.statusText=t.payload},setIsAgentWorking(e,t){e.isAgentWorking=t.payload},setResponseStartTime(e,t){e.responseStartTime=t.payload},setInterimTranscript(e,t){e.interimTranscript=t.payload},openBotTurn(e,t){e.botTurn={active:!0,chatMsgId:t.payload?.chatMsgId??null,segments:[]}},upsertBotSegment(e,t){const{id:r,text:n,spokenChars:s}=t.payload;e.botTurn.active=!0;const o=e.botTurn.segments.find(e=>e.id===r);o?(o.text=n,o.spokenChars=Math.max(o.spokenChars,s)):(e.botTurn.segments.push({id:r,text:n,spokenChars:s}),e.botTurn.segments.sort((e,t)=>e.id-t.id))},clearBotTurn(e){e.botTurn={active:!1,chatMsgId:null,segments:[]}},setMuted(e,t){e.muted=t.payload},setServerMuted(e,t){e.serverMuted=t.payload},setSelectedMicId(e,t){e.selectedMicId=t.payload},setSelectedSpeakerId(e,t){e.selectedSpeakerId=t.payload},setMicrophones(e,t){e.microphones=t.payload},setSpeakers(e,t){e.speakers=t.payload},signalSessionEnded(e){e.sessionEndedSignal+=1},resetVoiceState:()=>d(),resetIndicatorState(e){e.statusText=null,e.isAgentWorking=!1,e.responseStartTime=null,e.interimTranscript=null,e.serverMuted=!1,e.botTurn={active:!1,chatMsgId:null,segments:[]}}}}),{setConnectionStatus:m,setPipelineStatus:v,setListeningEnabled:I,setMuted:S,setServerMuted:_,setStatusText:E,setIsAgentWorking:T,setResponseStartTime:f,setInterimTranscript:C,openBotTurn:b,upsertBotSegment:h,clearBotTurn:k,setSelectedMicId:R,setSelectedSpeakerId:y,setMicrophones:V,setSpeakers:x,signalSessionEnded:M,resetVoiceState:N,resetIndicatorState:w}=p.actions,A=p.reducer,D=e.createContext(null),O=()=>{const t=e.useContext(D);if(!t)throw new Error("useAudioPlatform must be used inside <AudioPlatformProvider>. <VoiceProvider> mounts one by default; if you replaced VoiceProvider, you must wire your own.");return t};function P(){const e={};for(const[t,r]of Object.entries(n.getCustomHeaders()))r&&(e[t]=r);const t=n.readAccessToken();t&&(e[n.AUTH_HEADERS.ACCESS_TOKEN]=t);const r=n.readDeviceToken();return r&&(e[n.DEVICE_TOKEN_HEADER]=r),e}async function L(e){try{const t=await fetch(n.API_ENDPOINTS.WEBRTC_ICE(e),{headers:P()});return t.ok?(await t.json()).iceServers??[]:(n.logger.warn({message:"Voice ICE fetch failed — continuing without TURN",api_name:"voice-webrtc-ice",status:t.status}),[])}catch(e){return n.logger.warn({message:"Voice ICE fetch error — continuing without TURN",api_name:"voice-webrtc-ice",error_message:e instanceof Error?e.message:String(e)}),[]}}const G="voice-user-",U="Could not connect voice. Please try again later.",B=(e,t=`${G}${n.generateUniqueId()}`,r,s)=>({id:t,sender:"user",message:e,timestamp:(new Date).toISOString(),...r?.length?{documents:r}:{},...s?.length?{message_type:s}:{}}),q=(e,t)=>{const r=`${e}${n.generateUniqueId()}`;return{id:r,msg_id:r,sender:"agent",message:"",timestamp:(new Date).toISOString(),...t}};function j(){const e=t.useSelector(e=>e.voice.connectionStatus),r=t.useSelector(e=>e.voice.pipelineStatus),n=t.useSelector(e=>e.voice.listeningEnabled),i=t.useSelector(e=>e.voice.muted);return e===s.CONNECTING?a.CONNECTING:e!==s.CONNECTED?a.OFFLINE:n?i?a.MUTED:r===o.PROCESSING?a.PROCESSING:r===o.SPEAKING?a.SPEAKING:r===o.LISTENING?a.LISTENING:a.IDLE:a.READY}const F=e.createContext(null),K=e.createContext({strategyActive:!1,voiceSlot:null,onEnterVoice:null,onFloatingOrbClick:null,voiceInputHidden:!1,headerCenterSlot:null,floatOrbPositionRef:null}),W=r.createListenerMiddleware();W.startListening({actionCreator:R,effect:async e=>{const t=n.getStorageRef();t&&await t.setItem(l.SELECTED_MIC_ID,e.payload)}}),W.startListening({actionCreator:y,effect:async e=>{const t=n.getStorageRef();t&&await t.setItem(l.SELECTED_SPEAKER_ID,e.payload)}}),W.startListening({matcher:r.isAnyOf(n.clearSession,n.sessionExpired),effect:async(e,t)=>{const r=n.getStorageRef();r&&await Promise.all([r.removeItem(l.SELECTED_MIC_ID),r.removeItem(l.SELECTED_SPEAKER_ID)]),t.dispatch(N())}});const H=e=>{const t=document.createElement("audio");return t.autoplay=!0,t.style.display="none",t.srcObject=new MediaStream([e]),document.body.appendChild(t),()=>{try{t.pause(),t.srcObject=null,t.remove()}catch{}}},$={attachBotAudioTrack:H,requestMicPermission:async function(){try{const e=await(navigator.permissions?.query({name:"microphone"}).catch(()=>null));return n.logger.debug({message:"Voice mic permission probe",api_name:"voice-mic",permission_state:e?.state??"unavailable"}),"granted"===e?.state||(await navigator.mediaDevices.getUserMedia({audio:!0})).getTracks().forEach(e=>e.stop()),{granted:!0}}catch(e){return n.logger.warn({message:"Voice mic permission denied",api_name:"voice-mic",error_message:e instanceof Error?e.message:String(e)}),{granted:!1,error:e}}}};exports.AudioPlatformContext=D,exports.ORB_STATE_CONFIG={offline:{opacity:.15,playbackRate:.3,scale:1},connecting:{opacity:.4,playbackRate:.5,scale:1},ready:{opacity:.4,playbackRate:.4,scale:1},idle:{opacity:.6,playbackRate:.6,scale:1},listening:{opacity:.7,playbackRate:.7,scale:1},muted:{opacity:.7,playbackRate:.5,scale:1},processing:{opacity:1,playbackRate:1,scale:1},speaking:{opacity:1,playbackRate:1,scale:1.3}},exports.VOICE_CONNECTION_STATUSES=s,exports.VOICE_CONNECT_TIMEOUT_MS=15e3,exports.VOICE_DISPLAY_STATES=a,exports.VOICE_PIPELINE_MODES=i,exports.VOICE_PIPELINE_STATUSES=o,exports.VOICE_STORAGE_KEYS=l,exports.VoiceContext=F,exports.VoiceUIContext=K,exports.attachBrowserBotAudioTrack=H,exports.browserAudioPlatform=$,exports.clearBotTurn=k,exports.hydrateVoice=e=>async t=>{const[r,n]=await Promise.all([e.getItem(l.SELECTED_MIC_ID),e.getItem(l.SELECTED_SPEAKER_ID)]);r&&t(p.actions.setSelectedMicId(r)),n&&t(p.actions.setSelectedSpeakerId(n))},exports.openBotTurn=b,exports.resetIndicatorState=w,exports.setConnectionStatus=m,exports.setInterimTranscript=C,exports.setIsAgentWorking=T,exports.setListeningEnabled=I,exports.setMuted=S,exports.setPipelineStatus=v,exports.setResponseStartTime=f,exports.setSelectedMicId=R,exports.setSelectedSpeakerId=y,exports.setServerMuted=_,exports.setStatusText=E,exports.upsertBotSegment=h,exports.useAudioPlatform=O,exports.useMediaDevices=function(){return{microphones:t.useSelector(e=>e.voice.microphones),speakers:t.useSelector(e=>e.voice.speakers),selectedMicId:t.useSelector(e=>e.voice.selectedMicId),selectedSpeakerId:t.useSelector(e=>e.voice.selectedSpeakerId)}},exports.useVoice=()=>{const t=e.useContext(F);if(!t)throw new Error("useVoice must be used within VoiceProvider");return t},exports.useVoiceDisplayState=j,exports.useVoiceProviderValue=({conversationId:r,voiceBridge:a,transportFactory:l,assistantStartsConversation:d})=>{const g=t.useDispatch(),p=O(),N=t.useSelector(e=>e.voice.muted),w=t.useSelector(e=>e.voice.serverMuted),A=t.useSelector(e=>e.voice.pipelineStatus),D=t.useSelector(e=>e.voice.isAgentWorking),F=t.useSelector(e=>e.voice.sessionEndedSignal),{connect:K,disconnect:W,sendText:H,setMicEnabled:$,setInputDevice:Y,setOutputDevice:Z}=function(r,a,l,d){const g=t.useDispatch(),p=t.useStore(),N=O(),w=t.useSelector(e=>e.auth.accessToken),A=e.useRef(a);e.useEffect(()=>{A.current=a});const D=e.useRef(null),j=e.useRef(new Map),F=e.useRef(""),K=e.useRef(!1),W=e.useRef(!1),H=e.useRef(null),$=e.useRef(""),Y=e.useRef(null),Z=e.useRef(null),z=e.useRef(null),J=e.useRef(null),Q=e.useRef(!0),X=e.useRef(null),ee=e.useRef(!1),te=e.useRef(""),re=e.useRef(0),ne=e.useRef(0),se=e.useRef(!1),oe=e.useRef(0),ie=e.useRef(null),ae=e.useRef(null),ce=e.useRef(0),ue=e.useRef(0),le=e.useRef(null),de=e.useRef(null),ge=e.useCallback(()=>{if(null!=ie.current)return;const e=Date.now();ie.current=e,g(f(e)),g(n.clearLastResponseTime())},[g]),pe=e.useCallback(()=>{const e=ie.current;ie.current=null,g(f(null)),null!=e&&g(n.setLastResponseTime(Date.now()-e))},[g]),me=e.useCallback(()=>{ie.current=null,g(f(null))},[g]),ve=e.useCallback((e=n.INDICATOR_TIMER_ACTIONS.KEEP)=>{g(E(null)),g(T(!1)),e===n.INDICATOR_TIMER_ACTIONS.FINALIZE?pe():e===n.INDICATOR_TIMER_ACTIONS.CANCEL&&me()},[g,pe,me]),[Ie]=e.useState(()=>({setStatusText:e=>g(E(e||null)),setIsAgentWorking:e=>g(T(e)),appendStreamText:()=>{},pushWidget:e=>{const t=q("voice-widget-",{widget:[e],message_type:["widget"]});A.current.setMessages(e=>[...e,t])},onPayment:e=>A.current.onPaymentInit?.(e),onRequestDocuments:e=>A.current.onRequestDocuments?.(e),onConversationTitle:e=>A.current.onConversationTitle?.(e),onError:e=>{n.logger.error({message:"Voice RTVI server error",api_name:"voice-rtvi",error_message:e});const t=q("voice-error-",{message:"Something went wrong. Please try again.",message_type:["error"]});A.current.setMessages(e=>[...e,t])},onStatusActive:()=>{ge()}})),Se=e.useCallback(()=>{J.current&&(clearTimeout(J.current),J.current=null)},[]),_e=e.useCallback(()=>[...j.current.entries()].sort((e,t)=>e[0]-t[0]).map(([,e])=>e.text).join(" ").trim(),[]),Ee=e.useCallback(()=>{D.current=null,j.current=new Map,F.current="",g(k())},[g]),Te=e.useCallback(()=>{const e=D.current;if(e){const t=_e();t&&A.current.updateStreamingMessage(e,t),A.current.commitStreamingMessages()}Ee()},[_e,Ee]),fe=e.useCallback(e=>{Se(),X.current&&(clearTimeout(X.current),X.current=null),ee.current=!1,te.current="",g(V([])),g(x([])),z.current?.(),z.current=null,Y.current=null,se.current=!1,g(I(!1)),g(v(o.IDLE)),g(m(s.DISCONNECTED)),g(C(null)),g(_(!1)),H.current=null,$.current="",K.current=!1,W.current=!1,e?Te():(A.current.discardStreamingMessages(),Ee()),ve(n.INDICATOR_TIMER_ACTIONS.CANCEL)},[g,ve,Se,Te,Ee]),Ce=e.useCallback(()=>{if(D.current)return;const e=`voice-agent-${n.generateUniqueId()}`;D.current=e,j.current=new Map,F.current="",g(b({chatMsgId:e}))},[g]),be=e.useCallback(e=>{const t=e.spoken_progress?(e.spoken_progress?.accumulated_text??"")+(e.spoken_progress?.remaining_text??""):e.text??"";if(!t)return;Ce();const r=K.current?function(e,t){const r=(e.get(0)?.text??"")+t;return e.set(0,{text:r,completed:!1}),{id:0,text:r,spokenChars:r.length}}(j.current,t):function(e,t,r,n){const s=t.segment_id??0,o="completed"===t.spoken_status;if(o&&!e.has(s)){const t=[...e.entries()].find(([,e])=>e.text===r&&!e.completed);if(t)return t[1].completed=!0,{id:t[0],text:r,spokenChars:r.length}}const i=e.get(s);return e.set(s,{text:r,completed:o||!0===i?.completed}),{id:s,text:r,spokenChars:n&&t.spoken_progress?(t.spoken_progress.accumulated_text??"").length:r.length}}(j.current,e,t,W.current);g(h(r));const n=_e();n&&D.current&&n!==F.current&&(F.current=n,A.current.updateStreamingMessage(D.current,n))},[g,Ce,_e]),he=e.useCallback(e=>{const t=e,s=t?.type,o=t?.data;switch(n.logger.debug({message:"Voice RTVI server-message received",api_name:"voice-rtvi",conversation_id:r??void 0,event_type:s??"unknown"}),s){case"status_update":n.handleSharedEvent({kind:"status_update",statusText:o?.text??""},Ie);break;case"started-thinking":g(T(!0)),ge();break;case"stopped-thinking":ve(n.INDICATOR_TIMER_ACTIONS.FINALIZE);break;case"clear_status":break;case"widget":o&&n.handleSharedEvent({kind:"widget",widget:o},Ie);break;case"payment":o&&n.handleSharedEvent({kind:"payment",payment:o},Ie);break;case"request_documents":o&&n.handleSharedEvent({kind:"request_documents",requestDocuments:o},Ie);break;case"conversation_title":{const e="string"==typeof o?.text?o.text.trim():"";e&&n.handleSharedEvent({kind:"conversation_title",title:e},Ie);break}case"session-ended":de.current={reason:"string"==typeof o?.reason?o.reason:"ended"};break;default:n.logger.debug({message:"Voice RTVI unhandled server-message",api_name:"voice-rtvi",event_type:s??"unknown"})}},[Ie,g,ge,ve,r]),ke=e.useRef(()=>{}),Re=e.useRef(async()=>{}),ye=e.useCallback(e=>{const t=()=>e===re.current;return{onConnected:()=>{if(!t())return;n.logger.info({message:"Voice RTVI connected",api_name:"voice-rtvi",conversation_id:r??void 0}),g(m(s.CONNECTED)),g(I(!0)),g(v(o.IDLE)),Y.current&&Y.current.isMicEnabled!==Q.current&&(n.logger.debug({message:"Voice re-asserting mic state on reconnect",api_name:"voice-rtvi",conversation_id:r??void 0,enabled:Q.current}),Y.current.enableMic(Q.current));const{selectedMicId:e,selectedSpeakerId:i}=p.getState().voice;e&&Y.current?.updateMic(e),i&&Y.current?.updateSpeaker(i);const a=e=>t=>n.logger.warn({message:"Voice device enumeration failed",api_name:"voice-mic",conversation_id:r??void 0,device_kind:e,error_message:t instanceof Error?t.message:String(t)});Y.current?.getAllMics().then(e=>{t()&&g(V(e.map(e=>({deviceId:e.deviceId,label:e.label}))))}).catch(a("mics")),Y.current?.getAllSpeakers().then(e=>{t()&&g(x(e.map(e=>({deviceId:e.deviceId,label:e.label}))))}).catch(a("speakers")),ee.current=!0},onBotReady:e=>{if(!t())return;const s=e?.about;K.current=s?.pipeline_mode===i.REALTIME,W.current=!0===s?.tts_word_timestamps,ce.current=0,ue.current=0,oe.current=0,n.logger.info({message:"Voice RTVI bot-ready",api_name:"voice-rtvi",conversation_id:r??void 0,pipeline_mode:s?.pipeline_mode??"cascade",tts_word_timestamps:W.current,assistant_starts:d}),Se(),d&&(g(T(!0)),ge())},onDisconnected:()=>{if(!t())return;n.logger.info({message:"Voice RTVI disconnected",api_name:"voice-rtvi",conversation_id:r??void 0,drop_outcome:le.current??"transient",reconnect_attempts:ce.current}),fe(!0);const e=ne.current,s=le.current;le.current=null;const o=de.current;return de.current=null,o?(n.logger.info({message:"Voice session ended by server — no reconnect",api_name:"voice-rtvi",conversation_id:r??void 0,reason:o.reason}),"idle_timeout"===o.reason&&n.showToast({title:"Voice ended",description:"It got quiet for a while, so we wrapped up. Start again anytime."}),void g(M())):"terminal"===s?(n.logger.warn({message:"Voice RTVI terminal error — no retry",api_name:"voice-rtvi"}),void g(M())):"auth"===s?ue.current>=1||null==r?(n.logger.warn({message:"Voice RTVI auth refresh budget exhausted — giving up",api_name:"voice-rtvi",conversation_id:r??void 0}),void g(M())):(ue.current+=1,n.logger.info({message:"Voice RTVI auth drop — refreshing token then reconnecting",api_name:"voice-rtvi",conversation_id:r??void 0,auth_refresh_count:ue.current}),void(async()=>{let t=!1;try{t=!!await(n.getTokenRefreshHandler()?.())}catch{}e===ne.current&&(t?Re.current():(n.logger.warn({message:"Voice RTVI token refresh failed — no reconnect",api_name:"voice-rtvi",conversation_id:r??void 0}),g(M())))})()):ce.current>=3||null==r?(ce.current>=3&&(n.logger.warn({message:"Voice RTVI auto-reconnect cap reached — giving up",api_name:"voice-rtvi",conversation_id:r??void 0,reconnect_attempts:ce.current}),n.showErrorToast("Voice connection lost. Please try again.")),void g(M())):(ce.current+=1,n.logger.info({message:"Voice RTVI scheduling auto-reconnect",api_name:"voice-rtvi",conversation_id:r??void 0,reconnect_attempts:ce.current,delay_ms:1e3}),ae.current&&clearTimeout(ae.current),void(ae.current=setTimeout(()=>{ae.current=null,Re.current()},1e3)))},onError:e=>{if(!t())return;const s=e?.data,i=s?.error??s?.message??"Voice error";n.handleSharedEvent({kind:"error",error:i},Ie),g(v(o.IDLE)),ve(n.INDICATOR_TIMER_ACTIONS.CANCEL),s?.fatal&&(c.some(e=>e.test(i))?le.current="auth":u.some(e=>e.test(i))&&(le.current="terminal"),n.logger.warn({message:"Voice RTVI fatal error — closing transport",api_name:"voice-rtvi",conversation_id:r??void 0,drop_outcome:le.current??"generic"}),Y.current?.disconnect().catch(()=>{}))},onTrackStarted:(e,s)=>{t()&&(s?.local||"audio"!==e.kind||(n.logger.debug({message:"Voice RTVI bot audio track started",api_name:"voice-rtvi",conversation_id:r??void 0}),z.current?.(),z.current=N.attachBotAudioTrack(e)))},onAvailableMicsUpdated:e=>{t()&&(g(V(e.map(e=>({deviceId:e.deviceId,label:e.label})))),ee.current&&(X.current&&clearTimeout(X.current),X.current=setTimeout(()=>{if(X.current=null,!Y.current)return;const{selectedMicId:e,microphones:t}=p.getState().voice;n.logger.info({message:"Voice audio devices changed",api_name:"voice-mic",microphones:t.map(e=>e.label).join(", ")}),n.logger.debug({message:"Voice re-acquiring mic (device change)",api_name:"voice-rtvi",enabled:Q.current,device:e||"default"}),Y.current.updateMic(e)},300)))},onAvailableSpeakersUpdated:e=>{if(!t())return;if(g(x(e.map(e=>({deviceId:e.deviceId,label:e.label})))),!ee.current)return;const r=e.map(e=>e.label).join(", ");r!==te.current&&(te.current=r,n.logger.info({message:"Voice audio output devices changed",api_name:"voice-mic",speakers:r}))},onMicUpdated:e=>{if(!t())return;const s=Y.current;s&&s.isMicEnabled!==Q.current&&(n.logger.debug({message:"Voice re-asserting mic state after track swap",api_name:"voice-rtvi",conversation_id:r??void 0,enabled:Q.current,device:e?.label}),s.enableMic(Q.current))},onUserMuteStarted:()=>{t()&&n.logger.debug({message:"Voice RTVI user-mute-started (ignored)",api_name:"voice-rtvi",conversation_id:r??void 0})},onUserMuteStopped:()=>{t()&&(n.logger.debug({message:"Voice RTVI user-mute-stopped",api_name:"voice-rtvi",conversation_id:r??void 0,honored:d}),d&&g(_(!1)))},onUserStartedSpeaking:()=>{t()&&(n.logger.debug({message:"Voice RTVI user started speaking",api_name:"voice-rtvi",conversation_id:r??void 0}),H.current=null,$.current="",g(C(null)),g(v(o.LISTENING)))},onUserStoppedSpeaking:()=>{if(!t())return;n.logger.debug({message:"Voice RTVI user stopped speaking",api_name:"voice-rtvi",conversation_id:r??void 0,has_interim:!!$.current.trim()}),g(v(o.PROCESSING)),g(T(!0)),g(E(null)),ge();const e=$.current.trim();if(e){const t=`${G}${n.generateUniqueId()}`;H.current=t,g(C(null)),A.current.setMessages(r=>[...r,B(e,t)])}},onUserTranscript:e=>{if(!t())return;if(!e.final)return void(e.text&&($.current=e.text,K.current||g(C(e.text))));g(C(null));const s=e.text&&e.text.trim()?e.text:"";n.logger.debug({message:"Voice RTVI user transcript (final)",api_name:"voice-rtvi",conversation_id:r??void 0,has_text:!!s});const o=H.current;H.current=null,o?s&&A.current.setMessages(e=>e.map(e=>e.id===o?{...e,message:s}:e)):s&&A.current.setMessages(e=>[...e,B(s)])},onBotStartedSpeaking:()=>{t()&&(n.logger.debug({message:"Voice RTVI bot started speaking",api_name:"voice-rtvi",conversation_id:r??void 0}),Ce(),g(v(o.SPEAKING)),ve(n.INDICATOR_TIMER_ACTIONS.FINALIZE))},onBotOutput:e=>{t()&&be(e)},onBotStoppedSpeaking:()=>{t()&&(n.logger.debug({message:"Voice RTVI bot stopped speaking",api_name:"voice-rtvi",conversation_id:r??void 0}),Te(),g(v(o.IDLE)),ve(n.INDICATOR_TIMER_ACTIONS.FINALIZE),d&&p.getState().voice.serverMuted&&(n.logger.warn({message:"Voice greeting mute-hold released on bot-turn end (server unmute missing)",api_name:"voice-rtvi",conversation_id:r??void 0}),g(_(!1))))},onServerMessage:e=>{t()&&he(e)}}},[g,p,N,Se,fe,ve,ge,be,Ce,Te,he,Ie,d,r]),Ve=e.useCallback(async()=>{if(Y.current||se.current)return;if(!r)return;if(se.current=!0,n.logger.info({message:"Voice RTVI connecting",api_name:"voice-rtvi",conversation_id:r,assistant_starts:d}),d&&(Q.current=!1,g(_(!0)),n.logger.debug({message:"Voice greeting mute-hold armed",api_name:"voice-rtvi",conversation_id:r})),n.isTokenAboutToExpire(n.readTokenExpiresAt())&&oe.current<1){oe.current+=1,n.logger.debug({message:"Voice pre-connect token refresh (freshness gate)",api_name:"voice-rtvi",conversation_id:r,freshness_refresh_count:oe.current});const e=ne.current;try{await Promise.resolve(n.getTokenRefreshHandler()?.())}catch{}if(e!==ne.current)return}const e=++re.current,t=ne.current;de.current=null,le.current=null,g(m(s.CONNECTING));const o=()=>t!==ne.current||e!==re.current;try{const[{PipecatClient:t,LogLevel:s,RTVIMessage:i},a]=await Promise.all([Promise.resolve().then(()=>require("./index.module-DCaqEXL5.cjs")),L(r)]);if(o())return;Z.current=i;const c=new t({transport:l(),enableMic:!0,enableCam:!1,callbacks:ye(e)});if(c.setLogLevel(s.WARN),o())return void c.disconnect().catch(()=>{});Y.current=c,n.logger.debug({message:"Voice RTVI client constructed",api_name:"voice-rtvi",conversation_id:r,enable_mic:Q.current}),Se(),J.current=setTimeout(()=>{n.logger.warn({message:"Voice RTVI connect timed out (no bot-ready)",api_name:"voice-rtvi"}),n.showErrorToast("Voice connection timed out. Please try again."),g(M()),ke.current()},15e3),await c.connect({webrtcRequestParams:{endpoint:n.API_ENDPOINTS.WEBRTC_OFFER(r),headers:new Headers(P())},iceConfig:{iceServers:a}})}catch(t){const r=(e=>{const t="object"==typeof e&&null!==e&&"status"in e?e.status:void 0;return"number"==typeof t?t:void 0})(t);n.logger.error({message:"Voice RTVI connect failed",api_name:"voice-rtvi",status:r,error_message:t instanceof Error?t.message:String(t)});const s=(e=>void 0!==e&&e>=400&&e<500)(r),o=s?await(async(e,t)=>{const r=429===t?"Voice session limit reached. Please try again later.":U,n="object"==typeof e&&null!==e&&"cause"in e?e.cause:void 0;if(!(n instanceof Response))return r;try{const e=await n.json(),t=e?.data?.error_message??e?.data?.message;return"string"==typeof t?t:r}catch{return r}})(t,r):U;e===re.current&&(n.showErrorToast(o),s&&(ae.current&&(clearTimeout(ae.current),ae.current=null),ce.current=0,g(M())),fe(!1))}},[r,g,l,ye,Se,fe,d]),xe=e.useCallback((e=!0)=>{n.logger.info({message:"Voice RTVI disconnect (intentional teardown)",api_name:"voice-rtvi",conversation_id:r??void 0,commit_turn:e}),ne.current+=1,re.current+=1,oe.current=0,ae.current&&(clearTimeout(ae.current),ae.current=null),ce.current=0,ue.current=0,le.current=null,Q.current=!0,g(S(!1));const t=Y.current;t?.disconnect().catch(e=>{n.logger.warn({message:"Voice RTVI disconnect threw",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}),fe(e)},[fe,g,r]),Me=e.useCallback(e=>{Q.current=e,Y.current&&(n.logger.debug({message:"Voice mic enable toggled",api_name:"voice-rtvi",enabled:e}),Y.current.enableMic(e))},[]),Ne=e.useCallback(e=>{g(R(e)),Y.current?.updateMic(e)},[g]),we=e.useCallback(e=>{g(y(e)),Y.current?.updateSpeaker(e)},[g]),Ae=e.useCallback((e,t)=>{const s=Y.current;if(!s)return;const i=t?.modelContext;n.logger.info({message:"Voice RTVI send-text",api_name:"voice-rtvi",conversation_id:r??void 0,run_immediately:!0,has_model_context:Boolean(i?.length)});try{const t=Z.current;i?.length&&t?s.transport.sendMessage(new t("send-text",{content:e,options:{run_immediately:!0,audio_response:!0},model_context:i})):s.sendText(e,{run_immediately:!0,audio_response:!0})}catch(e){return void n.logger.error({message:"Voice RTVI send-text failed",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}D.current&&Te(),A.current.setMessages(r=>[...r,B(e,void 0,t?.documents,t?.messageType)]),g(T(!0)),g(E(null)),ge(),g(v(o.PROCESSING))},[g,ge,Te,r]);e.useEffect(()=>{ke.current=xe},[xe]),e.useEffect(()=>{Re.current=Ve},[Ve]);const De=e.useRef(r),Oe=e.useRef(!1);e.useEffect(()=>{const e=De.current;if(De.current=r,e===r)return;const t=se.current;t&&(Oe.current=!0),t&&xe(!1),Oe.current&&null!=r&&(Oe.current=!1,Ve())},[r,xe,Ve]);const Pe=e.useRef(w);return e.useEffect(()=>{const e=Pe.current;Pe.current=w,e&&!w&&se.current&&xe(!1)},[w,xe]),e.useEffect(()=>()=>{ke.current()},[]),{connect:Ve,disconnect:xe,sendText:Ae,setMicEnabled:Me,setInputDevice:Ne,setOutputDevice:we}}(r,a,l,d);e.useEffect(()=>{$(!N&&!w)},[N,w,$]);const z=j();e.useEffect(()=>(a.setVoiceSendText(H),()=>a.setVoiceSendText(null)),[a,H]);const J=e.useRef(!1);return{displayState:z,pipelineStatus:A,isAgentWorking:D,enterVoiceMode:e.useCallback(async()=>{if(J.current)return n.showErrorToast("Microphone permission changed — please reload the page to apply the new setting."),!1;const e=await p.requestMicPermission();if(!e.granted){const t=e.error;return n.logger.error({message:"Mic permission denied",api_name:"voice-mic",error_message:t instanceof Error?t.message:t?String(t):void 0}),J.current=!0,n.showErrorToast("Microphone access is required for voice mode. Please allow it in your browser's site settings and reload the page."),!1}return K(),!0},[p,K]),exitVoiceMode:e.useCallback(()=>{W()},[W]),toggleMute:e.useCallback(()=>{g(S(!N))},[g,N]),sendText:H,muted:N,serverMuted:w,sessionEndedSignal:F,setInputDevice:Y,setOutputDevice:Z}},exports.useVoiceUI=()=>e.useContext(K),exports.voiceListenerMiddleware=W,exports.voiceReducer=A;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./sessionHelpers-ALyIwLPk.cjs"),t=require("react"),r=require("react-redux"),s=require("@reduxjs/toolkit"),o=require("@1interface/shared-core"),i=require("./browserAudioPlatform-DyVOp76o.cjs"),S=t.createContext({strategyActive:!1,voiceSlot:null,onEnterVoice:null,onFloatingOrbClick:null,voiceInputHidden:!1,headerCenterSlot:null,floatOrbPositionRef:null}),a=s.createListenerMiddleware();a.startListening({actionCreator:e.setSelectedMicId,effect:async t=>{const r=o.getStorageRef();r&&await r.setItem(e.VOICE_STORAGE_KEYS.SELECTED_MIC_ID,t.payload)}}),a.startListening({actionCreator:e.setSelectedSpeakerId,effect:async t=>{const r=o.getStorageRef();r&&await r.setItem(e.VOICE_STORAGE_KEYS.SELECTED_SPEAKER_ID,t.payload)}}),a.startListening({matcher:s.isAnyOf(o.clearSession,o.sessionExpired),effect:async(t,r)=>{const s=o.getStorageRef();s&&await Promise.all([s.removeItem(e.VOICE_STORAGE_KEYS.SELECTED_MIC_ID),s.removeItem(e.VOICE_STORAGE_KEYS.SELECTED_SPEAKER_ID)]),r.dispatch(e.resetVoiceState())}}),exports.AudioPlatformContext=e.AudioPlatformContext,exports.ORB_STATE_CONFIG=e.ORB_STATE_CONFIG,exports.VOICE_CONNECTION_STATUSES=e.VOICE_CONNECTION_STATUSES,exports.VOICE_CONNECT_ERROR_MESSAGE=e.VOICE_CONNECT_ERROR_MESSAGE,exports.VOICE_CONNECT_TIMEOUT_MS=e.VOICE_CONNECT_TIMEOUT_MS,exports.VOICE_DISPLAY_STATES=e.VOICE_DISPLAY_STATES,exports.VOICE_PIPELINE_MODES=e.VOICE_PIPELINE_MODES,exports.VOICE_PIPELINE_STATUSES=e.VOICE_PIPELINE_STATUSES,exports.VOICE_SAMPLE_RATE=e.VOICE_SAMPLE_RATE,exports.VOICE_STORAGE_KEYS=e.VOICE_STORAGE_KEYS,exports.VoiceContext=e.VoiceContext,exports.clearBotTurn=e.clearBotTurn,exports.hydrateVoice=e.hydrateVoice,exports.openBotTurn=e.openBotTurn,exports.resetIndicatorState=e.resetIndicatorState,exports.setConnectionStatus=e.setConnectionStatus,exports.setInterimTranscript=e.setInterimTranscript,exports.setIsAgentWorking=e.setIsAgentWorking,exports.setListeningEnabled=e.setListeningEnabled,exports.setMuted=e.setMuted,exports.setPipelineStatus=e.setPipelineStatus,exports.setResponseStartTime=e.setResponseStartTime,exports.setSelectedMicId=e.setSelectedMicId,exports.setSelectedSpeakerId=e.setSelectedSpeakerId,exports.setServerMuted=e.setServerMuted,exports.setStatusText=e.setStatusText,exports.upsertBotSegment=e.upsertBotSegment,exports.useAudioPlatform=e.useAudioPlatform,exports.useVoice=e.useVoice,exports.useVoiceDisplayState=e.useVoiceDisplayState,exports.voiceReducer=e.voiceSlice_default,exports.attachBrowserBotAudioTrack=i.attachBrowserBotAudioTrack,exports.browserAudioPlatform=i.browserAudioPlatform,exports.VoiceUIContext=S,exports.useMediaDevices=function(){return{microphones:r.useSelector(e=>e.voice.microphones),speakers:r.useSelector(e=>e.voice.speakers),selectedMicId:r.useSelector(e=>e.voice.selectedMicId),selectedSpeakerId:r.useSelector(e=>e.voice.selectedSpeakerId)}},exports.useVoiceUI=()=>t.useContext(S),exports.voiceListenerMiddleware=a;
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{createContext as e,useContext as t,useRef as n,useEffect as r,useCallback as i,useState as s}from"react";import{useDispatch as c,useStore as o,useSelector as a}from"react-redux";import{createSlice as u,createListenerMiddleware as d,isAnyOf as l}from"@reduxjs/toolkit";import{API_ENDPOINTS as m,logger as p,getCustomHeaders as g,readAccessToken as v,AUTH_HEADERS as _,readDeviceToken as I,DEVICE_TOKEN_HEADER as S,clearLastResponseTime as E,setLastResponseTime as f,INDICATOR_TIMER_ACTIONS as T,generateUniqueId as b,handleSharedEvent as h,showToast as y,getTokenRefreshHandler as k,showErrorToast as V,isTokenAboutToExpire as M,readTokenExpiresAt as C,getStorageRef as w,clearSession as R,sessionExpired as N}from"@1interface/shared-core";const D={DISCONNECTED:"disconnected",CONNECTING:"connecting",CONNECTED:"connected"},x={IDLE:"idle",LISTENING:"listening",PROCESSING:"processing",SPEAKING:"speaking"},A={CASCADE:"cascade",REALTIME:"realtime"},L={OFFLINE:"offline",CONNECTING:"connecting",READY:"ready",IDLE:"idle",LISTENING:"listening",MUTED:"muted",PROCESSING:"processing",SPEAKING:"speaking"},P=15e3,O=[/rejected/i,/unauthori[sz]ed/i,/forbidden/i,/\b40[13]\b/],G=[/\b402\b/,/\b429\b/,/rate[\s-]?limit/i],B={SELECTED_MIC_ID:"voice_selectedMicId",SELECTED_SPEAKER_ID:"voice_selectedSpeakerId"},j={offline:{opacity:.15,playbackRate:.3,scale:1},connecting:{opacity:.4,playbackRate:.5,scale:1},ready:{opacity:.4,playbackRate:.4,scale:1},idle:{opacity:.6,playbackRate:.6,scale:1},listening:{opacity:.7,playbackRate:.7,scale:1},muted:{opacity:.7,playbackRate:.5,scale:1},processing:{opacity:1,playbackRate:1,scale:1},speaking:{opacity:1,playbackRate:1,scale:1.3}};function U(){return{connectionStatus:D.DISCONNECTED,pipelineStatus:x.IDLE,listeningEnabled:!1,muted:!1,serverMuted:!1,statusText:null,isAgentWorking:!1,responseStartTime:null,interimTranscript:null,botTurn:{active:!1,chatMsgId:null,segments:[]},sessionEndedSignal:0,selectedMicId:"",selectedSpeakerId:"",microphones:[],speakers:[]}}const q=e=>async t=>{const[n,r]=await Promise.all([e.getItem(B.SELECTED_MIC_ID),e.getItem(B.SELECTED_SPEAKER_ID)]);n&&t(F.actions.setSelectedMicId(n)),r&&t(F.actions.setSelectedSpeakerId(r))},F=u({name:"voice",initialState:U(),reducers:{setConnectionStatus(e,t){e.connectionStatus=t.payload},setPipelineStatus(e,t){e.pipelineStatus=t.payload},setListeningEnabled(e,t){e.listeningEnabled=t.payload},setStatusText(e,t){e.statusText=t.payload},setIsAgentWorking(e,t){e.isAgentWorking=t.payload},setResponseStartTime(e,t){e.responseStartTime=t.payload},setInterimTranscript(e,t){e.interimTranscript=t.payload},openBotTurn(e,t){e.botTurn={active:!0,chatMsgId:t.payload?.chatMsgId??null,segments:[]}},upsertBotSegment(e,t){const{id:n,text:r,spokenChars:i}=t.payload;e.botTurn.active=!0;const s=e.botTurn.segments.find(e=>e.id===n);s?(s.text=r,s.spokenChars=Math.max(s.spokenChars,i)):(e.botTurn.segments.push({id:n,text:r,spokenChars:i}),e.botTurn.segments.sort((e,t)=>e.id-t.id))},clearBotTurn(e){e.botTurn={active:!1,chatMsgId:null,segments:[]}},setMuted(e,t){e.muted=t.payload},setServerMuted(e,t){e.serverMuted=t.payload},setSelectedMicId(e,t){e.selectedMicId=t.payload},setSelectedSpeakerId(e,t){e.selectedSpeakerId=t.payload},setMicrophones(e,t){e.microphones=t.payload},setSpeakers(e,t){e.speakers=t.payload},signalSessionEnded(e){e.sessionEndedSignal+=1},resetVoiceState:()=>U(),resetIndicatorState(e){e.statusText=null,e.isAgentWorking=!1,e.responseStartTime=null,e.interimTranscript=null,e.serverMuted=!1,e.botTurn={active:!1,chatMsgId:null,segments:[]}}}}),{setConnectionStatus:W,setPipelineStatus:K,setListeningEnabled:$,setMuted:Z,setServerMuted:H,setStatusText:Y,setIsAgentWorking:z,setResponseStartTime:J,setInterimTranscript:Q,openBotTurn:X,upsertBotSegment:ee,clearBotTurn:te,setSelectedMicId:ne,setSelectedSpeakerId:re,setMicrophones:ie,setSpeakers:se,signalSessionEnded:ce,resetVoiceState:oe,resetIndicatorState:ae}=F.actions,ue=F.reducer,de=e(null),le=()=>{const e=t(de);if(!e)throw new Error("useAudioPlatform must be used inside <AudioPlatformProvider>. <VoiceProvider> mounts one by default; if you replaced VoiceProvider, you must wire your own.");return e};function me(){const e={};for(const[t,n]of Object.entries(g()))n&&(e[t]=n);const t=v();t&&(e[_.ACCESS_TOKEN]=t);const n=I();return n&&(e[S]=n),e}async function pe(e){try{const t=await fetch(m.WEBRTC_ICE(e),{headers:me()});return t.ok?(await t.json()).iceServers??[]:(p.warn({message:"Voice ICE fetch failed — continuing without TURN",api_name:"voice-webrtc-ice",status:t.status}),[])}catch(e){return p.warn({message:"Voice ICE fetch error — continuing without TURN",api_name:"voice-webrtc-ice",error_message:e instanceof Error?e.message:String(e)}),[]}}const ge="voice-user-",ve="Could not connect voice. Please try again later.",_e=(e,t=`${ge}${b()}`,n,r)=>({id:t,sender:"user",message:e,timestamp:(new Date).toISOString(),...n?.length?{documents:n}:{},...r?.length?{message_type:r}:{}}),Ie=(e,t)=>{const n=`${e}${b()}`;return{id:n,msg_id:n,sender:"agent",message:"",timestamp:(new Date).toISOString(),...t}};function Se(){const e=a(e=>e.voice.connectionStatus),t=a(e=>e.voice.pipelineStatus),n=a(e=>e.voice.listeningEnabled),r=a(e=>e.voice.muted);return e===D.CONNECTING?L.CONNECTING:e!==D.CONNECTED?L.OFFLINE:n?r?L.MUTED:t===x.PROCESSING?L.PROCESSING:t===x.SPEAKING?L.SPEAKING:t===x.LISTENING?L.LISTENING:L.IDLE:L.READY}const Ee=e(null),fe=({conversationId:e,voiceBridge:t,transportFactory:u,assistantStartsConversation:d})=>{const l=c(),g=le(),v=a(e=>e.voice.muted),_=a(e=>e.voice.serverMuted),I=a(e=>e.voice.pipelineStatus),S=a(e=>e.voice.isAgentWorking),w=a(e=>e.voice.sessionEndedSignal),{connect:R,disconnect:N,sendText:L,setMicEnabled:P,setInputDevice:B,setOutputDevice:j}=function(e,t,u,d){const l=c(),g=o(),v=le(),_=a(e=>e.auth.accessToken),I=n(t);r(()=>{I.current=t});const S=n(null),w=n(new Map),R=n(""),N=n(!1),L=n(!1),P=n(null),B=n(""),j=n(null),U=n(null),q=n(null),F=n(null),oe=n(!0),ae=n(null),ue=n(!1),de=n(""),Se=n(0),Ee=n(0),fe=n(!1),Te=n(0),be=n(null),he=n(null),ye=n(0),ke=n(0),Ve=n(null),Me=n(null),Ce=i(()=>{if(null!=be.current)return;const e=Date.now();be.current=e,l(J(e)),l(E())},[l]),we=i(()=>{const e=be.current;be.current=null,l(J(null)),null!=e&&l(f(Date.now()-e))},[l]),Re=i(()=>{be.current=null,l(J(null))},[l]),Ne=i((e=T.KEEP)=>{l(Y(null)),l(z(!1)),e===T.FINALIZE?we():e===T.CANCEL&&Re()},[l,we,Re]),[De]=s(()=>({setStatusText:e=>l(Y(e||null)),setIsAgentWorking:e=>l(z(e)),appendStreamText:()=>{},pushWidget:e=>{const t=Ie("voice-widget-",{widget:[e],message_type:["widget"]});I.current.setMessages(e=>[...e,t])},onPayment:e=>I.current.onPaymentInit?.(e),onRequestDocuments:e=>I.current.onRequestDocuments?.(e),onConversationTitle:e=>I.current.onConversationTitle?.(e),onError:e=>{p.error({message:"Voice RTVI server error",api_name:"voice-rtvi",error_message:e});const t=Ie("voice-error-",{message:"Something went wrong. Please try again.",message_type:["error"]});I.current.setMessages(e=>[...e,t])},onStatusActive:()=>{Ce()}})),xe=i(()=>{F.current&&(clearTimeout(F.current),F.current=null)},[]),Ae=i(()=>[...w.current.entries()].sort((e,t)=>e[0]-t[0]).map(([,e])=>e.text).join(" ").trim(),[]),Le=i(()=>{S.current=null,w.current=new Map,R.current="",l(te())},[l]),Pe=i(()=>{const e=S.current;if(e){const t=Ae();t&&I.current.updateStreamingMessage(e,t),I.current.commitStreamingMessages()}Le()},[Ae,Le]),Oe=i(e=>{xe(),ae.current&&(clearTimeout(ae.current),ae.current=null),ue.current=!1,de.current="",l(ie([])),l(se([])),q.current?.(),q.current=null,j.current=null,fe.current=!1,l($(!1)),l(K(x.IDLE)),l(W(D.DISCONNECTED)),l(Q(null)),l(H(!1)),P.current=null,B.current="",N.current=!1,L.current=!1,e?Pe():(I.current.discardStreamingMessages(),Le()),Ne(T.CANCEL)},[l,Ne,xe,Pe,Le]),Ge=i(()=>{if(S.current)return;const e=`voice-agent-${b()}`;S.current=e,w.current=new Map,R.current="",l(X({chatMsgId:e}))},[l]),Be=i(e=>{const t=e.spoken_progress?(e.spoken_progress?.accumulated_text??"")+(e.spoken_progress?.remaining_text??""):e.text??"";if(!t)return;Ge();const n=N.current?function(e,t){const n=(e.get(0)?.text??"")+t;return e.set(0,{text:n,completed:!1}),{id:0,text:n,spokenChars:n.length}}(w.current,t):function(e,t,n,r){const i=t.segment_id??0,s="completed"===t.spoken_status;if(s&&!e.has(i)){const t=[...e.entries()].find(([,e])=>e.text===n&&!e.completed);if(t)return t[1].completed=!0,{id:t[0],text:n,spokenChars:n.length}}const c=e.get(i);return e.set(i,{text:n,completed:s||!0===c?.completed}),{id:i,text:n,spokenChars:r&&t.spoken_progress?(t.spoken_progress.accumulated_text??"").length:n.length}}(w.current,e,t,L.current);l(ee(n));const r=Ae();r&&S.current&&r!==R.current&&(R.current=r,I.current.updateStreamingMessage(S.current,r))},[l,Ge,Ae]),je=i(t=>{const n=t,r=n?.type,i=n?.data;switch(p.debug({message:"Voice RTVI server-message received",api_name:"voice-rtvi",conversation_id:e??void 0,event_type:r??"unknown"}),r){case"status_update":h({kind:"status_update",statusText:i?.text??""},De);break;case"started-thinking":l(z(!0)),Ce();break;case"stopped-thinking":Ne(T.FINALIZE);break;case"clear_status":break;case"widget":i&&h({kind:"widget",widget:i},De);break;case"payment":i&&h({kind:"payment",payment:i},De);break;case"request_documents":i&&h({kind:"request_documents",requestDocuments:i},De);break;case"conversation_title":{const e="string"==typeof i?.text?i.text.trim():"";e&&h({kind:"conversation_title",title:e},De);break}case"session-ended":Me.current={reason:"string"==typeof i?.reason?i.reason:"ended"};break;default:p.debug({message:"Voice RTVI unhandled server-message",api_name:"voice-rtvi",event_type:r??"unknown"})}},[De,l,Ce,Ne,e]),Ue=n(()=>{}),qe=n(async()=>{}),Fe=i(t=>{const n=()=>t===Se.current;return{onConnected:()=>{if(!n())return;p.info({message:"Voice RTVI connected",api_name:"voice-rtvi",conversation_id:e??void 0}),l(W(D.CONNECTED)),l($(!0)),l(K(x.IDLE)),j.current&&j.current.isMicEnabled!==oe.current&&(p.debug({message:"Voice re-asserting mic state on reconnect",api_name:"voice-rtvi",conversation_id:e??void 0,enabled:oe.current}),j.current.enableMic(oe.current));const{selectedMicId:t,selectedSpeakerId:r}=g.getState().voice;t&&j.current?.updateMic(t),r&&j.current?.updateSpeaker(r);const i=t=>n=>p.warn({message:"Voice device enumeration failed",api_name:"voice-mic",conversation_id:e??void 0,device_kind:t,error_message:n instanceof Error?n.message:String(n)});j.current?.getAllMics().then(e=>{n()&&l(ie(e.map(e=>({deviceId:e.deviceId,label:e.label}))))}).catch(i("mics")),j.current?.getAllSpeakers().then(e=>{n()&&l(se(e.map(e=>({deviceId:e.deviceId,label:e.label}))))}).catch(i("speakers")),ue.current=!0},onBotReady:t=>{if(!n())return;const r=t?.about;N.current=r?.pipeline_mode===A.REALTIME,L.current=!0===r?.tts_word_timestamps,ye.current=0,ke.current=0,Te.current=0,p.info({message:"Voice RTVI bot-ready",api_name:"voice-rtvi",conversation_id:e??void 0,pipeline_mode:r?.pipeline_mode??"cascade",tts_word_timestamps:L.current,assistant_starts:d}),xe(),d&&(l(z(!0)),Ce())},onDisconnected:()=>{if(!n())return;p.info({message:"Voice RTVI disconnected",api_name:"voice-rtvi",conversation_id:e??void 0,drop_outcome:Ve.current??"transient",reconnect_attempts:ye.current}),Oe(!0);const t=Ee.current,r=Ve.current;Ve.current=null;const i=Me.current;return Me.current=null,i?(p.info({message:"Voice session ended by server — no reconnect",api_name:"voice-rtvi",conversation_id:e??void 0,reason:i.reason}),"idle_timeout"===i.reason&&y({title:"Voice ended",description:"It got quiet for a while, so we wrapped up. Start again anytime."}),void l(ce())):"terminal"===r?(p.warn({message:"Voice RTVI terminal error — no retry",api_name:"voice-rtvi"}),void l(ce())):"auth"===r?ke.current>=1||null==e?(p.warn({message:"Voice RTVI auth refresh budget exhausted — giving up",api_name:"voice-rtvi",conversation_id:e??void 0}),void l(ce())):(ke.current+=1,p.info({message:"Voice RTVI auth drop — refreshing token then reconnecting",api_name:"voice-rtvi",conversation_id:e??void 0,auth_refresh_count:ke.current}),void(async()=>{let n=!1;try{n=!!await(k()?.())}catch{}t===Ee.current&&(n?qe.current():(p.warn({message:"Voice RTVI token refresh failed — no reconnect",api_name:"voice-rtvi",conversation_id:e??void 0}),l(ce())))})()):ye.current>=3||null==e?(ye.current>=3&&(p.warn({message:"Voice RTVI auto-reconnect cap reached — giving up",api_name:"voice-rtvi",conversation_id:e??void 0,reconnect_attempts:ye.current}),V("Voice connection lost. Please try again.")),void l(ce())):(ye.current+=1,p.info({message:"Voice RTVI scheduling auto-reconnect",api_name:"voice-rtvi",conversation_id:e??void 0,reconnect_attempts:ye.current,delay_ms:1e3}),he.current&&clearTimeout(he.current),void(he.current=setTimeout(()=>{he.current=null,qe.current()},1e3)))},onError:t=>{if(!n())return;const r=t?.data,i=r?.error??r?.message??"Voice error";h({kind:"error",error:i},De),l(K(x.IDLE)),Ne(T.CANCEL),r?.fatal&&(O.some(e=>e.test(i))?Ve.current="auth":G.some(e=>e.test(i))&&(Ve.current="terminal"),p.warn({message:"Voice RTVI fatal error — closing transport",api_name:"voice-rtvi",conversation_id:e??void 0,drop_outcome:Ve.current??"generic"}),j.current?.disconnect().catch(()=>{}))},onTrackStarted:(t,r)=>{n()&&(r?.local||"audio"!==t.kind||(p.debug({message:"Voice RTVI bot audio track started",api_name:"voice-rtvi",conversation_id:e??void 0}),q.current?.(),q.current=v.attachBotAudioTrack(t)))},onAvailableMicsUpdated:e=>{n()&&(l(ie(e.map(e=>({deviceId:e.deviceId,label:e.label})))),ue.current&&(ae.current&&clearTimeout(ae.current),ae.current=setTimeout(()=>{if(ae.current=null,!j.current)return;const{selectedMicId:e,microphones:t}=g.getState().voice;p.info({message:"Voice audio devices changed",api_name:"voice-mic",microphones:t.map(e=>e.label).join(", ")}),p.debug({message:"Voice re-acquiring mic (device change)",api_name:"voice-rtvi",enabled:oe.current,device:e||"default"}),j.current.updateMic(e)},300)))},onAvailableSpeakersUpdated:e=>{if(!n())return;if(l(se(e.map(e=>({deviceId:e.deviceId,label:e.label})))),!ue.current)return;const t=e.map(e=>e.label).join(", ");t!==de.current&&(de.current=t,p.info({message:"Voice audio output devices changed",api_name:"voice-mic",speakers:t}))},onMicUpdated:t=>{if(!n())return;const r=j.current;r&&r.isMicEnabled!==oe.current&&(p.debug({message:"Voice re-asserting mic state after track swap",api_name:"voice-rtvi",conversation_id:e??void 0,enabled:oe.current,device:t?.label}),r.enableMic(oe.current))},onUserMuteStarted:()=>{n()&&p.debug({message:"Voice RTVI user-mute-started (ignored)",api_name:"voice-rtvi",conversation_id:e??void 0})},onUserMuteStopped:()=>{n()&&(p.debug({message:"Voice RTVI user-mute-stopped",api_name:"voice-rtvi",conversation_id:e??void 0,honored:d}),d&&l(H(!1)))},onUserStartedSpeaking:()=>{n()&&(p.debug({message:"Voice RTVI user started speaking",api_name:"voice-rtvi",conversation_id:e??void 0}),P.current=null,B.current="",l(Q(null)),l(K(x.LISTENING)))},onUserStoppedSpeaking:()=>{if(!n())return;p.debug({message:"Voice RTVI user stopped speaking",api_name:"voice-rtvi",conversation_id:e??void 0,has_interim:!!B.current.trim()}),l(K(x.PROCESSING)),l(z(!0)),l(Y(null)),Ce();const t=B.current.trim();if(t){const e=`${ge}${b()}`;P.current=e,l(Q(null)),I.current.setMessages(n=>[...n,_e(t,e)])}},onUserTranscript:t=>{if(!n())return;if(!t.final)return void(t.text&&(B.current=t.text,N.current||l(Q(t.text))));l(Q(null));const r=t.text&&t.text.trim()?t.text:"";p.debug({message:"Voice RTVI user transcript (final)",api_name:"voice-rtvi",conversation_id:e??void 0,has_text:!!r});const i=P.current;P.current=null,i?r&&I.current.setMessages(e=>e.map(e=>e.id===i?{...e,message:r}:e)):r&&I.current.setMessages(e=>[...e,_e(r)])},onBotStartedSpeaking:()=>{n()&&(p.debug({message:"Voice RTVI bot started speaking",api_name:"voice-rtvi",conversation_id:e??void 0}),Ge(),l(K(x.SPEAKING)),Ne(T.FINALIZE))},onBotOutput:e=>{n()&&Be(e)},onBotStoppedSpeaking:()=>{n()&&(p.debug({message:"Voice RTVI bot stopped speaking",api_name:"voice-rtvi",conversation_id:e??void 0}),Pe(),l(K(x.IDLE)),Ne(T.FINALIZE),d&&g.getState().voice.serverMuted&&(p.warn({message:"Voice greeting mute-hold released on bot-turn end (server unmute missing)",api_name:"voice-rtvi",conversation_id:e??void 0}),l(H(!1))))},onServerMessage:e=>{n()&&je(e)}}},[l,g,v,xe,Oe,Ne,Ce,Be,Ge,Pe,je,De,d,e]),We=i(async()=>{if(j.current||fe.current)return;if(!e)return;if(fe.current=!0,p.info({message:"Voice RTVI connecting",api_name:"voice-rtvi",conversation_id:e,assistant_starts:d}),d&&(oe.current=!1,l(H(!0)),p.debug({message:"Voice greeting mute-hold armed",api_name:"voice-rtvi",conversation_id:e})),M(C())&&Te.current<1){Te.current+=1,p.debug({message:"Voice pre-connect token refresh (freshness gate)",api_name:"voice-rtvi",conversation_id:e,freshness_refresh_count:Te.current});const t=Ee.current;try{await Promise.resolve(k()?.())}catch{}if(t!==Ee.current)return}const t=++Se.current,n=Ee.current;Me.current=null,Ve.current=null,l(W(D.CONNECTING));const r=()=>n!==Ee.current||t!==Se.current;try{const[{PipecatClient:n,LogLevel:i,RTVIMessage:s},c]=await Promise.all([import("./index.module-S2alA3-r.js"),pe(e)]);if(r())return;U.current=s;const o=new n({transport:u(),enableMic:!0,enableCam:!1,callbacks:Fe(t)});if(o.setLogLevel(i.WARN),r())return void o.disconnect().catch(()=>{});j.current=o,p.debug({message:"Voice RTVI client constructed",api_name:"voice-rtvi",conversation_id:e,enable_mic:oe.current}),xe(),F.current=setTimeout(()=>{p.warn({message:"Voice RTVI connect timed out (no bot-ready)",api_name:"voice-rtvi"}),V("Voice connection timed out. Please try again."),l(ce()),Ue.current()},15e3),await o.connect({webrtcRequestParams:{endpoint:m.WEBRTC_OFFER(e),headers:new Headers(me())},iceConfig:{iceServers:c}})}catch(e){const n=(e=>{const t="object"==typeof e&&null!==e&&"status"in e?e.status:void 0;return"number"==typeof t?t:void 0})(e);p.error({message:"Voice RTVI connect failed",api_name:"voice-rtvi",status:n,error_message:e instanceof Error?e.message:String(e)});const r=(e=>void 0!==e&&e>=400&&e<500)(n),i=r?await(async(e,t)=>{const n=429===t?"Voice session limit reached. Please try again later.":ve,r="object"==typeof e&&null!==e&&"cause"in e?e.cause:void 0;if(!(r instanceof Response))return n;try{const e=await r.json(),t=e?.data?.error_message??e?.data?.message;return"string"==typeof t?t:n}catch{return n}})(e,n):ve;t===Se.current&&(V(i),r&&(he.current&&(clearTimeout(he.current),he.current=null),ye.current=0,l(ce())),Oe(!1))}},[e,l,u,Fe,xe,Oe,d]),Ke=i((t=!0)=>{p.info({message:"Voice RTVI disconnect (intentional teardown)",api_name:"voice-rtvi",conversation_id:e??void 0,commit_turn:t}),Ee.current+=1,Se.current+=1,Te.current=0,he.current&&(clearTimeout(he.current),he.current=null),ye.current=0,ke.current=0,Ve.current=null,oe.current=!0,l(Z(!1));const n=j.current;n?.disconnect().catch(e=>{p.warn({message:"Voice RTVI disconnect threw",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}),Oe(t)},[Oe,l,e]),$e=i(e=>{oe.current=e,j.current&&(p.debug({message:"Voice mic enable toggled",api_name:"voice-rtvi",enabled:e}),j.current.enableMic(e))},[]),Ze=i(e=>{l(ne(e)),j.current?.updateMic(e)},[l]),He=i(e=>{l(re(e)),j.current?.updateSpeaker(e)},[l]),Ye=i((t,n)=>{const r=j.current;if(!r)return;const i=n?.modelContext;p.info({message:"Voice RTVI send-text",api_name:"voice-rtvi",conversation_id:e??void 0,run_immediately:!0,has_model_context:Boolean(i?.length)});try{const e=U.current;i?.length&&e?r.transport.sendMessage(new e("send-text",{content:t,options:{run_immediately:!0,audio_response:!0},model_context:i})):r.sendText(t,{run_immediately:!0,audio_response:!0})}catch(e){return void p.error({message:"Voice RTVI send-text failed",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}S.current&&Pe(),I.current.setMessages(e=>[...e,_e(t,void 0,n?.documents,n?.messageType)]),l(z(!0)),l(Y(null)),Ce(),l(K(x.PROCESSING))},[l,Ce,Pe,e]);r(()=>{Ue.current=Ke},[Ke]),r(()=>{qe.current=We},[We]);const ze=n(e),Je=n(!1);r(()=>{const t=ze.current;if(ze.current=e,t===e)return;const n=fe.current;n&&(Je.current=!0),n&&Ke(!1),Je.current&&null!=e&&(Je.current=!1,We())},[e,Ke,We]);const Qe=n(_);return r(()=>{const e=Qe.current;Qe.current=_,e&&!_&&fe.current&&Ke(!1)},[_,Ke]),r(()=>()=>{Ue.current()},[]),{connect:We,disconnect:Ke,sendText:Ye,setMicEnabled:$e,setInputDevice:Ze,setOutputDevice:He}}(e,t,u,d);r(()=>{P(!v&&!_)},[v,_,P]);const U=Se();r(()=>(t.setVoiceSendText(L),()=>t.setVoiceSendText(null)),[t,L]);const q=n(!1);return{displayState:U,pipelineStatus:I,isAgentWorking:S,enterVoiceMode:i(async()=>{if(q.current)return V("Microphone permission changed — please reload the page to apply the new setting."),!1;const e=await g.requestMicPermission();if(!e.granted){const t=e.error;return p.error({message:"Mic permission denied",api_name:"voice-mic",error_message:t instanceof Error?t.message:t?String(t):void 0}),q.current=!0,V("Microphone access is required for voice mode. Please allow it in your browser's site settings and reload the page."),!1}return R(),!0},[g,R]),exitVoiceMode:i(()=>{N()},[N]),toggleMute:i(()=>{l(Z(!v))},[l,v]),sendText:L,muted:v,serverMuted:_,sessionEndedSignal:w,setInputDevice:B,setOutputDevice:j}},Te=()=>{const e=t(Ee);if(!e)throw new Error("useVoice must be used within VoiceProvider");return e},be=e({strategyActive:!1,voiceSlot:null,onEnterVoice:null,onFloatingOrbClick:null,voiceInputHidden:!1,headerCenterSlot:null,floatOrbPositionRef:null}),he=()=>t(be);function ye(){return{microphones:a(e=>e.voice.microphones),speakers:a(e=>e.voice.speakers),selectedMicId:a(e=>e.voice.selectedMicId),selectedSpeakerId:a(e=>e.voice.selectedSpeakerId)}}const ke=d();ke.startListening({actionCreator:ne,effect:async e=>{const t=w();t&&await t.setItem(B.SELECTED_MIC_ID,e.payload)}}),ke.startListening({actionCreator:re,effect:async e=>{const t=w();t&&await t.setItem(B.SELECTED_SPEAKER_ID,e.payload)}}),ke.startListening({matcher:l(R,N),effect:async(e,t)=>{const n=w();n&&await Promise.all([n.removeItem(B.SELECTED_MIC_ID),n.removeItem(B.SELECTED_SPEAKER_ID)]),t.dispatch(oe())}});const Ve=e=>{const t=document.createElement("audio");return t.autoplay=!0,t.style.display="none",t.srcObject=new MediaStream([e]),document.body.appendChild(t),()=>{try{t.pause(),t.srcObject=null,t.remove()}catch{}}},Me={attachBotAudioTrack:Ve,requestMicPermission:async function(){try{const e=await(navigator.permissions?.query({name:"microphone"}).catch(()=>null));return p.debug({message:"Voice mic permission probe",api_name:"voice-mic",permission_state:e?.state??"unavailable"}),"granted"===e?.state||(await navigator.mediaDevices.getUserMedia({audio:!0})).getTracks().forEach(e=>e.stop()),{granted:!0}}catch(e){return p.warn({message:"Voice mic permission denied",api_name:"voice-mic",error_message:e instanceof Error?e.message:String(e)}),{granted:!1,error:e}}}};export{de as AudioPlatformContext,j as ORB_STATE_CONFIG,D as VOICE_CONNECTION_STATUSES,P as VOICE_CONNECT_TIMEOUT_MS,L as VOICE_DISPLAY_STATES,A as VOICE_PIPELINE_MODES,x as VOICE_PIPELINE_STATUSES,B as VOICE_STORAGE_KEYS,Ee as VoiceContext,be as VoiceUIContext,Ve as attachBrowserBotAudioTrack,Me as browserAudioPlatform,te as clearBotTurn,q as hydrateVoice,X as openBotTurn,ae as resetIndicatorState,W as setConnectionStatus,Q as setInterimTranscript,z as setIsAgentWorking,$ as setListeningEnabled,Z as setMuted,K as setPipelineStatus,J as setResponseStartTime,ne as setSelectedMicId,re as setSelectedSpeakerId,H as setServerMuted,Y as setStatusText,ee as upsertBotSegment,le as useAudioPlatform,ye as useMediaDevices,Te as useVoice,Se as useVoiceDisplayState,fe as useVoiceProviderValue,he as useVoiceUI,ke as voiceListenerMiddleware,ue as voiceReducer};
1
+ import{x as e,L as t,y as s,M as a}from"./sessionHelpers-BIwU9Fpt.js";import{N as o,O as r,e as i,v as n,t as c,P as E,r as l,V as I,A as S,Q as m,E as d,R as u,G as C,S as p,d as _,f as T,h as f,b as O,w as A,c as P,C as L,g as M,i as V,H as v,u as D,T as N,U as R,W as g}from"./sessionHelpers-BIwU9Fpt.js";import{createContext as y,useContext as h}from"react";import{useSelector as k}from"react-redux";import{createListenerMiddleware as x,isAnyOf as b}from"@reduxjs/toolkit";import{getStorageRef as w,clearSession as B,sessionExpired as j}from"@1interface/shared-core";import{a as H,b as U}from"./browserAudioPlatform-BlQ_Ssdk.js";const F=y({strategyActive:!1,voiceSlot:null,onEnterVoice:null,onFloatingOrbClick:null,voiceInputHidden:!1,headerCenterSlot:null,floatOrbPositionRef:null}),G=()=>h(F);function K(){return{microphones:k(e=>e.voice.microphones),speakers:k(e=>e.voice.speakers),selectedMicId:k(e=>e.voice.selectedMicId),selectedSpeakerId:k(e=>e.voice.selectedSpeakerId)}}const W=x();W.startListening({actionCreator:e,effect:async e=>{const s=w();s&&await s.setItem(t.SELECTED_MIC_ID,e.payload)}}),W.startListening({actionCreator:s,effect:async e=>{const s=w();s&&await s.setItem(t.SELECTED_SPEAKER_ID,e.payload)}}),W.startListening({matcher:b(B,j),effect:async(e,s)=>{const o=w();o&&await Promise.all([o.removeItem(t.SELECTED_MIC_ID),o.removeItem(t.SELECTED_SPEAKER_ID)]),s.dispatch(a())}});export{o as AudioPlatformContext,r as ORB_STATE_CONFIG,i as VOICE_CONNECTION_STATUSES,n as VOICE_CONNECT_ERROR_MESSAGE,c as VOICE_CONNECT_TIMEOUT_MS,E as VOICE_DISPLAY_STATES,l as VOICE_PIPELINE_MODES,I as VOICE_PIPELINE_STATUSES,S as VOICE_SAMPLE_RATE,t as VOICE_STORAGE_KEYS,m as VoiceContext,F as VoiceUIContext,H as attachBrowserBotAudioTrack,U as browserAudioPlatform,d as clearBotTurn,u as hydrateVoice,C as openBotTurn,p as resetIndicatorState,_ as setConnectionStatus,T as setInterimTranscript,f as setIsAgentWorking,O as setListeningEnabled,A as setMuted,P as setPipelineStatus,L as setResponseStartTime,e as setSelectedMicId,s as setSelectedSpeakerId,M as setServerMuted,V as setStatusText,v as upsertBotSegment,D as useAudioPlatform,K as useMediaDevices,N as useVoice,R as useVoiceDisplayState,G as useVoiceUI,W as voiceListenerMiddleware,g as voiceReducer};
@@ -0,0 +1 @@
1
+ "use strict";const e=require("react"),t=require("react-redux"),s=require("@reduxjs/toolkit"),o=require("@1interface/shared-core"),n={DISCONNECTED:"disconnected",CONNECTING:"connecting",CONNECTED:"connected"},r={IDLE:"idle",LISTENING:"listening",PROCESSING:"processing",SPEAKING:"speaking"},i={OFFLINE:"offline",CONNECTING:"connecting",READY:"ready",IDLE:"idle",LISTENING:"listening",MUTED:"muted",PROCESSING:"processing",SPEAKING:"speaking"},a={SELECTED_MIC_ID:"voice_selectedMicId",SELECTED_SPEAKER_ID:"voice_selectedSpeakerId"};function c(){return{connectionStatus:n.DISCONNECTED,pipelineStatus:r.IDLE,listeningEnabled:!1,muted:!1,serverMuted:!1,statusText:null,isAgentWorking:!1,responseStartTime:null,interimTranscript:null,botTurn:{active:!1,chatMsgId:null,segments:[]},sessionEndedSignal:0,selectedMicId:"",selectedSpeakerId:"",microphones:[],speakers:[]}}const d=c(),p=s.createSlice({name:"voice",initialState:d,reducers:{setConnectionStatus(e,t){e.connectionStatus=t.payload},setPipelineStatus(e,t){e.pipelineStatus=t.payload},setListeningEnabled(e,t){e.listeningEnabled=t.payload},setStatusText(e,t){e.statusText=t.payload},setIsAgentWorking(e,t){e.isAgentWorking=t.payload},setResponseStartTime(e,t){e.responseStartTime=t.payload},setInterimTranscript(e,t){e.interimTranscript=t.payload},openBotTurn(e,t){e.botTurn={active:!0,chatMsgId:t.payload?.chatMsgId??null,segments:[]}},upsertBotSegment(e,t){const{id:s,text:o,spokenChars:n}=t.payload;e.botTurn.active=!0;const r=e.botTurn.segments.find(e=>e.id===s);r?(r.text=o,r.spokenChars=Math.max(r.spokenChars,n)):(e.botTurn.segments.push({id:s,text:o,spokenChars:n}),e.botTurn.segments.sort((e,t)=>e.id-t.id))},clearBotTurn(e){e.botTurn={active:!1,chatMsgId:null,segments:[]}},setMuted(e,t){e.muted=t.payload},setServerMuted(e,t){e.serverMuted=t.payload},setSelectedMicId(e,t){e.selectedMicId=t.payload},setSelectedSpeakerId(e,t){e.selectedSpeakerId=t.payload},setMicrophones(e,t){e.microphones=t.payload},setSpeakers(e,t){e.speakers=t.payload},signalSessionEnded(e){e.sessionEndedSignal+=1},resetVoiceState:()=>c(),resetIndicatorState(e){e.statusText=null,e.isAgentWorking=!1,e.responseStartTime=null,e.interimTranscript=null,e.serverMuted=!1,e.botTurn={active:!1,chatMsgId:null,segments:[]}}}}),{setConnectionStatus:E,setPipelineStatus:u,setListeningEnabled:l,setMuted:S,setServerMuted:I,setStatusText:T,setIsAgentWorking:g,setResponseStartTime:_,setInterimTranscript:C,openBotTurn:x,upsertBotSegment:N,clearBotTurn:m,setSelectedMicId:O,setSelectedSpeakerId:R,setMicrophones:M,setSpeakers:A,signalSessionEnded:y,resetVoiceState:D,resetIndicatorState:k}=p.actions,v=p.reducer;function P(){const e=t.useSelector(e=>e.voice.connectionStatus),s=t.useSelector(e=>e.voice.pipelineStatus),o=t.useSelector(e=>e.voice.listeningEnabled),a=t.useSelector(e=>e.voice.muted);return e===n.CONNECTING?i.CONNECTING:e!==n.CONNECTED?i.OFFLINE:o?a?i.MUTED:s===r.PROCESSING?i.PROCESSING:s===r.SPEAKING?i.SPEAKING:s===r.LISTENING?i.LISTENING:i.IDLE:i.READY}const V=e.createContext(null),b=()=>{const t=e.useContext(V);if(!t)throw new Error("useAudioPlatform must be used inside <AudioPlatformProvider>. <VoiceProvider> mounts one by default; if you replaced VoiceProvider, you must wire your own.");return t},h=e.createContext(null),G="voice-user-";exports.AudioPlatformContext=V,exports.ORB_STATE_CONFIG={offline:{opacity:.15,playbackRate:.3,scale:1},connecting:{opacity:.4,playbackRate:.5,scale:1},ready:{opacity:.4,playbackRate:.4,scale:1},idle:{opacity:.6,playbackRate:.6,scale:1},listening:{opacity:.7,playbackRate:.7,scale:1},muted:{opacity:.7,playbackRate:.5,scale:1},processing:{opacity:1,playbackRate:1,scale:1},speaking:{opacity:1,playbackRate:1,scale:1.3}},exports.VOICE_AGENT_ID_PREFIX="voice-agent-",exports.VOICE_AUTH_ERROR_PATTERNS=[/rejected/i,/unauthori[sz]ed/i,/forbidden/i,/\b40[13]\b/],exports.VOICE_CONNECTION_STATUSES=n,exports.VOICE_CONNECT_ERROR_MESSAGE="Could not connect voice. Please try again later.",exports.VOICE_CONNECT_TIMEOUT_MS=15e3,exports.VOICE_DISPLAY_STATES=i,exports.VOICE_ERROR_ID_PREFIX="voice-error-",exports.VOICE_GENERIC_ERROR_MESSAGE="Something went wrong. Please try again.",exports.VOICE_MAX_AUTH_REFRESHES=1,exports.VOICE_MAX_AUTO_RECONNECTS=3,exports.VOICE_PIPELINE_MODES={CASCADE:"cascade",REALTIME:"realtime"},exports.VOICE_PIPELINE_STATUSES=r,exports.VOICE_RECONNECT_DELAY_MS=1e3,exports.VOICE_SAMPLE_RATE=24e3,exports.VOICE_SERVER_MSG_TYPES={STATUS_UPDATE:"status_update",STARTED_THINKING:"started-thinking",STOPPED_THINKING:"stopped-thinking",CLEAR_STATUS:"clear_status",WIDGET:"widget",PAYMENT:"payment",REQUEST_DOCUMENTS:"request_documents",CONVERSATION_TITLE:"conversation_title",SESSION_ENDED:"session-ended"},exports.VOICE_STORAGE_KEYS=a,exports.VOICE_TERMINAL_ERROR_PATTERNS=[/\b402\b/,/\b429\b/,/rate[\s-]?limit/i],exports.VOICE_THINKING_SOUND_PATH="/sounds/thinking-sound.mp3",exports.VOICE_THINKING_SOUND_VOLUME=.5,exports.VOICE_USER_ID_PREFIX=G,exports.VOICE_WIDGET_ID_PREFIX="voice-widget-",exports.VoiceContext=h,exports.clearBotTurn=m,exports.hydrateVoice=e=>async t=>{const[s,o]=await Promise.all([e.getItem(a.SELECTED_MIC_ID),e.getItem(a.SELECTED_SPEAKER_ID)]);s&&t(p.actions.setSelectedMicId(s)),o&&t(p.actions.setSelectedSpeakerId(o))},exports.makeAgentMessage=(e,t)=>{const s=`${e}${o.generateUniqueId()}`;return{id:s,msg_id:s,sender:"agent",message:"",timestamp:(new Date).toISOString(),...t}},exports.makeUserMessage=(e,t=`${G}${o.generateUniqueId()}`,s,n)=>({id:t,sender:"user",message:e,timestamp:(new Date).toISOString(),...s?.length?{documents:s}:{},...n?.length?{message_type:n}:{}}),exports.openBotTurn=x,exports.resetIndicatorState=k,exports.resetVoiceState=D,exports.setConnectionStatus=E,exports.setInterimTranscript=C,exports.setIsAgentWorking=g,exports.setListeningEnabled=l,exports.setMicrophones=M,exports.setMuted=S,exports.setPipelineStatus=u,exports.setResponseStartTime=_,exports.setSelectedMicId=O,exports.setSelectedSpeakerId=R,exports.setServerMuted=I,exports.setSpeakers=A,exports.setStatusText=T,exports.signalSessionEnded=y,exports.upsertBotSegment=N,exports.useAudioPlatform=b,exports.useVoice=()=>{const t=e.useContext(h);if(!t)throw new Error("useVoice must be used within VoiceProvider");return t},exports.useVoiceContextValue=(s,{voiceBridge:n})=>{const r=t.useDispatch(),i=b(),a=t.useSelector(e=>e.voice.muted),c=t.useSelector(e=>e.voice.serverMuted),d=t.useSelector(e=>e.voice.pipelineStatus),p=t.useSelector(e=>e.voice.isAgentWorking),E=t.useSelector(e=>e.voice.sessionEndedSignal),{connect:u,disconnect:l,sendText:I,setMicEnabled:T,setInputDevice:g,setOutputDevice:_,primeAudio:C}=s;e.useEffect(()=>{T(!a&&!c)},[a,c,T]);const x=P();e.useEffect(()=>(n.setVoiceSendText(I),()=>n.setVoiceSendText(null)),[n,I]);const N=e.useRef(!1),m=e.useCallback(async()=>{if(N.current)return o.showErrorToast("Microphone permission changed — please reload the page to apply the new setting."),!1;C?.();const e=await i.requestMicPermission();if(!e.granted){const t=e.error;return o.logger.error({message:"Mic permission denied",api_name:"voice-mic",error_message:t instanceof Error?t.message:t?String(t):void 0}),N.current=!0,o.showErrorToast("Microphone access is required for voice mode. Please allow it in your browser's site settings and reload the page."),!1}return u(),!0},[i,u,C]),O=e.useCallback(()=>{l()},[l]),R=e.useCallback(()=>{r(S(!a))},[r,a]);return e.useMemo(()=>({displayState:x,pipelineStatus:d,isAgentWorking:p,enterVoiceMode:m,exitVoiceMode:O,toggleMute:R,sendText:I,muted:a,serverMuted:c,sessionEndedSignal:E,setInputDevice:g,setOutputDevice:_}),[x,d,p,m,O,R,I,a,c,E,g,_])},exports.useVoiceDisplayState=P,exports.voiceSlice_default=v;
@@ -0,0 +1 @@
1
+ import{createContext as e,useContext as t,useEffect as s,useRef as a,useCallback as n,useMemo as i}from"react";import{useSelector as o,useDispatch as r}from"react-redux";import{createSlice as c}from"@reduxjs/toolkit";import{showErrorToast as d,logger as l,generateUniqueId as u}from"@1interface/shared-core";const p={DISCONNECTED:"disconnected",CONNECTING:"connecting",CONNECTED:"connected"},S={IDLE:"idle",LISTENING:"listening",PROCESSING:"processing",SPEAKING:"speaking"},g={CASCADE:"cascade",REALTIME:"realtime"},E={STATUS_UPDATE:"status_update",STARTED_THINKING:"started-thinking",STOPPED_THINKING:"stopped-thinking",CLEAR_STATUS:"clear_status",WIDGET:"widget",PAYMENT:"payment",REQUEST_DOCUMENTS:"request_documents",CONVERSATION_TITLE:"conversation_title",SESSION_ENDED:"session-ended"},m={OFFLINE:"offline",CONNECTING:"connecting",READY:"ready",IDLE:"idle",LISTENING:"listening",MUTED:"muted",PROCESSING:"processing",SPEAKING:"speaking"},I=15e3,T=1,N=[/rejected/i,/unauthori[sz]ed/i,/forbidden/i,/\b40[13]\b/],y=[/\b402\b/,/\b429\b/,/rate[\s-]?limit/i],v=1e3,C=3,M=24e3,k="/sounds/thinking-sound.mp3",D=.5,b={SELECTED_MIC_ID:"voice_selectedMicId",SELECTED_SPEAKER_ID:"voice_selectedSpeakerId"},h={offline:{opacity:.15,playbackRate:.3,scale:1},connecting:{opacity:.4,playbackRate:.5,scale:1},ready:{opacity:.4,playbackRate:.4,scale:1},idle:{opacity:.6,playbackRate:.6,scale:1},listening:{opacity:.7,playbackRate:.7,scale:1},muted:{opacity:.7,playbackRate:.5,scale:1},processing:{opacity:1,playbackRate:1,scale:1},speaking:{opacity:1,playbackRate:1,scale:1.3}};function A(){return{connectionStatus:p.DISCONNECTED,pipelineStatus:S.IDLE,listeningEnabled:!1,muted:!1,serverMuted:!1,statusText:null,isAgentWorking:!1,responseStartTime:null,interimTranscript:null,botTurn:{active:!1,chatMsgId:null,segments:[]},sessionEndedSignal:0,selectedMicId:"",selectedSpeakerId:"",microphones:[],speakers:[]}}const P=e=>async t=>{const[s,a]=await Promise.all([e.getItem(b.SELECTED_MIC_ID),e.getItem(b.SELECTED_SPEAKER_ID)]);s&&t(_.actions.setSelectedMicId(s)),a&&t(_.actions.setSelectedSpeakerId(a))},_=c({name:"voice",initialState:A(),reducers:{setConnectionStatus(e,t){e.connectionStatus=t.payload},setPipelineStatus(e,t){e.pipelineStatus=t.payload},setListeningEnabled(e,t){e.listeningEnabled=t.payload},setStatusText(e,t){e.statusText=t.payload},setIsAgentWorking(e,t){e.isAgentWorking=t.payload},setResponseStartTime(e,t){e.responseStartTime=t.payload},setInterimTranscript(e,t){e.interimTranscript=t.payload},openBotTurn(e,t){e.botTurn={active:!0,chatMsgId:t.payload?.chatMsgId??null,segments:[]}},upsertBotSegment(e,t){const{id:s,text:a,spokenChars:n}=t.payload;e.botTurn.active=!0;const i=e.botTurn.segments.find(e=>e.id===s);i?(i.text=a,i.spokenChars=Math.max(i.spokenChars,n)):(e.botTurn.segments.push({id:s,text:a,spokenChars:n}),e.botTurn.segments.sort((e,t)=>e.id-t.id))},clearBotTurn(e){e.botTurn={active:!1,chatMsgId:null,segments:[]}},setMuted(e,t){e.muted=t.payload},setServerMuted(e,t){e.serverMuted=t.payload},setSelectedMicId(e,t){e.selectedMicId=t.payload},setSelectedSpeakerId(e,t){e.selectedSpeakerId=t.payload},setMicrophones(e,t){e.microphones=t.payload},setSpeakers(e,t){e.speakers=t.payload},signalSessionEnded(e){e.sessionEndedSignal+=1},resetVoiceState:()=>A(),resetIndicatorState(e){e.statusText=null,e.isAgentWorking=!1,e.responseStartTime=null,e.interimTranscript=null,e.serverMuted=!1,e.botTurn={active:!1,chatMsgId:null,segments:[]}}}}),{setConnectionStatus:f,setPipelineStatus:O,setListeningEnabled:R,setMuted:G,setServerMuted:L,setStatusText:w,setIsAgentWorking:x,setResponseStartTime:V,setInterimTranscript:K,openBotTurn:W,upsertBotSegment:B,clearBotTurn:U,setSelectedMicId:F,setSelectedSpeakerId:q,setMicrophones:Y,setSpeakers:$,signalSessionEnded:j,resetVoiceState:H,resetIndicatorState:z}=_.actions,Q=_.reducer;function J(){const e=o(e=>e.voice.connectionStatus),t=o(e=>e.voice.pipelineStatus),s=o(e=>e.voice.listeningEnabled),a=o(e=>e.voice.muted);return e===p.CONNECTING?m.CONNECTING:e!==p.CONNECTED?m.OFFLINE:s?a?m.MUTED:t===S.PROCESSING?m.PROCESSING:t===S.SPEAKING?m.SPEAKING:t===S.LISTENING?m.LISTENING:m.IDLE:m.READY}const X=e(null),Z=()=>{const e=t(X);if(!e)throw new Error("useAudioPlatform must be used inside <AudioPlatformProvider>. <VoiceProvider> mounts one by default; if you replaced VoiceProvider, you must wire your own.");return e},ee=e(null),te=(e,{voiceBridge:t})=>{const c=r(),u=Z(),p=o(e=>e.voice.muted),S=o(e=>e.voice.serverMuted),g=o(e=>e.voice.pipelineStatus),E=o(e=>e.voice.isAgentWorking),m=o(e=>e.voice.sessionEndedSignal),{connect:I,disconnect:T,sendText:N,setMicEnabled:y,setInputDevice:v,setOutputDevice:C,primeAudio:M}=e;s(()=>{y(!p&&!S)},[p,S,y]);const k=J();s(()=>(t.setVoiceSendText(N),()=>t.setVoiceSendText(null)),[t,N]);const D=a(!1),b=n(async()=>{if(D.current)return d("Microphone permission changed — please reload the page to apply the new setting."),!1;M?.();const e=await u.requestMicPermission();if(!e.granted){const t=e.error;return l.error({message:"Mic permission denied",api_name:"voice-mic",error_message:t instanceof Error?t.message:t?String(t):void 0}),D.current=!0,d("Microphone access is required for voice mode. Please allow it in your browser's site settings and reload the page."),!1}return I(),!0},[u,I,M]),h=n(()=>{T()},[T]),A=n(()=>{c(G(!p))},[c,p]);return i(()=>({displayState:k,pipelineStatus:g,isAgentWorking:E,enterVoiceMode:b,exitVoiceMode:h,toggleMute:A,sendText:N,muted:p,serverMuted:S,sessionEndedSignal:m,setInputDevice:v,setOutputDevice:C}),[k,g,E,b,h,A,N,p,S,m,v,C])},se=()=>{const e=t(ee);if(!e)throw new Error("useVoice must be used within VoiceProvider");return e},ae="voice-user-",ne="voice-widget-",ie="voice-agent-",oe="voice-error-",re="Something went wrong. Please try again.",ce="Could not connect voice. Please try again later.",de=(e,t=`${ae}${u()}`,s,a)=>({id:t,sender:"user",message:e,timestamp:(new Date).toISOString(),...s?.length?{documents:s}:{},...a?.length?{message_type:a}:{}}),le=(e,t)=>{const s=`${e}${u()}`;return{id:s,msg_id:s,sender:"agent",message:"",timestamp:(new Date).toISOString(),...t}};export{M as A,E as B,V as C,le as D,U as E,ie as F,W as G,B as H,re as I,oe as J,ne as K,b as L,H as M,X as N,h as O,m as P,ee as Q,P as R,z as S,se as T,J as U,S as V,Q as W,k as X,D as Y,$ as a,R as b,O as c,f as d,p as e,K as f,L as g,x as h,w as i,ae as j,N as k,y as l,de as m,j as n,T as o,C as p,v as q,g as r,Y as s,I as t,Z as u,ce as v,G as w,F as x,q as y,te as z};
@@ -0,0 +1 @@
1
+ "use strict";const e=require("react"),t=require("react-redux"),n=require("./sessionHelpers-ALyIwLPk.cjs"),s=require("@1interface/shared-core");exports.foldCascadeBotOutput=function(e,t,n,s){const r=t.segment_id??0,a="completed"===t.spoken_status;if(a&&!e.has(r)){const t=[...e.entries()].find(([,e])=>e.text===n&&!e.completed);if(t)return t[1].completed=!0,{id:t[0],text:n,spokenChars:n.length}}const u=e.get(r);return e.set(r,{text:n,completed:a||!0===u?.completed}),{id:r,text:n,spokenChars:s&&t.spoken_progress?(t.spoken_progress.accumulated_text??"").length:n.length}},exports.foldRealtimeBotOutput=function(e,t){const n=(e.get(0)?.text??"")+t;return e.set(0,{text:n,completed:!1}),{id:0,text:n,spokenChars:n.length}},exports.useVoiceSessionCore=function(r,a,u){const o=t.useDispatch(),c=e.useRef(a),i=e.useRef(u);e.useEffect(()=>{c.current=a,i.current=u});const l=e.useRef(null),E=e.useRef(new Map),S=e.useRef(""),_=e.useRef(null),d=e.useCallback(()=>{if(null!=_.current)return;const e=Date.now();_.current=e,o(n.setResponseStartTime(e)),o(s.clearLastResponseTime())},[o]),g=e.useCallback(()=>{const e=_.current;_.current=null,o(n.setResponseStartTime(null)),null!=e&&o(s.setLastResponseTime(Date.now()-e))},[o]),T=e.useCallback(()=>{_.current=null,o(n.setResponseStartTime(null))},[o]),R=e.useCallback((e=s.INDICATOR_TIMER_ACTIONS.KEEP)=>{o(n.setStatusText(null)),o(n.setIsAgentWorking(!1)),e===s.INDICATOR_TIMER_ACTIONS.FINALIZE?g():e===s.INDICATOR_TIMER_ACTIONS.CANCEL&&T()},[o,g,T]),[I]=e.useState(()=>({setStatusText:e=>o(n.setStatusText(e||null)),setIsAgentWorking:e=>o(n.setIsAgentWorking(e)),appendStreamText:()=>{},pushWidget:e=>{const t=n.makeAgentMessage(n.VOICE_WIDGET_ID_PREFIX,{widget:[e],message_type:["widget"]});c.current.setMessages(e=>[...e,t])},onPayment:e=>c.current.onPaymentInit?.(e),onRequestDocuments:e=>c.current.onRequestDocuments?.(e),onConversationTitle:e=>c.current.onConversationTitle?.(e),onError:e=>{s.logger.error({message:"Voice server error",api_name:i.current.apiName,error_message:e});const t=n.makeAgentMessage(n.VOICE_ERROR_ID_PREFIX,{message:n.VOICE_GENERIC_ERROR_MESSAGE,message_type:["error"]});c.current.setMessages(e=>[...e,t])},onStatusActive:()=>{d()}})),p=e.useCallback(()=>[...E.current.entries()].sort((e,t)=>e[0]-t[0]).map(([,e])=>e.text).join(" ").trim(),[]),m=e.useCallback(()=>{l.current=null,E.current=new Map,S.current="",o(n.clearBotTurn())},[o]),C=e.useCallback(()=>{const e=l.current;if(e){const t=p();t&&c.current.updateStreamingMessage(e,t),c.current.commitStreamingMessages()}m()},[p,m]),k=e.useCallback(()=>{c.current.discardStreamingMessages(),m()},[m]),M=e.useCallback(()=>{if(l.current)return;const e=`${n.VOICE_AGENT_ID_PREFIX}${s.generateUniqueId()}`;l.current=e,E.current=new Map,S.current="",o(n.openBotTurn({chatMsgId:e}))},[o]),O=e.useCallback(e=>{o(n.upsertBotSegment(e));const t=p();t&&l.current&&t!==S.current&&(S.current=t,c.current.updateStreamingMessage(l.current,t))},[o,p]),f=e.useCallback(e=>{const t=e,a=t?.type,u=t?.data;switch(s.logger.debug({message:"Voice server-message received",api_name:i.current.apiName,conversation_id:r??void 0,event_type:a??"unknown"}),a){case n.VOICE_SERVER_MSG_TYPES.STATUS_UPDATE:s.handleSharedEvent({kind:"status_update",statusText:u?.text??""},I);break;case n.VOICE_SERVER_MSG_TYPES.STARTED_THINKING:o(n.setIsAgentWorking(!0)),d();break;case n.VOICE_SERVER_MSG_TYPES.STOPPED_THINKING:R(s.INDICATOR_TIMER_ACTIONS.FINALIZE);break;case n.VOICE_SERVER_MSG_TYPES.CLEAR_STATUS:i.current.onClearStatus?.();break;case n.VOICE_SERVER_MSG_TYPES.WIDGET:u&&s.handleSharedEvent({kind:"widget",widget:u},I);break;case n.VOICE_SERVER_MSG_TYPES.PAYMENT:u&&s.handleSharedEvent({kind:"payment",payment:u},I);break;case n.VOICE_SERVER_MSG_TYPES.REQUEST_DOCUMENTS:u&&s.handleSharedEvent({kind:"request_documents",requestDocuments:u},I);break;case n.VOICE_SERVER_MSG_TYPES.CONVERSATION_TITLE:{const e="string"==typeof u?.text?u.text.trim():"";e&&s.handleSharedEvent({kind:"conversation_title",title:e},I);break}case n.VOICE_SERVER_MSG_TYPES.SESSION_ENDED:{const e="string"==typeof u?.reason?u.reason:"ended";i.current.onSessionEnded?.(e);break}default:s.logger.debug({message:"Voice unhandled server-message",api_name:i.current.apiName,event_type:a??"unknown"})}},[I,o,d,R,r]);return{bridgeRef:c,botTurnMsgIdRef:l,liveSegmentsRef:E,startResponseTimer:d,finalizeResponseTimer:g,cancelResponseTimer:T,clearIndicator:R,sharedDeps:I,buildTurnText:p,clearTurnState:m,commitBotTurn:C,discardBotTurn:k,openBotTurnIfNeeded:M,ingestBotSegment:O,routeServerMessage:f}};
@@ -0,0 +1 @@
1
+ import{useRef as e,useEffect as t,useCallback as r,useState as n}from"react";import{useDispatch as s}from"react-redux";import{C as a,i as o,h as c,D as u,E as i,F as m,G as d,H as p,B as l,I as g,J as T,K as S}from"./sessionHelpers-BIwU9Fpt.js";import{clearLastResponseTime as _,setLastResponseTime as f,INDICATOR_TIMER_ACTIONS as k,logger as E,generateUniqueId as I,handleSharedEvent as x}from"@1interface/shared-core";function N(e,t,r,n){const s=t.segment_id??0,a="completed"===t.spoken_status;if(a&&!e.has(s)){const t=[...e.entries()].find(([,e])=>e.text===r&&!e.completed);if(t)return t[1].completed=!0,{id:t[0],text:r,spokenChars:r.length}}const o=e.get(s);return e.set(s,{text:r,completed:a||!0===o?.completed}),{id:s,text:r,spokenChars:n&&t.spoken_progress?(t.spoken_progress.accumulated_text??"").length:r.length}}function h(e,t){const r=(e.get(0)?.text??"")+t;return e.set(0,{text:r,completed:!1}),{id:0,text:r,spokenChars:r.length}}function b(N,h,b){const v=s(),D=e(h),M=e(b);t(()=>{D.current=h,M.current=b});const w=e(null),A=e(new Map),C=e(""),R=e(null),y=r(()=>{if(null!=R.current)return;const e=Date.now();R.current=e,v(a(e)),v(_())},[v]),P=r(()=>{const e=R.current;R.current=null,v(a(null)),null!=e&&v(f(Date.now()-e))},[v]),B=r(()=>{R.current=null,v(a(null))},[v]),L=r((e=k.KEEP)=>{v(o(null)),v(c(!1)),e===k.FINALIZE?P():e===k.CANCEL&&B()},[v,P,B]),[O]=n(()=>({setStatusText:e=>v(o(e||null)),setIsAgentWorking:e=>v(c(e)),appendStreamText:()=>{},pushWidget:e=>{const t=u(S,{widget:[e],message_type:["widget"]});D.current.setMessages(e=>[...e,t])},onPayment:e=>D.current.onPaymentInit?.(e),onRequestDocuments:e=>D.current.onRequestDocuments?.(e),onConversationTitle:e=>D.current.onConversationTitle?.(e),onError:e=>{E.error({message:"Voice server error",api_name:M.current.apiName,error_message:e});const t=u(T,{message:g,message_type:["error"]});D.current.setMessages(e=>[...e,t])},onStatusActive:()=>{y()}})),U=r(()=>[...A.current.entries()].sort((e,t)=>e[0]-t[0]).map(([,e])=>e.text).join(" ").trim(),[]),q=r(()=>{w.current=null,A.current=new Map,C.current="",v(i())},[v]),G=r(()=>{const e=w.current;if(e){const t=U();t&&D.current.updateStreamingMessage(e,t),D.current.commitStreamingMessages()}q()},[U,q]),H=r(()=>{D.current.discardStreamingMessages(),q()},[q]),K=r(()=>{if(w.current)return;const e=`${m}${I()}`;w.current=e,A.current=new Map,C.current="",v(d({chatMsgId:e}))},[v]),V=r(e=>{v(p(e));const t=U();t&&w.current&&t!==C.current&&(C.current=t,D.current.updateStreamingMessage(w.current,t))},[v,U]),F=r(e=>{const t=e,r=t?.type,n=t?.data;switch(E.debug({message:"Voice server-message received",api_name:M.current.apiName,conversation_id:N??void 0,event_type:r??"unknown"}),r){case l.STATUS_UPDATE:x({kind:"status_update",statusText:n?.text??""},O);break;case l.STARTED_THINKING:v(c(!0)),y();break;case l.STOPPED_THINKING:L(k.FINALIZE);break;case l.CLEAR_STATUS:M.current.onClearStatus?.();break;case l.WIDGET:n&&x({kind:"widget",widget:n},O);break;case l.PAYMENT:n&&x({kind:"payment",payment:n},O);break;case l.REQUEST_DOCUMENTS:n&&x({kind:"request_documents",requestDocuments:n},O);break;case l.CONVERSATION_TITLE:{const e="string"==typeof n?.text?n.text.trim():"";e&&x({kind:"conversation_title",title:e},O);break}case l.SESSION_ENDED:{const e="string"==typeof n?.reason?n.reason:"ended";M.current.onSessionEnded?.(e);break}default:E.debug({message:"Voice unhandled server-message",api_name:M.current.apiName,event_type:r??"unknown"})}},[O,v,y,L,N]);return{bridgeRef:D,botTurnMsgIdRef:w,liveSegmentsRef:A,startResponseTimer:y,finalizeResponseTimer:P,cancelResponseTimer:B,clearIndicator:L,sharedDeps:O,buildTurnText:U,clearTurnState:q,commitBotTurn:G,discardBotTurn:H,openBotTurnIfNeeded:K,ingestBotSegment:V,routeServerMessage:F}}export{N as a,h as f,b as u};
@@ -0,0 +1,76 @@
1
+ import { AttachedDocument } from '@1interface/shared-core';
2
+ import { Message } from '@1interface/shared-core';
3
+ import { ModelContextPart } from '@1interface/shared-core';
4
+ import { PaymentInitPayload } from '@1interface/shared-core';
5
+ import { RequestDocumentsPayload } from '@1interface/shared-core';
6
+ import { Transport } from '@pipecat-ai/client-js';
7
+
8
+ export declare const useVoiceProviderValue: ({ conversationId, voiceBridge, transportFactory, assistantStartsConversation, }: VoiceProviderParams) => VoiceContextValue;
9
+
10
+ declare const VOICE_DISPLAY_STATES: {
11
+ readonly OFFLINE: "offline";
12
+ readonly CONNECTING: "connecting";
13
+ readonly READY: "ready";
14
+ readonly IDLE: "idle";
15
+ readonly LISTENING: "listening";
16
+ readonly MUTED: "muted";
17
+ readonly PROCESSING: "processing";
18
+ readonly SPEAKING: "speaking";
19
+ };
20
+
21
+ declare const VOICE_PIPELINE_STATUSES: {
22
+ readonly IDLE: "idle";
23
+ readonly LISTENING: "listening";
24
+ readonly PROCESSING: "processing";
25
+ readonly SPEAKING: "speaking";
26
+ };
27
+
28
+ declare interface VoiceChatBridge {
29
+ setMessages: React.Dispatch<React.SetStateAction<Message[]>>;
30
+ updateStreamingMessage: (chatMsgId: string, content: string) => void;
31
+ commitStreamingMessages: () => void;
32
+ discardStreamingMessages: () => void;
33
+ setVoiceSendText: (fn: ((text: string, options?: VoiceSendTextOptions) => void) | null) => void;
34
+ onPaymentInit?: (init: PaymentInitPayload) => void;
35
+ onRequestDocuments?: (payload: RequestDocumentsPayload) => void;
36
+ onConversationTitle?: (title: string) => void;
37
+ }
38
+
39
+ declare interface VoiceContextValue {
40
+ displayState: VoiceDisplayState;
41
+ pipelineStatus: VoicePipelineStatus;
42
+ isAgentWorking: boolean;
43
+ enterVoiceMode: () => Promise<boolean>;
44
+ exitVoiceMode: () => void;
45
+ toggleMute: () => void;
46
+ sendText: (text: string, options?: VoiceSendTextOptions) => void;
47
+ muted: boolean;
48
+ serverMuted: boolean;
49
+ sessionEndedSignal: number;
50
+ setInputDevice: (deviceId: string) => void;
51
+ setOutputDevice: (deviceId: string) => void;
52
+ }
53
+
54
+ declare type VoiceDisplayState = (typeof VOICE_DISPLAY_STATES)[keyof typeof VOICE_DISPLAY_STATES];
55
+
56
+ declare type VoicePipelineStatus = (typeof VOICE_PIPELINE_STATUSES)[keyof typeof VOICE_PIPELINE_STATUSES];
57
+
58
+ declare interface VoiceProviderBaseParams {
59
+ conversationId: string | null;
60
+ voiceBridge: VoiceChatBridge;
61
+ assistantStartsConversation: boolean;
62
+ }
63
+
64
+ export declare interface VoiceProviderParams extends VoiceProviderBaseParams {
65
+ transportFactory: VoiceTransportFactory;
66
+ }
67
+
68
+ declare interface VoiceSendTextOptions {
69
+ modelContext?: ModelContextPart[];
70
+ documents?: AttachedDocument[];
71
+ messageType?: string[];
72
+ }
73
+
74
+ declare type VoiceTransportFactory = () => Transport | Promise<Transport>;
75
+
76
+ export { }
package/dist/webrtc.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./sessionHelpers-ALyIwLPk.cjs"),r=require("react"),t=require("react-redux"),n=require("./useVoiceSessionCore-Bir8eqjh.cjs"),i=require("@1interface/shared-core");function s(){const e={};for(const[r,t]of Object.entries(i.getCustomHeaders()))t&&(e[r]=t);const r=i.readAccessToken();r&&(e[i.AUTH_HEADERS.ACCESS_TOKEN]=r);const t=i.readDeviceToken();return t&&(e[i.DEVICE_TOKEN_HEADER]=t),e}async function o(e){try{const r=await fetch(i.API_ENDPOINTS.WEBRTC_ICE(e),{headers:s()});return r.ok?(await r.json()).iceServers??[]:(i.logger.warn({message:"Voice ICE fetch failed — continuing without TURN",api_name:"voice-webrtc-ice",status:r.status}),[])}catch(e){return i.logger.warn({message:"Voice ICE fetch error — continuing without TURN",api_name:"voice-webrtc-ice",error_message:e instanceof Error?e.message:String(e)}),[]}}exports.useVoiceProviderValue=({conversationId:c,voiceBridge:a,transportFactory:u,assistantStartsConversation:d})=>{const l=function(c,a,u,d){const l=t.useDispatch(),g=t.useStore(),m=e.useAudioPlatform(),v=t.useSelector(e=>e.auth.accessToken),_=r.useRef(null),{bridgeRef:p,botTurnMsgIdRef:E,liveSegmentsRef:I,startResponseTimer:T,clearIndicator:S,sharedDeps:f,commitBotTurn:R,discardBotTurn:V,openBotTurnIfNeeded:C,ingestBotSegment:b,routeServerMessage:N}=n.useVoiceSessionCore(c,a,{apiName:"voice-rtvi",onSessionEnded:e=>{_.current={reason:e}}}),h=r.useRef(!1),O=r.useRef(!1),A=r.useRef(null),k=r.useRef(""),M=r.useRef(null),P=r.useRef(null),w=r.useRef(null),U=r.useRef(null),y=r.useRef(!0),L=r.useRef(null),x=r.useRef(!1),D=r.useRef(""),B=r.useRef(0),H=r.useRef(0),j=r.useRef(!1),q=r.useRef(0),F=r.useRef(null),G=r.useRef(0),W=r.useRef(0),X=r.useRef(null),K=r.useCallback(()=>{U.current&&(clearTimeout(U.current),U.current=null)},[]),Y=r.useCallback(r=>{K(),L.current&&(clearTimeout(L.current),L.current=null),x.current=!1,D.current="",l(e.setMicrophones([])),l(e.setSpeakers([])),w.current?.(),w.current=null,M.current=null,j.current=!1,l(e.setListeningEnabled(!1)),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.IDLE)),l(e.setConnectionStatus(e.VOICE_CONNECTION_STATUSES.DISCONNECTED)),l(e.setInterimTranscript(null)),l(e.setServerMuted(!1)),A.current=null,k.current="",h.current=!1,O.current=!1,r?R():V(),S(i.INDICATOR_TIMER_ACTIONS.CANCEL)},[l,S,K,R,V]),Z=r.useCallback(e=>{const r=e.spoken_progress?(e.spoken_progress?.accumulated_text??"")+(e.spoken_progress?.remaining_text??""):e.text??"";if(!r)return;C();const t=h.current?n.foldRealtimeBotOutput(I.current,r):n.foldCascadeBotOutput(I.current,e,r,O.current);b(t)},[C,b,I]),$=r.useRef(()=>{}),z=r.useRef(async()=>{}),J=r.useCallback(r=>{const t=()=>r===B.current;return{onConnected:()=>{if(!t())return;i.logger.info({message:"Voice RTVI connected",api_name:"voice-rtvi",conversation_id:c??void 0}),l(e.setConnectionStatus(e.VOICE_CONNECTION_STATUSES.CONNECTED)),l(e.setListeningEnabled(!0)),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.IDLE)),M.current&&M.current.isMicEnabled!==y.current&&(i.logger.debug({message:"Voice re-asserting mic state on reconnect",api_name:"voice-rtvi",conversation_id:c??void 0,enabled:y.current}),M.current.enableMic(y.current));const{selectedMicId:r,selectedSpeakerId:n}=g.getState().voice;r&&M.current?.updateMic(r),n&&M.current?.updateSpeaker(n);const s=e=>r=>i.logger.warn({message:"Voice device enumeration failed",api_name:"voice-mic",conversation_id:c??void 0,device_kind:e,error_message:r instanceof Error?r.message:String(r)});M.current?.getAllMics().then(r=>{t()&&l(e.setMicrophones(r.map(e=>({deviceId:e.deviceId,label:e.label}))))}).catch(s("mics")),M.current?.getAllSpeakers().then(r=>{t()&&l(e.setSpeakers(r.map(e=>({deviceId:e.deviceId,label:e.label}))))}).catch(s("speakers")),x.current=!0},onBotReady:r=>{if(!t())return;const n=r?.about;h.current=n?.pipeline_mode===e.VOICE_PIPELINE_MODES.REALTIME,O.current=!0===n?.tts_word_timestamps,G.current=0,W.current=0,q.current=0,i.logger.info({message:"Voice RTVI bot-ready",api_name:"voice-rtvi",conversation_id:c??void 0,pipeline_mode:n?.pipeline_mode??"cascade",tts_word_timestamps:O.current,assistant_starts:d}),K(),d&&(l(e.setIsAgentWorking(!0)),T())},onDisconnected:()=>{if(!t())return;i.logger.info({message:"Voice RTVI disconnected",api_name:"voice-rtvi",conversation_id:c??void 0,drop_outcome:X.current??"transient",reconnect_attempts:G.current}),Y(!0);const r=H.current,n=X.current;X.current=null;const s=_.current;return _.current=null,s?(i.logger.info({message:"Voice session ended by server — no reconnect",api_name:"voice-rtvi",conversation_id:c??void 0,reason:s.reason}),"idle_timeout"===s.reason&&i.showToast({title:"Voice ended",description:"It got quiet for a while, so we wrapped up. Start again anytime."}),void l(e.signalSessionEnded())):"terminal"===n?(i.logger.warn({message:"Voice RTVI terminal error — no retry",api_name:"voice-rtvi"}),void l(e.signalSessionEnded())):"auth"===n?W.current>=e.VOICE_MAX_AUTH_REFRESHES||null==c?(i.logger.warn({message:"Voice RTVI auth refresh budget exhausted — giving up",api_name:"voice-rtvi",conversation_id:c??void 0}),void l(e.signalSessionEnded())):(W.current+=1,i.logger.info({message:"Voice RTVI auth drop — refreshing token then reconnecting",api_name:"voice-rtvi",conversation_id:c??void 0,auth_refresh_count:W.current}),void(async()=>{let t=!1;try{t=!!await(i.getTokenRefreshHandler()?.())}catch{}r===H.current&&(t?z.current():(i.logger.warn({message:"Voice RTVI token refresh failed — no reconnect",api_name:"voice-rtvi",conversation_id:c??void 0}),l(e.signalSessionEnded())))})()):G.current>=e.VOICE_MAX_AUTO_RECONNECTS||null==c?(G.current>=e.VOICE_MAX_AUTO_RECONNECTS&&(i.logger.warn({message:"Voice RTVI auto-reconnect cap reached — giving up",api_name:"voice-rtvi",conversation_id:c??void 0,reconnect_attempts:G.current}),i.showErrorToast("Voice connection lost. Please try again.")),void l(e.signalSessionEnded())):(G.current+=1,i.logger.info({message:"Voice RTVI scheduling auto-reconnect",api_name:"voice-rtvi",conversation_id:c??void 0,reconnect_attempts:G.current,delay_ms:e.VOICE_RECONNECT_DELAY_MS}),F.current&&clearTimeout(F.current),void(F.current=setTimeout(()=>{F.current=null,z.current()},e.VOICE_RECONNECT_DELAY_MS)))},onError:r=>{if(!t())return;const n=r?.data,s=n?.error??n?.message??"Voice error";i.handleSharedEvent({kind:"error",error:s},f),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.IDLE)),S(i.INDICATOR_TIMER_ACTIONS.CANCEL),n?.fatal&&(e.VOICE_AUTH_ERROR_PATTERNS.some(e=>e.test(s))?X.current="auth":e.VOICE_TERMINAL_ERROR_PATTERNS.some(e=>e.test(s))&&(X.current="terminal"),i.logger.warn({message:"Voice RTVI fatal error — closing transport",api_name:"voice-rtvi",conversation_id:c??void 0,drop_outcome:X.current??"generic"}),M.current?.disconnect().catch(()=>{}))},onTrackStarted:(e,r)=>{t()&&(r?.local||"audio"!==e.kind||(i.logger.debug({message:"Voice RTVI bot audio track started",api_name:"voice-rtvi",conversation_id:c??void 0}),w.current?.(),w.current=m.attachBotAudioTrack(e)))},onAvailableMicsUpdated:r=>{t()&&(l(e.setMicrophones(r.map(e=>({deviceId:e.deviceId,label:e.label})))),x.current&&(L.current&&clearTimeout(L.current),L.current=setTimeout(()=>{if(L.current=null,!M.current)return;const{selectedMicId:e,microphones:r}=g.getState().voice;i.logger.info({message:"Voice audio devices changed",api_name:"voice-mic",microphones:r.map(e=>e.label).join(", ")}),i.logger.debug({message:"Voice re-acquiring mic (device change)",api_name:"voice-rtvi",enabled:y.current,device:e||"default"}),M.current.updateMic(e)},300)))},onAvailableSpeakersUpdated:r=>{if(!t())return;if(l(e.setSpeakers(r.map(e=>({deviceId:e.deviceId,label:e.label})))),!x.current)return;const n=r.map(e=>e.label).join(", ");n!==D.current&&(D.current=n,i.logger.info({message:"Voice audio output devices changed",api_name:"voice-mic",speakers:n}))},onMicUpdated:e=>{if(!t())return;const r=M.current;r&&r.isMicEnabled!==y.current&&(i.logger.debug({message:"Voice re-asserting mic state after track swap",api_name:"voice-rtvi",conversation_id:c??void 0,enabled:y.current,device:e?.label}),r.enableMic(y.current))},onUserMuteStarted:()=>{t()&&i.logger.debug({message:"Voice RTVI user-mute-started (ignored)",api_name:"voice-rtvi",conversation_id:c??void 0})},onUserMuteStopped:()=>{t()&&(i.logger.debug({message:"Voice RTVI user-mute-stopped",api_name:"voice-rtvi",conversation_id:c??void 0,honored:d}),d&&l(e.setServerMuted(!1)))},onUserStartedSpeaking:()=>{t()&&(i.logger.debug({message:"Voice RTVI user started speaking",api_name:"voice-rtvi",conversation_id:c??void 0}),A.current=null,k.current="",l(e.setInterimTranscript(null)),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.LISTENING)))},onUserStoppedSpeaking:()=>{if(!t())return;i.logger.debug({message:"Voice RTVI user stopped speaking",api_name:"voice-rtvi",conversation_id:c??void 0,has_interim:!!k.current.trim()}),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.PROCESSING)),l(e.setIsAgentWorking(!0)),l(e.setStatusText(null)),T();const r=k.current.trim();if(r){const t=`${e.VOICE_USER_ID_PREFIX}${i.generateUniqueId()}`;A.current=t,l(e.setInterimTranscript(null)),p.current.setMessages(n=>[...n,e.makeUserMessage(r,t)])}},onUserTranscript:r=>{if(!t())return;if(!r.final)return void(r.text&&(k.current=r.text,h.current||l(e.setInterimTranscript(r.text))));l(e.setInterimTranscript(null));const n=r.text&&r.text.trim()?r.text:"";i.logger.debug({message:"Voice RTVI user transcript (final)",api_name:"voice-rtvi",conversation_id:c??void 0,has_text:!!n});const s=A.current;A.current=null,s?n&&p.current.setMessages(e=>e.map(e=>e.id===s?{...e,message:n}:e)):n&&p.current.setMessages(r=>[...r,e.makeUserMessage(n)])},onBotStartedSpeaking:()=>{t()&&(i.logger.debug({message:"Voice RTVI bot started speaking",api_name:"voice-rtvi",conversation_id:c??void 0}),C(),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.SPEAKING)),S(i.INDICATOR_TIMER_ACTIONS.FINALIZE))},onBotOutput:e=>{t()&&Z(e)},onBotStoppedSpeaking:()=>{t()&&(i.logger.debug({message:"Voice RTVI bot stopped speaking",api_name:"voice-rtvi",conversation_id:c??void 0}),R(),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.IDLE)),S(i.INDICATOR_TIMER_ACTIONS.FINALIZE),d&&g.getState().voice.serverMuted&&(i.logger.warn({message:"Voice greeting mute-hold released on bot-turn end (server unmute missing)",api_name:"voice-rtvi",conversation_id:c??void 0}),l(e.setServerMuted(!1))))},onServerMessage:e=>{t()&&N(e)}}},[l,g,m,K,Y,S,T,Z,C,R,N,f,p,d,c]),Q=r.useCallback(async()=>{if(M.current||j.current)return;if(!c)return;if(j.current=!0,i.logger.info({message:"Voice RTVI connecting",api_name:"voice-rtvi",conversation_id:c,assistant_starts:d}),d&&(y.current=!1,l(e.setServerMuted(!0)),i.logger.debug({message:"Voice greeting mute-hold armed",api_name:"voice-rtvi",conversation_id:c})),i.isTokenAboutToExpire(i.readTokenExpiresAt())&&q.current<e.VOICE_MAX_AUTH_REFRESHES){q.current+=1,i.logger.debug({message:"Voice pre-connect token refresh (freshness gate)",api_name:"voice-rtvi",conversation_id:c,freshness_refresh_count:q.current});const e=H.current;try{await Promise.resolve(i.getTokenRefreshHandler()?.())}catch{}if(e!==H.current)return}const r=++B.current,t=H.current;_.current=null,X.current=null,l(e.setConnectionStatus(e.VOICE_CONNECTION_STATUSES.CONNECTING));const n=()=>t!==H.current||r!==B.current;try{const[{PipecatClient:t,LogLevel:a,RTVIMessage:d},g,m]=await Promise.all([import("@pipecat-ai/client-js"),o(c),u()]);if(n())return;P.current=d;const v=new t({transport:m,enableMic:!0,enableCam:!1,callbacks:J(r)});if(v.setLogLevel(a.WARN),n())return void v.disconnect().catch(()=>{});M.current=v,i.logger.debug({message:"Voice RTVI client constructed",api_name:"voice-rtvi",conversation_id:c,enable_mic:y.current}),K(),U.current=setTimeout(()=>{i.logger.warn({message:"Voice RTVI connect timed out (no bot-ready)",api_name:"voice-rtvi"}),i.showErrorToast("Voice connection timed out. Please try again."),l(e.signalSessionEnded()),$.current()},e.VOICE_CONNECT_TIMEOUT_MS),await v.connect({webrtcRequestParams:{endpoint:i.API_ENDPOINTS.WEBRTC_OFFER(c),headers:new Headers(s())},iceConfig:{iceServers:g}})}catch(t){const n=(e=>{const r="object"==typeof e&&null!==e&&"status"in e?e.status:void 0;return"number"==typeof r?r:void 0})(t);i.logger.error({message:"Voice RTVI connect failed",api_name:"voice-rtvi",status:n,error_message:t instanceof Error?t.message:String(t)});const s=(e=>void 0!==e&&e>=400&&e<500)(n),o=s?await(async(r,t)=>{const n=429===t?"Voice session limit reached. Please try again later.":e.VOICE_CONNECT_ERROR_MESSAGE,i="object"==typeof r&&null!==r&&"cause"in r?r.cause:void 0;if(!(i instanceof Response))return n;try{const e=await i.json(),r=e?.data?.error_message??e?.data?.message;return"string"==typeof r?r:n}catch{return n}})(t,n):e.VOICE_CONNECT_ERROR_MESSAGE;r===B.current&&(i.showErrorToast(o),s&&(F.current&&(clearTimeout(F.current),F.current=null),G.current=0,l(e.signalSessionEnded())),Y(!1))}},[c,l,u,J,K,Y,d]),ee=r.useCallback((r=!0)=>{i.logger.info({message:"Voice RTVI disconnect (intentional teardown)",api_name:"voice-rtvi",conversation_id:c??void 0,commit_turn:r}),H.current+=1,B.current+=1,q.current=0,F.current&&(clearTimeout(F.current),F.current=null),G.current=0,W.current=0,X.current=null,y.current=!0,l(e.setMuted(!1));const t=M.current;t?.disconnect().catch(e=>{i.logger.warn({message:"Voice RTVI disconnect threw",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}),Y(r)},[Y,l,c]),re=r.useCallback(e=>{y.current=e,M.current&&(i.logger.debug({message:"Voice mic enable toggled",api_name:"voice-rtvi",enabled:e}),M.current.enableMic(e))},[]),te=r.useCallback(r=>{l(e.setSelectedMicId(r)),M.current?.updateMic(r)},[l]),ne=r.useCallback(r=>{l(e.setSelectedSpeakerId(r)),M.current?.updateSpeaker(r)},[l]),ie=r.useCallback((r,t)=>{const n=M.current;if(!n)return;const s=t?.modelContext;i.logger.info({message:"Voice RTVI send-text",api_name:"voice-rtvi",conversation_id:c??void 0,run_immediately:!0,has_model_context:Boolean(s?.length)});try{const e=P.current;s?.length&&e?n.transport.sendMessage(new e("send-text",{content:r,options:{run_immediately:!0,audio_response:!0},model_context:s})):n.sendText(r,{run_immediately:!0,audio_response:!0})}catch(e){return void i.logger.error({message:"Voice RTVI send-text failed",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}E.current&&R(),p.current.setMessages(n=>[...n,e.makeUserMessage(r,void 0,t?.documents,t?.messageType)]),l(e.setIsAgentWorking(!0)),l(e.setStatusText(null)),T(),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.PROCESSING))},[l,T,R,E,p,c]);r.useEffect(()=>{$.current=ee},[ee]),r.useEffect(()=>{z.current=Q},[Q]);const se=r.useRef(c),oe=r.useRef(!1);r.useEffect(()=>{const e=se.current;if(se.current=c,e===c)return;const r=j.current;r&&(oe.current=!0),r&&ee(!1),oe.current&&null!=c&&(oe.current=!1,Q())},[c,ee,Q]);const ce=r.useRef(v);return r.useEffect(()=>{const e=ce.current;ce.current=v,e&&!v&&j.current&&ee(!1)},[v,ee]),r.useEffect(()=>()=>{$.current()},[]),{connect:Q,disconnect:ee,sendText:ie,setMicEnabled:re,setInputDevice:te,setOutputDevice:ne}}(c,a,u,d);return e.useVoiceContextValue(l,{voiceBridge:a})};
@@ -0,0 +1 @@
1
+ import{u as e,s as r,a as t,b as n,c as i,V as c,d as o,e as a,f as s,g as u,m as d,h as m,i as l,j as v,k as p,l as g,n as _,o as f,p as V,q as I,r as b,t as h,v as T,w as R,x as S,y as E,z as w}from"./sessionHelpers-BIwU9Fpt.js";import{useRef as k,useCallback as y,useEffect as M}from"react";import{useDispatch as C,useStore as x,useSelector as N}from"react-redux";import{u as B,f as L,a as A}from"./useVoiceSessionCore-Cwkj3eVq.js";import{API_ENDPOINTS as j,logger as D,getCustomHeaders as O,readAccessToken as P,AUTH_HEADERS as U,readDeviceToken as F,DEVICE_TOKEN_HEADER as G,INDICATOR_TIMER_ACTIONS as q,generateUniqueId as W,handleSharedEvent as H,showToast as K,getTokenRefreshHandler as Z,showErrorToast as $,isTokenAboutToExpire as z,readTokenExpiresAt as J}from"@1interface/shared-core";function Q(){const e={};for(const[r,t]of Object.entries(O()))t&&(e[r]=t);const r=P();r&&(e[U.ACCESS_TOKEN]=r);const t=F();return t&&(e[G]=t),e}async function X(e){try{const r=await fetch(j.WEBRTC_ICE(e),{headers:Q()});return r.ok?(await r.json()).iceServers??[]:(D.warn({message:"Voice ICE fetch failed — continuing without TURN",api_name:"voice-webrtc-ice",status:r.status}),[])}catch(e){return D.warn({message:"Voice ICE fetch error — continuing without TURN",api_name:"voice-webrtc-ice",error_message:e instanceof Error?e.message:String(e)}),[]}}const Y=({conversationId:O,voiceBridge:P,transportFactory:U,assistantStartsConversation:F})=>{const G=function(w,O,P,U){const F=C(),G=x(),Y=e(),ee=N(e=>e.auth.accessToken),re=k(null),{bridgeRef:te,botTurnMsgIdRef:ne,liveSegmentsRef:ie,startResponseTimer:ce,clearIndicator:oe,sharedDeps:ae,commitBotTurn:se,discardBotTurn:ue,openBotTurnIfNeeded:de,ingestBotSegment:me,routeServerMessage:le}=B(w,O,{apiName:"voice-rtvi",onSessionEnded:e=>{re.current={reason:e}}}),ve=k(!1),pe=k(!1),ge=k(null),_e=k(""),fe=k(null),Ve=k(null),Ie=k(null),be=k(null),he=k(!0),Te=k(null),Re=k(!1),Se=k(""),Ee=k(0),we=k(0),ke=k(!1),ye=k(0),Me=k(null),Ce=k(0),xe=k(0),Ne=k(null),Be=y(()=>{be.current&&(clearTimeout(be.current),be.current=null)},[]),Le=y(e=>{Be(),Te.current&&(clearTimeout(Te.current),Te.current=null),Re.current=!1,Se.current="",F(r([])),F(t([])),Ie.current?.(),Ie.current=null,fe.current=null,ke.current=!1,F(n(!1)),F(i(c.IDLE)),F(o(a.DISCONNECTED)),F(s(null)),F(u(!1)),ge.current=null,_e.current="",ve.current=!1,pe.current=!1,e?se():ue(),oe(q.CANCEL)},[F,oe,Be,se,ue]),Ae=y(e=>{const r=e.spoken_progress?(e.spoken_progress?.accumulated_text??"")+(e.spoken_progress?.remaining_text??""):e.text??"";if(!r)return;de();const t=ve.current?L(ie.current,r):A(ie.current,e,r,pe.current);me(t)},[de,me,ie]),je=k(()=>{}),De=k(async()=>{}),Oe=y(e=>{const h=()=>e===Ee.current;return{onConnected:()=>{if(!h())return;D.info({message:"Voice RTVI connected",api_name:"voice-rtvi",conversation_id:w??void 0}),F(o(a.CONNECTED)),F(n(!0)),F(i(c.IDLE)),fe.current&&fe.current.isMicEnabled!==he.current&&(D.debug({message:"Voice re-asserting mic state on reconnect",api_name:"voice-rtvi",conversation_id:w??void 0,enabled:he.current}),fe.current.enableMic(he.current));const{selectedMicId:e,selectedSpeakerId:s}=G.getState().voice;e&&fe.current?.updateMic(e),s&&fe.current?.updateSpeaker(s);const u=e=>r=>D.warn({message:"Voice device enumeration failed",api_name:"voice-mic",conversation_id:w??void 0,device_kind:e,error_message:r instanceof Error?r.message:String(r)});fe.current?.getAllMics().then(e=>{h()&&F(r(e.map(e=>({deviceId:e.deviceId,label:e.label}))))}).catch(u("mics")),fe.current?.getAllSpeakers().then(e=>{h()&&F(t(e.map(e=>({deviceId:e.deviceId,label:e.label}))))}).catch(u("speakers")),Re.current=!0},onBotReady:e=>{if(!h())return;const r=e?.about;ve.current=r?.pipeline_mode===b.REALTIME,pe.current=!0===r?.tts_word_timestamps,Ce.current=0,xe.current=0,ye.current=0,D.info({message:"Voice RTVI bot-ready",api_name:"voice-rtvi",conversation_id:w??void 0,pipeline_mode:r?.pipeline_mode??"cascade",tts_word_timestamps:pe.current,assistant_starts:U}),Be(),U&&(F(m(!0)),ce())},onDisconnected:()=>{if(!h())return;D.info({message:"Voice RTVI disconnected",api_name:"voice-rtvi",conversation_id:w??void 0,drop_outcome:Ne.current??"transient",reconnect_attempts:Ce.current}),Le(!0);const e=we.current,r=Ne.current;Ne.current=null;const t=re.current;return re.current=null,t?(D.info({message:"Voice session ended by server — no reconnect",api_name:"voice-rtvi",conversation_id:w??void 0,reason:t.reason}),"idle_timeout"===t.reason&&K({title:"Voice ended",description:"It got quiet for a while, so we wrapped up. Start again anytime."}),void F(_())):"terminal"===r?(D.warn({message:"Voice RTVI terminal error — no retry",api_name:"voice-rtvi"}),void F(_())):"auth"===r?xe.current>=f||null==w?(D.warn({message:"Voice RTVI auth refresh budget exhausted — giving up",api_name:"voice-rtvi",conversation_id:w??void 0}),void F(_())):(xe.current+=1,D.info({message:"Voice RTVI auth drop — refreshing token then reconnecting",api_name:"voice-rtvi",conversation_id:w??void 0,auth_refresh_count:xe.current}),void(async()=>{let r=!1;try{r=!!await(Z()?.())}catch{}e===we.current&&(r?De.current():(D.warn({message:"Voice RTVI token refresh failed — no reconnect",api_name:"voice-rtvi",conversation_id:w??void 0}),F(_())))})()):Ce.current>=V||null==w?(Ce.current>=V&&(D.warn({message:"Voice RTVI auto-reconnect cap reached — giving up",api_name:"voice-rtvi",conversation_id:w??void 0,reconnect_attempts:Ce.current}),$("Voice connection lost. Please try again.")),void F(_())):(Ce.current+=1,D.info({message:"Voice RTVI scheduling auto-reconnect",api_name:"voice-rtvi",conversation_id:w??void 0,reconnect_attempts:Ce.current,delay_ms:I}),Me.current&&clearTimeout(Me.current),void(Me.current=setTimeout(()=>{Me.current=null,De.current()},I)))},onError:e=>{if(!h())return;const r=e?.data,t=r?.error??r?.message??"Voice error";H({kind:"error",error:t},ae),F(i(c.IDLE)),oe(q.CANCEL),r?.fatal&&(p.some(e=>e.test(t))?Ne.current="auth":g.some(e=>e.test(t))&&(Ne.current="terminal"),D.warn({message:"Voice RTVI fatal error — closing transport",api_name:"voice-rtvi",conversation_id:w??void 0,drop_outcome:Ne.current??"generic"}),fe.current?.disconnect().catch(()=>{}))},onTrackStarted:(e,r)=>{h()&&(r?.local||"audio"!==e.kind||(D.debug({message:"Voice RTVI bot audio track started",api_name:"voice-rtvi",conversation_id:w??void 0}),Ie.current?.(),Ie.current=Y.attachBotAudioTrack(e)))},onAvailableMicsUpdated:e=>{h()&&(F(r(e.map(e=>({deviceId:e.deviceId,label:e.label})))),Re.current&&(Te.current&&clearTimeout(Te.current),Te.current=setTimeout(()=>{if(Te.current=null,!fe.current)return;const{selectedMicId:e,microphones:r}=G.getState().voice;D.info({message:"Voice audio devices changed",api_name:"voice-mic",microphones:r.map(e=>e.label).join(", ")}),D.debug({message:"Voice re-acquiring mic (device change)",api_name:"voice-rtvi",enabled:he.current,device:e||"default"}),fe.current.updateMic(e)},300)))},onAvailableSpeakersUpdated:e=>{if(!h())return;if(F(t(e.map(e=>({deviceId:e.deviceId,label:e.label})))),!Re.current)return;const r=e.map(e=>e.label).join(", ");r!==Se.current&&(Se.current=r,D.info({message:"Voice audio output devices changed",api_name:"voice-mic",speakers:r}))},onMicUpdated:e=>{if(!h())return;const r=fe.current;r&&r.isMicEnabled!==he.current&&(D.debug({message:"Voice re-asserting mic state after track swap",api_name:"voice-rtvi",conversation_id:w??void 0,enabled:he.current,device:e?.label}),r.enableMic(he.current))},onUserMuteStarted:()=>{h()&&D.debug({message:"Voice RTVI user-mute-started (ignored)",api_name:"voice-rtvi",conversation_id:w??void 0})},onUserMuteStopped:()=>{h()&&(D.debug({message:"Voice RTVI user-mute-stopped",api_name:"voice-rtvi",conversation_id:w??void 0,honored:U}),U&&F(u(!1)))},onUserStartedSpeaking:()=>{h()&&(D.debug({message:"Voice RTVI user started speaking",api_name:"voice-rtvi",conversation_id:w??void 0}),ge.current=null,_e.current="",F(s(null)),F(i(c.LISTENING)))},onUserStoppedSpeaking:()=>{if(!h())return;D.debug({message:"Voice RTVI user stopped speaking",api_name:"voice-rtvi",conversation_id:w??void 0,has_interim:!!_e.current.trim()}),F(i(c.PROCESSING)),F(m(!0)),F(l(null)),ce();const e=_e.current.trim();if(e){const r=`${v}${W()}`;ge.current=r,F(s(null)),te.current.setMessages(t=>[...t,d(e,r)])}},onUserTranscript:e=>{if(!h())return;if(!e.final)return void(e.text&&(_e.current=e.text,ve.current||F(s(e.text))));F(s(null));const r=e.text&&e.text.trim()?e.text:"";D.debug({message:"Voice RTVI user transcript (final)",api_name:"voice-rtvi",conversation_id:w??void 0,has_text:!!r});const t=ge.current;ge.current=null,t?r&&te.current.setMessages(e=>e.map(e=>e.id===t?{...e,message:r}:e)):r&&te.current.setMessages(e=>[...e,d(r)])},onBotStartedSpeaking:()=>{h()&&(D.debug({message:"Voice RTVI bot started speaking",api_name:"voice-rtvi",conversation_id:w??void 0}),de(),F(i(c.SPEAKING)),oe(q.FINALIZE))},onBotOutput:e=>{h()&&Ae(e)},onBotStoppedSpeaking:()=>{h()&&(D.debug({message:"Voice RTVI bot stopped speaking",api_name:"voice-rtvi",conversation_id:w??void 0}),se(),F(i(c.IDLE)),oe(q.FINALIZE),U&&G.getState().voice.serverMuted&&(D.warn({message:"Voice greeting mute-hold released on bot-turn end (server unmute missing)",api_name:"voice-rtvi",conversation_id:w??void 0}),F(u(!1))))},onServerMessage:e=>{h()&&le(e)}}},[F,G,Y,Be,Le,oe,ce,Ae,de,se,le,ae,te,U,w]),Pe=y(async()=>{if(fe.current||ke.current)return;if(!w)return;if(ke.current=!0,D.info({message:"Voice RTVI connecting",api_name:"voice-rtvi",conversation_id:w,assistant_starts:U}),U&&(he.current=!1,F(u(!0)),D.debug({message:"Voice greeting mute-hold armed",api_name:"voice-rtvi",conversation_id:w})),z(J())&&ye.current<f){ye.current+=1,D.debug({message:"Voice pre-connect token refresh (freshness gate)",api_name:"voice-rtvi",conversation_id:w,freshness_refresh_count:ye.current});const e=we.current;try{await Promise.resolve(Z()?.())}catch{}if(e!==we.current)return}const e=++Ee.current,r=we.current;re.current=null,Ne.current=null,F(o(a.CONNECTING));const t=()=>r!==we.current||e!==Ee.current;try{const[{PipecatClient:r,LogLevel:n,RTVIMessage:i},c,o]=await Promise.all([import("@pipecat-ai/client-js"),X(w),P()]);if(t())return;Ve.current=i;const a=new r({transport:o,enableMic:!0,enableCam:!1,callbacks:Oe(e)});if(a.setLogLevel(n.WARN),t())return void a.disconnect().catch(()=>{});fe.current=a,D.debug({message:"Voice RTVI client constructed",api_name:"voice-rtvi",conversation_id:w,enable_mic:he.current}),Be(),be.current=setTimeout(()=>{D.warn({message:"Voice RTVI connect timed out (no bot-ready)",api_name:"voice-rtvi"}),$("Voice connection timed out. Please try again."),F(_()),je.current()},h),await a.connect({webrtcRequestParams:{endpoint:j.WEBRTC_OFFER(w),headers:new Headers(Q())},iceConfig:{iceServers:c}})}catch(r){const t=(e=>{const r="object"==typeof e&&null!==e&&"status"in e?e.status:void 0;return"number"==typeof r?r:void 0})(r);D.error({message:"Voice RTVI connect failed",api_name:"voice-rtvi",status:t,error_message:r instanceof Error?r.message:String(r)});const n=(e=>void 0!==e&&e>=400&&e<500)(t),i=n?await(async(e,r)=>{const t=429===r?"Voice session limit reached. Please try again later.":T,n="object"==typeof e&&null!==e&&"cause"in e?e.cause:void 0;if(!(n instanceof Response))return t;try{const e=await n.json(),r=e?.data?.error_message??e?.data?.message;return"string"==typeof r?r:t}catch{return t}})(r,t):T;e===Ee.current&&($(i),n&&(Me.current&&(clearTimeout(Me.current),Me.current=null),Ce.current=0,F(_())),Le(!1))}},[w,F,P,Oe,Be,Le,U]),Ue=y((e=!0)=>{D.info({message:"Voice RTVI disconnect (intentional teardown)",api_name:"voice-rtvi",conversation_id:w??void 0,commit_turn:e}),we.current+=1,Ee.current+=1,ye.current=0,Me.current&&(clearTimeout(Me.current),Me.current=null),Ce.current=0,xe.current=0,Ne.current=null,he.current=!0,F(R(!1));const r=fe.current;r?.disconnect().catch(e=>{D.warn({message:"Voice RTVI disconnect threw",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}),Le(e)},[Le,F,w]),Fe=y(e=>{he.current=e,fe.current&&(D.debug({message:"Voice mic enable toggled",api_name:"voice-rtvi",enabled:e}),fe.current.enableMic(e))},[]),Ge=y(e=>{F(S(e)),fe.current?.updateMic(e)},[F]),qe=y(e=>{F(E(e)),fe.current?.updateSpeaker(e)},[F]),We=y((e,r)=>{const t=fe.current;if(!t)return;const n=r?.modelContext;D.info({message:"Voice RTVI send-text",api_name:"voice-rtvi",conversation_id:w??void 0,run_immediately:!0,has_model_context:Boolean(n?.length)});try{const r=Ve.current;n?.length&&r?t.transport.sendMessage(new r("send-text",{content:e,options:{run_immediately:!0,audio_response:!0},model_context:n})):t.sendText(e,{run_immediately:!0,audio_response:!0})}catch(e){return void D.error({message:"Voice RTVI send-text failed",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}ne.current&&se(),te.current.setMessages(t=>[...t,d(e,void 0,r?.documents,r?.messageType)]),F(m(!0)),F(l(null)),ce(),F(i(c.PROCESSING))},[F,ce,se,ne,te,w]);M(()=>{je.current=Ue},[Ue]),M(()=>{De.current=Pe},[Pe]);const He=k(w),Ke=k(!1);M(()=>{const e=He.current;if(He.current=w,e===w)return;const r=ke.current;r&&(Ke.current=!0),r&&Ue(!1),Ke.current&&null!=w&&(Ke.current=!1,Pe())},[w,Ue,Pe]);const Ze=k(ee);return M(()=>{const e=Ze.current;Ze.current=ee,e&&!ee&&ke.current&&Ue(!1)},[ee,Ue]),M(()=>()=>{je.current()},[]),{connect:Pe,disconnect:Ue,sendText:We,setMicEnabled:Fe,setInputDevice:Ge,setOutputDevice:qe}}(O,P,U,F);return w(G,{voiceBridge:P})};export{Y as useVoiceProviderValue};