@1interface/voice-core 0.2.0-pipecat.1 → 0.2.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.
package/dist/index.d.ts CHANGED
@@ -85,8 +85,6 @@ export declare const resetIndicatorState: ActionCreatorWithoutPayload<"voice/res
85
85
 
86
86
  export declare const setConnectionStatus: ActionCreatorWithOptionalPayload<VoiceConnectionStatus, "voice/setConnectionStatus">;
87
87
 
88
- export declare const setError: ActionCreatorWithOptionalPayload<string, "voice/setError">;
89
-
90
88
  export declare const setInterimTranscript: ActionCreatorWithOptionalPayload<string, "voice/setInterimTranscript">;
91
89
 
92
90
  export declare const setIsAgentWorking: ActionCreatorWithOptionalPayload<boolean, "voice/setIsAgentWorking">;
@@ -123,9 +121,9 @@ export declare function useMediaDevices(): {
123
121
 
124
122
  export declare const useVoice: () => VoiceContextValue;
125
123
 
126
- export declare function useVoiceDisplayState(loading: boolean, errored: boolean, effectiveMuted?: boolean): VoiceDisplayState;
124
+ export declare function useVoiceDisplayState(): VoiceDisplayState;
127
125
 
128
- export declare const useVoiceProviderValue: ({ conversationId, voiceBridge, transportFactory, }: VoiceProviderParams) => VoiceContextValue;
126
+ export declare const useVoiceProviderValue: ({ conversationId, voiceBridge, transportFactory, assistantStartsConversation, }: VoiceProviderParams) => VoiceContextValue;
129
127
 
130
128
  export declare const useVoiceUI: () => VoiceUIContextValue;
131
129
 
@@ -141,7 +139,6 @@ export declare const VOICE_DISPLAY_STATES: {
141
139
  readonly OFFLINE: "offline";
142
140
  readonly CONNECTING: "connecting";
143
141
  readonly READY: "ready";
144
- readonly LOADING: "loading";
145
142
  readonly IDLE: "idle";
146
143
  readonly LISTENING: "listening";
147
144
  readonly MUTED: "muted";
@@ -212,6 +209,7 @@ export declare interface VoiceProviderParams {
212
209
  conversationId: string | null;
213
210
  voiceBridge: VoiceChatBridge;
214
211
  transportFactory: VoiceTransportFactory;
212
+ assistantStartsConversation: boolean;
215
213
  }
216
214
 
217
215
  export declare const voiceReducer: Reducer<VoiceState>;
@@ -232,7 +230,6 @@ export declare interface VoiceState {
232
230
  statusText: string | null;
233
231
  isAgentWorking: boolean;
234
232
  responseStartTime: number | null;
235
- error: string | null;
236
233
  interimTranscript: string | null;
237
234
  botTurn: BotTurnState;
238
235
  selectedMicId: string;
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"},a={IDLE:"idle",LISTENING:"listening",PROCESSING:"processing",SPEAKING:"speaking"},o={CASCADE:"cascade",REALTIME:"realtime"},c={OFFLINE:"offline",CONNECTING:"connecting",READY:"ready",LOADING:"loading",IDLE:"idle",LISTENING:"listening",MUTED:"muted",PROCESSING:"processing",SPEAKING:"speaking"},i={SELECTED_MIC_ID:"voice_selectedMicId",SELECTED_SPEAKER_ID:"voice_selectedSpeakerId"};function u(){return{connectionStatus:s.DISCONNECTED,pipelineStatus:a.IDLE,listeningEnabled:!1,muted:!1,serverMuted:!1,statusText:null,isAgentWorking:!1,responseStartTime:null,error:null,interimTranscript:null,botTurn:{active:!1,chatMsgId:null,segments:[]},selectedMicId:"",selectedSpeakerId:""}}const l=u(),d=r.createSlice({name:"voice",initialState:l,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},setError(e,t){e.error=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 a=e.botTurn.segments.find(e=>e.id===r);a?(a.text=n,a.spokenChars=Math.max(a.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},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:p,setPipelineStatus:g,setListeningEnabled:E,setMuted:S,setServerMuted:I,setStatusText:m,setIsAgentWorking:T,setResponseStartTime:C,setError:f,setInterimTranscript:v,openBotTurn:k,upsertBotSegment:N,clearBotTurn:x,setSelectedMicId:R,setSelectedSpeakerId:y,resetVoiceState:_,resetIndicatorState:h}=d.actions,b=d.reducer,M=e.createContext(null),A=()=>{const t=e.useContext(M);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 D(){const e={};for(const[t,r]of Object.entries(n.getCustomHeaders()))r&&(e[t]=r);const t=n.readAccessToken();return t&&(e["X-Access-Token"]=t),e}async function O(e){try{const t=await fetch(n.API_ENDPOINTS.WEBRTC_ICE(e),{headers:D()});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 w="voice-user-",P=(e,t=`${w}${n.generateUniqueId()}`)=>({id:t,sender:"user",message:e,timestamp:(new Date).toISOString()});function L(e,r,n){const o=t.useSelector(e=>e.voice.connectionStatus),i=t.useSelector(e=>e.voice.pipelineStatus),u=t.useSelector(e=>e.voice.listeningEnabled),l=t.useSelector(e=>e.voice.muted);return o===s.CONNECTING?c.CONNECTING:o===s.CONNECTED?u?e?c.LOADING:r?c.OFFLINE:n??l?c.MUTED:i===a.PROCESSING?c.PROCESSING:i===a.SPEAKING?c.SPEAKING:i===a.LISTENING?c.LISTENING:c.IDLE:c.READY:c.OFFLINE}const V=e.createContext(null),G=e.createContext({strategyActive:!1,voiceSlot:null,onEnterVoice:null,onFloatingOrbClick:null,voiceInputHidden:!1,headerCenterSlot:null,floatOrbPositionRef:null}),B=r.createListenerMiddleware();B.startListening({actionCreator:R,effect:async e=>{const t=n.getStorageRef();t&&await t.setItem(i.SELECTED_MIC_ID,e.payload)}}),B.startListening({actionCreator:y,effect:async e=>{const t=n.getStorageRef();t&&await t.setItem(i.SELECTED_SPEAKER_ID,e.payload)}}),B.startListening({matcher:r.isAnyOf(n.clearSession,n.sessionExpired),effect:async(e,t)=>{const r=n.getStorageRef();r&&await Promise.all([r.removeItem(i.SELECTED_MIC_ID),r.removeItem(i.SELECTED_SPEAKER_ID)]),t.dispatch(_())}});class U{async enumerate(){const e={microphones:[],speakers:[]};if(!navigator.mediaDevices?.enumerateDevices)return e;try{const e=await navigator.mediaDevices.enumerateDevices();let t=0,r=0;return{microphones:e.filter(e=>"audioinput"===e.kind).map(e=>({deviceId:e.deviceId,label:e.label||"Microphone "+ ++t})),speakers:e.filter(e=>"audiooutput"===e.kind).map(e=>({deviceId:e.deviceId,label:e.label||"Speaker "+ ++r}))}}catch{return e}}onChange(e){const t=navigator.mediaDevices;return t?.addEventListener?(t.addEventListener("devicechange",e),()=>t.removeEventListener("devicechange",e)):()=>{}}}const F=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{}}},q={attachBotAudioTrack:F,devices:new U,requestMicPermission:async function(){try{return(await navigator.mediaDevices.getUserMedia({audio:!0})).getTracks().forEach(e=>e.stop()),{granted:!0}}catch(e){return{granted:!1,error:e}}}};exports.AudioPlatformContext=M,exports.BrowserAudioDevices=U,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},loading:{opacity:.6,playbackRate:.6,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=c,exports.VOICE_PIPELINE_MODES=o,exports.VOICE_PIPELINE_STATUSES=a,exports.VOICE_STORAGE_KEYS=i,exports.VoiceContext=V,exports.VoiceUIContext=G,exports.attachBrowserBotAudioTrack=F,exports.browserAudioPlatform=q,exports.clearBotTurn=x,exports.hydrateVoice=e=>async t=>{const[r,n]=await Promise.all([e.getItem(i.SELECTED_MIC_ID),e.getItem(i.SELECTED_SPEAKER_ID)]);r&&t(d.actions.setSelectedMicId(r)),n&&t(d.actions.setSelectedSpeakerId(n))},exports.openBotTurn=k,exports.resetIndicatorState=h,exports.setConnectionStatus=p,exports.setError=f,exports.setInterimTranscript=v,exports.setIsAgentWorking=T,exports.setListeningEnabled=E,exports.setMuted=S,exports.setPipelineStatus=g,exports.setResponseStartTime=C,exports.setSelectedMicId=R,exports.setSelectedSpeakerId=y,exports.setServerMuted=I,exports.setStatusText=m,exports.upsertBotSegment=N,exports.useAudioPlatform=A,exports.useMediaDevices=function(){const r=A(),[n,s]=e.useState([]),[a,o]=e.useState([]),c=t.useSelector(e=>e.voice.listeningEnabled),i=e.useCallback(async()=>{const e=await r.devices.enumerate();s(e.microphones),o(e.speakers)},[r]);return e.useEffect(()=>(i(),r.devices.onChange(i)),[r,i]),e.useEffect(()=>{c&&i()},[c,i]),{microphones:n,speakers:a,refresh:i}},exports.useVoice=()=>{const t=e.useContext(V);if(!t)throw new Error("useVoice must be used within VoiceProvider");return t},exports.useVoiceDisplayState=L,exports.useVoiceProviderValue=({conversationId:r,voiceBridge:c,transportFactory:i})=>{const u=t.useDispatch(),l=A(),d=t.useSelector(e=>e.voice.muted),R=t.useSelector(e=>e.voice.serverMuted),y=t.useSelector(e=>e.voice.pipelineStatus),_=t.useSelector(e=>e.voice.isAgentWorking),{connect:h,disconnect:b,sendText:M,setMicEnabled:V}=function(r,c,i){const u=t.useDispatch(),l=A(),d=t.useSelector(e=>e.auth.accessToken),S=e.useRef(c);e.useEffect(()=>{S.current=c});const R=e.useRef(null),y=e.useRef(new Map),_=e.useRef(""),h=e.useRef(!1),b=e.useRef(null),M=e.useRef(""),L=e.useRef(null),V=e.useRef(null),G=e.useRef(null),B=e.useRef(0),U=e.useRef(0),F=e.useRef(!1),q=e.useRef(0),W=e.useRef(null),K=e.useCallback(()=>{if(null!=W.current)return;const e=Date.now();W.current=e,u(C(e)),u(n.clearLastResponseTime())},[u]),j=e.useCallback(()=>{const e=W.current;W.current=null,u(C(null)),null!=e&&u(n.setLastResponseTime(Date.now()-e))},[u]),$=e.useCallback(()=>{W.current=null,u(C(null))},[u]),H=e.useCallback((e=n.INDICATOR_TIMER_ACTIONS.KEEP)=>{u(m(null)),u(T(!1)),e===n.INDICATOR_TIMER_ACTIONS.FINALIZE?j():e===n.INDICATOR_TIMER_ACTIONS.CANCEL&&$()},[u,j,$]),Y=e.useRef({setStatusText:e=>u(m(e||null)),setIsAgentWorking:e=>u(T(e)),appendStreamText:()=>{},pushWidget:e=>{const t=`voice-widget-${n.generateUniqueId()}`;S.current.setMessages(r=>[...r,{id:t,msg_id:t,sender:"agent",message:"",timestamp:(new Date).toISOString(),widget:[e],message_type:["widget"]}])},onPayment:e=>S.current.onPaymentInit?.(e),onConversationTitle:e=>S.current.onConversationTitle?.(e),onError:e=>{n.logger.error({message:"Voice RTVI server error",api_name:"voice-rtvi",error_message:e}),u(f(e))},onStatusActive:()=>{K()}}),Z=e.useCallback(()=>{G.current&&(clearTimeout(G.current),G.current=null)},[]),X=e.useCallback(()=>[...y.current.entries()].sort((e,t)=>e[0]-t[0]).map(([,e])=>e).join(" ").trim(),[]),z=e.useCallback(()=>{const e=R.current;if(e){const t=X();t&&S.current.updateStreamingMessage(e,t),S.current.commitStreamingMessages()}R.current=null,y.current=new Map,_.current="",u(x())},[u,X]),J=e.useCallback(e=>{Z(),V.current?.(),V.current=null,L.current=null,F.current=!1,u(E(!1)),u(g(a.IDLE)),u(p(s.DISCONNECTED)),u(v(null)),u(I(!1)),b.current=null,M.current="",h.current=!1,e?z():(S.current.discardStreamingMessages(),R.current=null,y.current=new Map,_.current="",u(x())),H(n.INDICATOR_TIMER_ACTIONS.CANCEL)},[u,H,Z,z]),Q=e.useCallback(()=>{if(R.current)return;const e=`voice-agent-${n.generateUniqueId()}`;R.current=e,y.current=new Map,_.current="",u(k({chatMsgId:e}))},[u]),ee=e.useCallback(e=>{const t=e.spoken_progress?.accumulated_text??"",r=e.spoken_progress?t+(e.spoken_progress?.remaining_text??""):e.text??"";if(!r)return;if(Q(),h.current){const e=(y.current.get(0)??"")+r;y.current.set(0,e),u(N({id:0,text:e,spokenChars:e.length}))}else{const n=e.segment_id??0;y.current.set(n,r),u(N({id:n,text:r,spokenChars:e.spoken_progress?t.length:r.length}))}const n=X();n&&R.current&&n!==_.current&&(_.current=n,S.current.updateStreamingMessage(R.current,n))},[u,Q,X]),te=e.useCallback(e=>{const t=e,r=t?.type,s=t?.data;switch(r){case"status_update":n.handleSharedEvent({kind:"status_update",statusText:s?.text??""},Y.current);break;case"clear_status":H(n.INDICATOR_TIMER_ACTIONS.KEEP);break;case"widget":s&&n.handleSharedEvent({kind:"widget",widget:s},Y.current);break;case"payment":s&&n.handleSharedEvent({kind:"payment",payment:s},Y.current);break;case"conversation_title":{const e="string"==typeof s?.text?s.text.trim():"";e&&n.handleSharedEvent({kind:"conversation_title",title:e},Y.current);break}default:n.logger.debug({message:"Voice RTVI unhandled server-message",api_name:"voice-rtvi",event_type:r??"unknown"})}},[H]),re=e.useRef(()=>{}),ne=e.useCallback(e=>{const t=()=>e===B.current;return{onConnected:()=>{t()&&(u(p(s.CONNECTED)),u(E(!0)),u(g(a.IDLE)))},onBotReady:e=>{if(!t())return;const r=e?.about;h.current=r?.pipeline_mode===o.REALTIME,Z()},onDisconnected:()=>{t()&&J(!0)},onError:e=>{if(!t())return;const r=e?.data;n.handleSharedEvent({kind:"error",error:r?.error??r?.message??"Voice error"},Y.current),u(g(a.IDLE)),H(n.INDICATOR_TIMER_ACTIONS.CANCEL),r?.fatal&&re.current()},onTrackStarted:(e,r)=>{t()&&(r?.local||"audio"!==e.kind||(V.current?.(),V.current=l.attachBotAudioTrack(e)))},onUserMuteStarted:()=>{t()&&u(I(!0))},onUserMuteStopped:()=>{t()&&u(I(!1))},onUserStartedSpeaking:()=>{t()&&(b.current=null,M.current="",u(v(null)),u(g(a.LISTENING)))},onUserStoppedSpeaking:()=>{if(!t())return;u(g(a.PROCESSING)),u(T(!0)),u(m(null)),K();const e=M.current.trim();if(e){const t=`${w}${n.generateUniqueId()}`;b.current=t,u(v(null)),S.current.setMessages(r=>[...r,P(e,t)])}},onUserTranscript:e=>{if(!t())return;if(!e.final)return void(e.text&&(M.current=e.text,h.current||u(v(e.text))));u(v(null));const r=e.text&&e.text.trim()?e.text:"",n=b.current;b.current=null,n?r&&S.current.setMessages(e=>e.map(e=>e.id===n?{...e,message:r}:e)):r&&S.current.setMessages(e=>[...e,P(r)])},onBotStartedSpeaking:()=>{t()&&(Q(),u(g(a.SPEAKING)),H(n.INDICATOR_TIMER_ACTIONS.FINALIZE))},onBotOutput:e=>{t()&&ee(e)},onBotStoppedSpeaking:()=>{t()&&(z(),u(g(a.IDLE)),H(n.INDICATOR_TIMER_ACTIONS.FINALIZE))},onServerMessage:e=>{t()&&te(e)}}},[u,l,Z,J,H,K,ee,Q,z,te]),se=e.useCallback(async()=>{if(L.current||F.current)return;if(!r)return;if(F.current=!0,n.isTokenAboutToExpire(n.readTokenExpiresAt())&&q.current<1){q.current+=1;const e=U.current;try{await Promise.resolve(n.getTokenRefreshHandler()?.())}catch{}if(e!==U.current)return}const e=++B.current,t=U.current;u(p(s.CONNECTING));const a=()=>t!==U.current||e!==B.current;try{const[{PipecatClient:t,LogLevel:s},o]=await Promise.all([Promise.resolve().then(()=>require("./index.module-DCaqEXL5.cjs")),O(r)]);if(a())return;const c=new t({transport:i(),enableMic:!0,enableCam:!1,callbacks:ne(e)});if(c.setLogLevel(s.WARN),a())return void c.disconnect();L.current=c,Z(),G.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."),re.current()},15e3),await c.connect({webrtcRequestParams:{endpoint:n.API_ENDPOINTS.WEBRTC_OFFER(r),headers:new Headers(D())},iceConfig:{iceServers:o}})}catch(t){n.logger.error({message:"Voice RTVI connect failed",api_name:"voice-rtvi",error_message:t instanceof Error?t.message:String(t)}),e===B.current&&(n.showErrorToast("Could not connect voice. Please try again."),J(!1))}},[r,u,i,ne,Z,J]),ae=e.useCallback((e=!0)=>{U.current+=1,B.current+=1,q.current=0;const t=L.current;if(t)try{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)})}J(e)},[J]),oe=e.useCallback(e=>{L.current?.enableMic(e)},[]),ce=e.useCallback(e=>{const t=L.current;if(t){try{t.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)})}R.current&&z(),S.current.setMessages(t=>[...t,P(e)]),u(T(!0)),u(m(null)),K(),u(g(a.PROCESSING))}},[u,K,z]);e.useEffect(()=>{re.current=ae},[ae]);const ie=e.useRef(r),ue=e.useRef(!1);e.useEffect(()=>{const e=ie.current;if(ie.current=r,e===r)return;const t=F.current;t&&(ue.current=!0),t&&ae(!1),ue.current&&null!=r&&(ue.current=!1,se())},[r,ae,se]);const le=e.useRef(d);return e.useEffect(()=>{const e=le.current;le.current=d,e&&!d&&F.current&&ae(!1)},[d,ae]),e.useEffect(()=>()=>{re.current()},[]),{connect:se,disconnect:ae,sendText:ce,setMicEnabled:oe}}(r,c,i);e.useEffect(()=>{V(!d&&!R)},[d,R,V]);const G=L(!1,!1,d);e.useEffect(()=>(c.setVoiceSendText(M),()=>c.setVoiceSendText(null)),[c,M]);const B=e.useRef(!1);return{displayState:G,pipelineStatus:y,isAgentWorking:_,enterVoiceMode:e.useCallback(async()=>{if(B.current)return n.showErrorToast("Microphone permission changed — please reload the page to apply the new setting."),!1;const e=await l.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}),B.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 h(),!0},[l,h]),exitVoiceMode:e.useCallback(()=>{b()},[b]),toggleMute:e.useCallback(()=>{u(S(!d))},[u,d]),sendText:M,muted:d,serverMuted:R}},exports.useVoiceUI=()=>e.useContext(G),exports.voiceListenerMiddleware=B,exports.voiceReducer=b;
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"},a={IDLE:"idle",LISTENING:"listening",PROCESSING:"processing",SPEAKING:"speaking"},o={CASCADE:"cascade",REALTIME:"realtime"},c={OFFLINE:"offline",CONNECTING:"connecting",READY:"ready",IDLE:"idle",LISTENING:"listening",MUTED:"muted",PROCESSING:"processing",SPEAKING:"speaking"},i=[/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:a.IDLE,listeningEnabled:!1,muted:!1,serverMuted:!1,statusText:null,isAgentWorking:!1,responseStartTime:null,interimTranscript:null,botTurn:{active:!1,chatMsgId:null,segments:[]},selectedMicId:"",selectedSpeakerId:""}}const p=d(),g=r.createSlice({name:"voice",initialState:p,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 a=e.botTurn.segments.find(e=>e.id===r);a?(a.text=n,a.spokenChars=Math.max(a.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},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:E,setPipelineStatus:S,setListeningEnabled:m,setMuted:I,setServerMuted:T,setStatusText:C,setIsAgentWorking:f,setResponseStartTime:v,setInterimTranscript:k,openBotTurn:h,upsertBotSegment:_,clearBotTurn:x,setSelectedMicId:R,setSelectedSpeakerId:N,resetVoiceState:b,resetIndicatorState:y}=g.actions,M=g.reducer,A=e.createContext(null),D=()=>{const t=e.useContext(A);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 w(){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 O(e){try{const t=await fetch(n.API_ENDPOINTS.WEBRTC_ICE(e),{headers:w()});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 P="voice-user-",L=(e,t=`${P}${n.generateUniqueId()}`)=>({id:t,sender:"user",message:e,timestamp:(new Date).toISOString()}),V=(e,t)=>{const r=`${e}${n.generateUniqueId()}`;return{id:r,msg_id:r,sender:"agent",message:"",timestamp:(new Date).toISOString(),...t}};function G(){const e=t.useSelector(e=>e.voice.connectionStatus),r=t.useSelector(e=>e.voice.pipelineStatus),n=t.useSelector(e=>e.voice.listeningEnabled),o=t.useSelector(e=>e.voice.muted);return e===s.CONNECTING?c.CONNECTING:e!==s.CONNECTED?c.OFFLINE:n?o?c.MUTED:r===a.PROCESSING?c.PROCESSING:r===a.SPEAKING?c.SPEAKING:r===a.LISTENING?c.LISTENING:c.IDLE:c.READY}const B=e.createContext(null),U=e.createContext({strategyActive:!1,voiceSlot:null,onEnterVoice:null,onFloatingOrbClick:null,voiceInputHidden:!1,headerCenterSlot:null,floatOrbPositionRef:null}),q=r.createListenerMiddleware();q.startListening({actionCreator:R,effect:async e=>{const t=n.getStorageRef();t&&await t.setItem(l.SELECTED_MIC_ID,e.payload)}}),q.startListening({actionCreator:N,effect:async e=>{const t=n.getStorageRef();t&&await t.setItem(l.SELECTED_SPEAKER_ID,e.payload)}}),q.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(b())}});class F{async enumerate(){const e={microphones:[],speakers:[]};if(!navigator.mediaDevices?.enumerateDevices)return e;try{const e=await navigator.mediaDevices.enumerateDevices();let t=0,r=0;return{microphones:e.filter(e=>"audioinput"===e.kind).map(e=>({deviceId:e.deviceId,label:e.label||"Microphone "+ ++t})),speakers:e.filter(e=>"audiooutput"===e.kind).map(e=>({deviceId:e.deviceId,label:e.label||"Speaker "+ ++r}))}}catch{return e}}onChange(e){const t=navigator.mediaDevices;return t?.addEventListener?(t.addEventListener("devicechange",e),()=>t.removeEventListener("devicechange",e)):()=>{}}}const K=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{}}},W={attachBotAudioTrack:K,devices:new F,requestMicPermission:async function(){try{return(await navigator.mediaDevices.getUserMedia({audio:!0})).getTracks().forEach(e=>e.stop()),{granted:!0}}catch(e){return{granted:!1,error:e}}}};exports.AudioPlatformContext=A,exports.BrowserAudioDevices=F,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=c,exports.VOICE_PIPELINE_MODES=o,exports.VOICE_PIPELINE_STATUSES=a,exports.VOICE_STORAGE_KEYS=l,exports.VoiceContext=B,exports.VoiceUIContext=U,exports.attachBrowserBotAudioTrack=K,exports.browserAudioPlatform=W,exports.clearBotTurn=x,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(g.actions.setSelectedMicId(r)),n&&t(g.actions.setSelectedSpeakerId(n))},exports.openBotTurn=h,exports.resetIndicatorState=y,exports.setConnectionStatus=E,exports.setInterimTranscript=k,exports.setIsAgentWorking=f,exports.setListeningEnabled=m,exports.setMuted=I,exports.setPipelineStatus=S,exports.setResponseStartTime=v,exports.setSelectedMicId=R,exports.setSelectedSpeakerId=N,exports.setServerMuted=T,exports.setStatusText=C,exports.upsertBotSegment=_,exports.useAudioPlatform=D,exports.useMediaDevices=function(){const r=D(),[n,s]=e.useState([]),[a,o]=e.useState([]),c=t.useSelector(e=>e.voice.listeningEnabled),i=e.useCallback(async()=>{const e=await r.devices.enumerate();s(e.microphones),o(e.speakers)},[r]);return e.useEffect(()=>(i(),r.devices.onChange(i)),[r,i]),e.useEffect(()=>{c&&i()},[c,i]),{microphones:n,speakers:a,refresh:i}},exports.useVoice=()=>{const t=e.useContext(B);if(!t)throw new Error("useVoice must be used within VoiceProvider");return t},exports.useVoiceDisplayState=G,exports.useVoiceProviderValue=({conversationId:r,voiceBridge:c,transportFactory:l,assistantStartsConversation:d})=>{const p=t.useDispatch(),g=D(),R=t.useSelector(e=>e.voice.muted),N=t.useSelector(e=>e.voice.serverMuted),b=t.useSelector(e=>e.voice.pipelineStatus),y=t.useSelector(e=>e.voice.isAgentWorking),{connect:M,disconnect:A,sendText:B,setMicEnabled:U}=function(r,c,l,d){const p=t.useDispatch(),g=D(),I=t.useSelector(e=>e.auth.accessToken),R=e.useRef(c);e.useEffect(()=>{R.current=c});const N=e.useRef(null),b=e.useRef(new Map),y=e.useRef(""),M=e.useRef(!1),A=e.useRef(!1),G=e.useRef(null),B=e.useRef(""),U=e.useRef(null),q=e.useRef(null),F=e.useRef(null),K=e.useRef(0),W=e.useRef(0),j=e.useRef(!1),H=e.useRef(0),$=e.useRef(null),Y=e.useRef(null),Z=e.useRef(0),z=e.useRef(0),J=e.useRef(null),Q=e.useCallback(()=>{if(null!=$.current)return;const e=Date.now();$.current=e,p(v(e)),p(n.clearLastResponseTime())},[p]),X=e.useCallback(()=>{const e=$.current;$.current=null,p(v(null)),null!=e&&p(n.setLastResponseTime(Date.now()-e))},[p]),ee=e.useCallback(()=>{$.current=null,p(v(null))},[p]),te=e.useCallback((e=n.INDICATOR_TIMER_ACTIONS.KEEP)=>{p(C(null)),p(f(!1)),e===n.INDICATOR_TIMER_ACTIONS.FINALIZE?X():e===n.INDICATOR_TIMER_ACTIONS.CANCEL&&ee()},[p,X,ee]),[re]=e.useState(()=>({setStatusText:e=>p(C(e||null)),setIsAgentWorking:e=>p(f(e)),appendStreamText:()=>{},pushWidget:e=>{const t=V("voice-widget-",{widget:[e],message_type:["widget"]});R.current.setMessages(e=>[...e,t])},onPayment:e=>R.current.onPaymentInit?.(e),onConversationTitle:e=>R.current.onConversationTitle?.(e),onError:e=>{n.logger.error({message:"Voice RTVI server error",api_name:"voice-rtvi",error_message:e});const t=V("voice-error-",{message:e,message_type:["error"]});R.current.setMessages(e=>[...e,t])},onStatusActive:()=>{Q()}})),ne=e.useCallback(()=>{F.current&&(clearTimeout(F.current),F.current=null)},[]),se=e.useCallback(()=>[...b.current.entries()].sort((e,t)=>e[0]-t[0]).map(([,e])=>e.text).join(" ").trim(),[]),ae=e.useCallback(()=>{N.current=null,b.current=new Map,y.current="",p(x())},[p]),oe=e.useCallback(()=>{const e=N.current;if(e){const t=se();t&&R.current.updateStreamingMessage(e,t),R.current.commitStreamingMessages()}ae()},[se,ae]),ce=e.useCallback(e=>{ne(),q.current?.(),q.current=null,U.current=null,j.current=!1,p(m(!1)),p(S(a.IDLE)),p(E(s.DISCONNECTED)),p(k(null)),p(T(!1)),G.current=null,B.current="",M.current=!1,A.current=!1,e?oe():(R.current.discardStreamingMessages(),ae()),te(n.INDICATOR_TIMER_ACTIONS.CANCEL)},[p,te,ne,oe,ae]),ie=e.useCallback(()=>{if(N.current)return;const e=`voice-agent-${n.generateUniqueId()}`;N.current=e,b.current=new Map,y.current="",p(h({chatMsgId:e}))},[p]),ue=e.useCallback(e=>{const t=e.spoken_progress?(e.spoken_progress?.accumulated_text??"")+(e.spoken_progress?.remaining_text??""):e.text??"";if(!t)return;ie();const r=M.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}}(b.current,t):function(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}}(b.current,e,t,A.current);p(_(r));const n=se();n&&N.current&&n!==y.current&&(y.current=n,R.current.updateStreamingMessage(N.current,n))},[p,ie,se]),le=e.useCallback(e=>{const t=e,r=t?.type,s=t?.data;switch(r){case"status_update":n.handleSharedEvent({kind:"status_update",statusText:s?.text??""},re);break;case"started-thinking":p(f(!0)),Q();break;case"stopped-thinking":te(n.INDICATOR_TIMER_ACTIONS.FINALIZE);break;case"clear_status":break;case"widget":s&&n.handleSharedEvent({kind:"widget",widget:s},re);break;case"payment":s&&n.handleSharedEvent({kind:"payment",payment:s},re);break;case"conversation_title":{const e="string"==typeof s?.text?s.text.trim():"";e&&n.handleSharedEvent({kind:"conversation_title",title:e},re);break}default:n.logger.debug({message:"Voice RTVI unhandled server-message",api_name:"voice-rtvi",event_type:r??"unknown"})}},[re,p,Q,te]),de=e.useRef(()=>{}),pe=e.useRef(async()=>{}),ge=e.useCallback(e=>{const t=()=>e===K.current;return{onConnected:()=>{t()&&(Z.current=0,z.current=0,H.current=0,p(E(s.CONNECTED)),p(m(!0)),p(S(a.IDLE)))},onBotReady:e=>{if(!t())return;const r=e?.about;M.current=r?.pipeline_mode===o.REALTIME,A.current=!0===r?.tts_word_timestamps,ne(),d&&(p(f(!0)),Q())},onDisconnected:()=>{if(!t())return;ce(!0);const e=W.current,s=J.current;if(J.current=null,"terminal"!==s){if("auth"===s){if(z.current>=1||null==r)return;return z.current+=1,void(async()=>{let t=!1;try{t=!!await(n.getTokenRefreshHandler()?.())}catch{}e===W.current&&t&&pe.current()})()}Z.current>=3||null==r?Z.current>=3&&n.showErrorToast("Voice connection lost. Please try again."):(Z.current+=1,Y.current&&clearTimeout(Y.current),Y.current=setTimeout(()=>{Y.current=null,pe.current()},1e3))}else n.logger.warn({message:"Voice RTVI terminal error — no retry",api_name:"voice-rtvi"})},onError:e=>{if(!t())return;const r=e?.data,s=r?.error??r?.message??"Voice error";n.handleSharedEvent({kind:"error",error:s},re),p(S(a.IDLE)),te(n.INDICATOR_TIMER_ACTIONS.CANCEL),r?.fatal&&(i.some(e=>e.test(s))?J.current="auth":u.some(e=>e.test(s))&&(J.current="terminal"),U.current?.disconnect().catch(()=>{}))},onTrackStarted:(e,r)=>{t()&&(r?.local||"audio"!==e.kind||(q.current?.(),q.current=g.attachBotAudioTrack(e)))},onUserMuteStarted:()=>{t()&&p(T(!0))},onUserMuteStopped:()=>{t()&&p(T(!1))},onUserStartedSpeaking:()=>{t()&&(G.current=null,B.current="",p(k(null)),p(S(a.LISTENING)))},onUserStoppedSpeaking:()=>{if(!t())return;p(S(a.PROCESSING)),p(f(!0)),p(C(null)),Q();const e=B.current.trim();if(e){const t=`${P}${n.generateUniqueId()}`;G.current=t,p(k(null)),R.current.setMessages(r=>[...r,L(e,t)])}},onUserTranscript:e=>{if(!t())return;if(!e.final)return void(e.text&&(B.current=e.text,M.current||p(k(e.text))));p(k(null));const r=e.text&&e.text.trim()?e.text:"",n=G.current;G.current=null,n?r&&R.current.setMessages(e=>e.map(e=>e.id===n?{...e,message:r}:e)):r&&R.current.setMessages(e=>[...e,L(r)])},onBotStartedSpeaking:()=>{t()&&(ie(),p(S(a.SPEAKING)),te(n.INDICATOR_TIMER_ACTIONS.FINALIZE))},onBotOutput:e=>{t()&&ue(e)},onBotStoppedSpeaking:()=>{t()&&(oe(),p(S(a.IDLE)),te(n.INDICATOR_TIMER_ACTIONS.FINALIZE))},onServerMessage:e=>{t()&&le(e)}}},[p,g,ne,ce,te,Q,ue,ie,oe,le,re,d,r]),Ee=e.useCallback(async()=>{if(U.current||j.current)return;if(!r)return;if(j.current=!0,n.isTokenAboutToExpire(n.readTokenExpiresAt())&&H.current<1){H.current+=1;const e=W.current;try{await Promise.resolve(n.getTokenRefreshHandler()?.())}catch{}if(e!==W.current)return}const e=++K.current,t=W.current;p(E(s.CONNECTING));const a=()=>t!==W.current||e!==K.current;try{const[{PipecatClient:t,LogLevel:s},o]=await Promise.all([Promise.resolve().then(()=>require("./index.module-DCaqEXL5.cjs")),O(r)]);if(a())return;const c=new t({transport:l(),enableMic:!0,enableCam:!1,callbacks:ge(e)});if(c.setLogLevel(s.WARN),a())return void c.disconnect().catch(()=>{});U.current=c,ne(),F.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."),de.current()},15e3),await c.connect({webrtcRequestParams:{endpoint:n.API_ENDPOINTS.WEBRTC_OFFER(r),headers:new Headers(w())},iceConfig:{iceServers:o}})}catch(t){n.logger.error({message:"Voice RTVI connect failed",api_name:"voice-rtvi",error_message:t instanceof Error?t.message:String(t)}),e===K.current&&(n.showErrorToast("Could not connect voice. Please try again."),ce(!1))}},[r,p,l,ge,ne,ce]),Se=e.useCallback((e=!0)=>{W.current+=1,K.current+=1,H.current=0,Y.current&&(clearTimeout(Y.current),Y.current=null),Z.current=0,z.current=0,J.current=null;const t=U.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)})}),ce(e)},[ce]),me=e.useCallback(e=>{U.current?.enableMic(e)},[]),Ie=e.useCallback(e=>{const t=U.current;if(t){try{t.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)})}N.current&&oe(),R.current.setMessages(t=>[...t,L(e)]),p(f(!0)),p(C(null)),Q(),p(S(a.PROCESSING))}},[p,Q,oe]);e.useEffect(()=>{de.current=Se},[Se]),e.useEffect(()=>{pe.current=Ee},[Ee]);const Te=e.useRef(r),Ce=e.useRef(!1);e.useEffect(()=>{const e=Te.current;if(Te.current=r,e===r)return;const t=j.current;t&&(Ce.current=!0),t&&Se(!1),Ce.current&&null!=r&&(Ce.current=!1,Ee())},[r,Se,Ee]);const fe=e.useRef(I);return e.useEffect(()=>{const e=fe.current;fe.current=I,e&&!I&&j.current&&Se(!1)},[I,Se]),e.useEffect(()=>()=>{de.current()},[]),{connect:Ee,disconnect:Se,sendText:Ie,setMicEnabled:me}}(r,c,l,d);e.useEffect(()=>{U(!R&&!N)},[R,N,U]);const q=G();e.useEffect(()=>(c.setVoiceSendText(B),()=>c.setVoiceSendText(null)),[c,B]);const F=e.useRef(!1);return{displayState:q,pipelineStatus:b,isAgentWorking:y,enterVoiceMode:e.useCallback(async()=>{if(F.current)return n.showErrorToast("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 n.logger.error({message:"Mic permission denied",api_name:"voice-mic",error_message:t instanceof Error?t.message:t?String(t):void 0}),F.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 M(),!0},[g,M]),exitVoiceMode:e.useCallback(()=>{A()},[A]),toggleMute:e.useCallback(()=>{p(I(!R))},[p,R]),sendText:B,muted:R,serverMuted:N}},exports.useVoiceUI=()=>e.useContext(U),exports.voiceListenerMiddleware=q,exports.voiceReducer=M;
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{createContext as e,useContext as t,useRef as r,useEffect as n,useCallback as s,useState as c}from"react";import{useDispatch as a,useSelector as i}from"react-redux";import{createSlice as o,createListenerMiddleware as u,isAnyOf as l}from"@reduxjs/toolkit";import{API_ENDPOINTS as d,logger as p,getCustomHeaders as g,readAccessToken as m,clearLastResponseTime as E,setLastResponseTime as S,INDICATOR_TIMER_ACTIONS as I,generateUniqueId as v,handleSharedEvent as T,isTokenAboutToExpire as y,readTokenExpiresAt as f,getTokenRefreshHandler as C,showErrorToast as k,getStorageRef as h,clearSession as N,sessionExpired as M}from"@1interface/shared-core";const b={DISCONNECTED:"disconnected",CONNECTING:"connecting",CONNECTED:"connected"},w={IDLE:"idle",LISTENING:"listening",PROCESSING:"processing",SPEAKING:"speaking"},_={CASCADE:"cascade",REALTIME:"realtime"},D={OFFLINE:"offline",CONNECTING:"connecting",READY:"ready",LOADING:"loading",IDLE:"idle",LISTENING:"listening",MUTED:"muted",PROCESSING:"processing",SPEAKING:"speaking"},L=15e3,R={SELECTED_MIC_ID:"voice_selectedMicId",SELECTED_SPEAKER_ID:"voice_selectedSpeakerId"},A={offline:{opacity:.15,playbackRate:.3,scale:1},connecting:{opacity:.4,playbackRate:.5,scale:1},ready:{opacity:.4,playbackRate:.4,scale:1},loading:{opacity:.6,playbackRate:.6,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 P(){return{connectionStatus:b.DISCONNECTED,pipelineStatus:w.IDLE,listeningEnabled:!1,muted:!1,serverMuted:!1,statusText:null,isAgentWorking:!1,responseStartTime:null,error:null,interimTranscript:null,botTurn:{active:!1,chatMsgId:null,segments:[]},selectedMicId:"",selectedSpeakerId:""}}const x=e=>async t=>{const[r,n]=await Promise.all([e.getItem(R.SELECTED_MIC_ID),e.getItem(R.SELECTED_SPEAKER_ID)]);r&&t(O.actions.setSelectedMicId(r)),n&&t(O.actions.setSelectedSpeakerId(n))},O=o({name:"voice",initialState:P(),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},setError(e,t){e.error=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 c=e.botTurn.segments.find(e=>e.id===r);c?(c.text=n,c.spokenChars=Math.max(c.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},resetVoiceState:()=>P(),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:V,setPipelineStatus:G,setListeningEnabled:B,setMuted:F,setServerMuted:W,setStatusText:K,setIsAgentWorking:U,setResponseStartTime:j,setError:$,setInterimTranscript:q,openBotTurn:Z,upsertBotSegment:H,clearBotTurn:Y,setSelectedMicId:X,setSelectedSpeakerId:z,resetVoiceState:J,resetIndicatorState:Q}=O.actions,ee=O.reducer,te=e(null),re=()=>{const e=t(te);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 ne(){const e={};for(const[t,r]of Object.entries(g()))r&&(e[t]=r);const t=m();return t&&(e["X-Access-Token"]=t),e}async function se(e){try{const t=await fetch(d.WEBRTC_ICE(e),{headers:ne()});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 ce="voice-user-",ae=(e,t=`${ce}${v()}`)=>({id:t,sender:"user",message:e,timestamp:(new Date).toISOString()});function ie(e,t,r){const n=i(e=>e.voice.connectionStatus),s=i(e=>e.voice.pipelineStatus),c=i(e=>e.voice.listeningEnabled),a=i(e=>e.voice.muted);return n===b.CONNECTING?D.CONNECTING:n===b.CONNECTED?c?e?D.LOADING:t?D.OFFLINE:r??a?D.MUTED:s===w.PROCESSING?D.PROCESSING:s===w.SPEAKING?D.SPEAKING:s===w.LISTENING?D.LISTENING:D.IDLE:D.READY:D.OFFLINE}const oe=e(null),ue=({conversationId:e,voiceBridge:t,transportFactory:c})=>{const o=a(),u=re(),l=i(e=>e.voice.muted),g=i(e=>e.voice.serverMuted),m=i(e=>e.voice.pipelineStatus),h=i(e=>e.voice.isAgentWorking),{connect:N,disconnect:M,sendText:D,setMicEnabled:L}=function(e,t,c){const o=a(),u=re(),l=i(e=>e.auth.accessToken),g=r(t);n(()=>{g.current=t});const m=r(null),h=r(new Map),N=r(""),M=r(!1),D=r(null),L=r(""),R=r(null),A=r(null),P=r(null),x=r(0),O=r(0),F=r(!1),X=r(0),z=r(null),J=s(()=>{if(null!=z.current)return;const e=Date.now();z.current=e,o(j(e)),o(E())},[o]),Q=s(()=>{const e=z.current;z.current=null,o(j(null)),null!=e&&o(S(Date.now()-e))},[o]),ee=s(()=>{z.current=null,o(j(null))},[o]),te=s((e=I.KEEP)=>{o(K(null)),o(U(!1)),e===I.FINALIZE?Q():e===I.CANCEL&&ee()},[o,Q,ee]),ie=r({setStatusText:e=>o(K(e||null)),setIsAgentWorking:e=>o(U(e)),appendStreamText:()=>{},pushWidget:e=>{const t=`voice-widget-${v()}`;g.current.setMessages(r=>[...r,{id:t,msg_id:t,sender:"agent",message:"",timestamp:(new Date).toISOString(),widget:[e],message_type:["widget"]}])},onPayment:e=>g.current.onPaymentInit?.(e),onConversationTitle:e=>g.current.onConversationTitle?.(e),onError:e=>{p.error({message:"Voice RTVI server error",api_name:"voice-rtvi",error_message:e}),o($(e))},onStatusActive:()=>{J()}}),oe=s(()=>{P.current&&(clearTimeout(P.current),P.current=null)},[]),ue=s(()=>[...h.current.entries()].sort((e,t)=>e[0]-t[0]).map(([,e])=>e).join(" ").trim(),[]),le=s(()=>{const e=m.current;if(e){const t=ue();t&&g.current.updateStreamingMessage(e,t),g.current.commitStreamingMessages()}m.current=null,h.current=new Map,N.current="",o(Y())},[o,ue]),de=s(e=>{oe(),A.current?.(),A.current=null,R.current=null,F.current=!1,o(B(!1)),o(G(w.IDLE)),o(V(b.DISCONNECTED)),o(q(null)),o(W(!1)),D.current=null,L.current="",M.current=!1,e?le():(g.current.discardStreamingMessages(),m.current=null,h.current=new Map,N.current="",o(Y())),te(I.CANCEL)},[o,te,oe,le]),pe=s(()=>{if(m.current)return;const e=`voice-agent-${v()}`;m.current=e,h.current=new Map,N.current="",o(Z({chatMsgId:e}))},[o]),ge=s(e=>{const t=e.spoken_progress?.accumulated_text??"",r=e.spoken_progress?t+(e.spoken_progress?.remaining_text??""):e.text??"";if(!r)return;if(pe(),M.current){const e=(h.current.get(0)??"")+r;h.current.set(0,e),o(H({id:0,text:e,spokenChars:e.length}))}else{const n=e.segment_id??0;h.current.set(n,r),o(H({id:n,text:r,spokenChars:e.spoken_progress?t.length:r.length}))}const n=ue();n&&m.current&&n!==N.current&&(N.current=n,g.current.updateStreamingMessage(m.current,n))},[o,pe,ue]),me=s(e=>{const t=e,r=t?.type,n=t?.data;switch(r){case"status_update":T({kind:"status_update",statusText:n?.text??""},ie.current);break;case"clear_status":te(I.KEEP);break;case"widget":n&&T({kind:"widget",widget:n},ie.current);break;case"payment":n&&T({kind:"payment",payment:n},ie.current);break;case"conversation_title":{const e="string"==typeof n?.text?n.text.trim():"";e&&T({kind:"conversation_title",title:e},ie.current);break}default:p.debug({message:"Voice RTVI unhandled server-message",api_name:"voice-rtvi",event_type:r??"unknown"})}},[te]),Ee=r(()=>{}),Se=s(e=>{const t=()=>e===x.current;return{onConnected:()=>{t()&&(o(V(b.CONNECTED)),o(B(!0)),o(G(w.IDLE)))},onBotReady:e=>{if(!t())return;const r=e?.about;M.current=r?.pipeline_mode===_.REALTIME,oe()},onDisconnected:()=>{t()&&de(!0)},onError:e=>{if(!t())return;const r=e?.data;T({kind:"error",error:r?.error??r?.message??"Voice error"},ie.current),o(G(w.IDLE)),te(I.CANCEL),r?.fatal&&Ee.current()},onTrackStarted:(e,r)=>{t()&&(r?.local||"audio"!==e.kind||(A.current?.(),A.current=u.attachBotAudioTrack(e)))},onUserMuteStarted:()=>{t()&&o(W(!0))},onUserMuteStopped:()=>{t()&&o(W(!1))},onUserStartedSpeaking:()=>{t()&&(D.current=null,L.current="",o(q(null)),o(G(w.LISTENING)))},onUserStoppedSpeaking:()=>{if(!t())return;o(G(w.PROCESSING)),o(U(!0)),o(K(null)),J();const e=L.current.trim();if(e){const t=`${ce}${v()}`;D.current=t,o(q(null)),g.current.setMessages(r=>[...r,ae(e,t)])}},onUserTranscript:e=>{if(!t())return;if(!e.final)return void(e.text&&(L.current=e.text,M.current||o(q(e.text))));o(q(null));const r=e.text&&e.text.trim()?e.text:"",n=D.current;D.current=null,n?r&&g.current.setMessages(e=>e.map(e=>e.id===n?{...e,message:r}:e)):r&&g.current.setMessages(e=>[...e,ae(r)])},onBotStartedSpeaking:()=>{t()&&(pe(),o(G(w.SPEAKING)),te(I.FINALIZE))},onBotOutput:e=>{t()&&ge(e)},onBotStoppedSpeaking:()=>{t()&&(le(),o(G(w.IDLE)),te(I.FINALIZE))},onServerMessage:e=>{t()&&me(e)}}},[o,u,oe,de,te,J,ge,pe,le,me]),Ie=s(async()=>{if(R.current||F.current)return;if(!e)return;if(F.current=!0,y(f())&&X.current<1){X.current+=1;const e=O.current;try{await Promise.resolve(C()?.())}catch{}if(e!==O.current)return}const t=++x.current,r=O.current;o(V(b.CONNECTING));const n=()=>r!==O.current||t!==x.current;try{const[{PipecatClient:r,LogLevel:s},a]=await Promise.all([import("./index.module-S2alA3-r.js"),se(e)]);if(n())return;const i=new r({transport:c(),enableMic:!0,enableCam:!1,callbacks:Se(t)});if(i.setLogLevel(s.WARN),n())return void i.disconnect();R.current=i,oe(),P.current=setTimeout(()=>{p.warn({message:"Voice RTVI connect timed out (no bot-ready)",api_name:"voice-rtvi"}),k("Voice connection timed out. Please try again."),Ee.current()},15e3),await i.connect({webrtcRequestParams:{endpoint:d.WEBRTC_OFFER(e),headers:new Headers(ne())},iceConfig:{iceServers:a}})}catch(e){p.error({message:"Voice RTVI connect failed",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)}),t===x.current&&(k("Could not connect voice. Please try again."),de(!1))}},[e,o,c,Se,oe,de]),ve=s((e=!0)=>{O.current+=1,x.current+=1,X.current=0;const t=R.current;if(t)try{t.disconnect()}catch(e){p.warn({message:"Voice RTVI disconnect threw",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}de(e)},[de]),Te=s(e=>{R.current?.enableMic(e)},[]),ye=s(e=>{const t=R.current;if(t){try{t.sendText(e,{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)})}m.current&&le(),g.current.setMessages(t=>[...t,ae(e)]),o(U(!0)),o(K(null)),J(),o(G(w.PROCESSING))}},[o,J,le]);n(()=>{Ee.current=ve},[ve]);const fe=r(e),Ce=r(!1);n(()=>{const t=fe.current;if(fe.current=e,t===e)return;const r=F.current;r&&(Ce.current=!0),r&&ve(!1),Ce.current&&null!=e&&(Ce.current=!1,Ie())},[e,ve,Ie]);const ke=r(l);return n(()=>{const e=ke.current;ke.current=l,e&&!l&&F.current&&ve(!1)},[l,ve]),n(()=>()=>{Ee.current()},[]),{connect:Ie,disconnect:ve,sendText:ye,setMicEnabled:Te}}(e,t,c);n(()=>{L(!l&&!g)},[l,g,L]);const R=ie(!1,!1,l);n(()=>(t.setVoiceSendText(D),()=>t.setVoiceSendText(null)),[t,D]);const A=r(!1);return{displayState:R,pipelineStatus:m,isAgentWorking:h,enterVoiceMode:s(async()=>{if(A.current)return k("Microphone permission changed — please reload the page to apply the new setting."),!1;const e=await u.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}),A.current=!0,k("Microphone access is required for voice mode. Please allow it in your browser's site settings and reload the page."),!1}return N(),!0},[u,N]),exitVoiceMode:s(()=>{M()},[M]),toggleMute:s(()=>{o(F(!l))},[o,l]),sendText:D,muted:l,serverMuted:g}},le=()=>{const e=t(oe);if(!e)throw new Error("useVoice must be used within VoiceProvider");return e},de=e({strategyActive:!1,voiceSlot:null,onEnterVoice:null,onFloatingOrbClick:null,voiceInputHidden:!1,headerCenterSlot:null,floatOrbPositionRef:null}),pe=()=>t(de);function ge(){const e=re(),[t,r]=c([]),[a,o]=c([]),u=i(e=>e.voice.listeningEnabled),l=s(async()=>{const t=await e.devices.enumerate();r(t.microphones),o(t.speakers)},[e]);return n(()=>(l(),e.devices.onChange(l)),[e,l]),n(()=>{u&&l()},[u,l]),{microphones:t,speakers:a,refresh:l}}const me=u();me.startListening({actionCreator:X,effect:async e=>{const t=h();t&&await t.setItem(R.SELECTED_MIC_ID,e.payload)}}),me.startListening({actionCreator:z,effect:async e=>{const t=h();t&&await t.setItem(R.SELECTED_SPEAKER_ID,e.payload)}}),me.startListening({matcher:l(N,M),effect:async(e,t)=>{const r=h();r&&await Promise.all([r.removeItem(R.SELECTED_MIC_ID),r.removeItem(R.SELECTED_SPEAKER_ID)]),t.dispatch(J())}});class Ee{async enumerate(){const e={microphones:[],speakers:[]};if(!navigator.mediaDevices?.enumerateDevices)return e;try{const e=await navigator.mediaDevices.enumerateDevices();let t=0,r=0;return{microphones:e.filter(e=>"audioinput"===e.kind).map(e=>({deviceId:e.deviceId,label:e.label||"Microphone "+ ++t})),speakers:e.filter(e=>"audiooutput"===e.kind).map(e=>({deviceId:e.deviceId,label:e.label||"Speaker "+ ++r}))}}catch{return e}}onChange(e){const t=navigator.mediaDevices;return t?.addEventListener?(t.addEventListener("devicechange",e),()=>t.removeEventListener("devicechange",e)):()=>{}}}const Se=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{}}},Ie={attachBotAudioTrack:Se,devices:new Ee,requestMicPermission:async function(){try{return(await navigator.mediaDevices.getUserMedia({audio:!0})).getTracks().forEach(e=>e.stop()),{granted:!0}}catch(e){return{granted:!1,error:e}}}};export{te as AudioPlatformContext,Ee as BrowserAudioDevices,A as ORB_STATE_CONFIG,b as VOICE_CONNECTION_STATUSES,L as VOICE_CONNECT_TIMEOUT_MS,D as VOICE_DISPLAY_STATES,_ as VOICE_PIPELINE_MODES,w as VOICE_PIPELINE_STATUSES,R as VOICE_STORAGE_KEYS,oe as VoiceContext,de as VoiceUIContext,Se as attachBrowserBotAudioTrack,Ie as browserAudioPlatform,Y as clearBotTurn,x as hydrateVoice,Z as openBotTurn,Q as resetIndicatorState,V as setConnectionStatus,$ as setError,q as setInterimTranscript,U as setIsAgentWorking,B as setListeningEnabled,F as setMuted,G as setPipelineStatus,j as setResponseStartTime,X as setSelectedMicId,z as setSelectedSpeakerId,W as setServerMuted,K as setStatusText,H as upsertBotSegment,re as useAudioPlatform,ge as useMediaDevices,le as useVoice,ie as useVoiceDisplayState,ue as useVoiceProviderValue,pe as useVoiceUI,me as voiceListenerMiddleware,ee as voiceReducer};
1
+ import{createContext as e,useContext as t,useRef as r,useEffect as n,useCallback as s,useState as c}from"react";import{useDispatch as a,useSelector as i}from"react-redux";import{createSlice as o,createListenerMiddleware as u,isAnyOf as l}from"@reduxjs/toolkit";import{API_ENDPOINTS as d,logger as p,getCustomHeaders as m,readAccessToken as g,AUTH_HEADERS as E,readDeviceToken as S,DEVICE_TOKEN_HEADER as I,clearLastResponseTime as v,setLastResponseTime as T,INDICATOR_TIMER_ACTIONS as f,generateUniqueId as y,handleSharedEvent as C,getTokenRefreshHandler as h,showErrorToast as k,isTokenAboutToExpire as b,readTokenExpiresAt as M,getStorageRef as N,clearSession as _,sessionExpired as w}from"@1interface/shared-core";const D={DISCONNECTED:"disconnected",CONNECTING:"connecting",CONNECTED:"connected"},x={IDLE:"idle",LISTENING:"listening",PROCESSING:"processing",SPEAKING:"speaking"},L={CASCADE:"cascade",REALTIME:"realtime"},R={OFFLINE:"offline",CONNECTING:"connecting",READY:"ready",IDLE:"idle",LISTENING:"listening",MUTED:"muted",PROCESSING:"processing",SPEAKING:"speaking"},P=15e3,A=[/rejected/i,/unauthori[sz]ed/i,/forbidden/i,/\b40[13]\b/],V=[/\b402\b/,/\b429\b/,/rate[\s-]?limit/i],O={SELECTED_MIC_ID:"voice_selectedMicId",SELECTED_SPEAKER_ID:"voice_selectedSpeakerId"},G={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 B(){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:[]},selectedMicId:"",selectedSpeakerId:""}}const F=e=>async t=>{const[r,n]=await Promise.all([e.getItem(O.SELECTED_MIC_ID),e.getItem(O.SELECTED_SPEAKER_ID)]);r&&t(W.actions.setSelectedMicId(r)),n&&t(W.actions.setSelectedSpeakerId(n))},W=o({name:"voice",initialState:B(),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 c=e.botTurn.segments.find(e=>e.id===r);c?(c.text=n,c.spokenChars=Math.max(c.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},resetVoiceState:()=>B(),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:K,setPipelineStatus:U,setListeningEnabled:j,setMuted:$,setServerMuted:q,setStatusText:Z,setIsAgentWorking:H,setResponseStartTime:Y,setInterimTranscript:z,openBotTurn:J,upsertBotSegment:Q,clearBotTurn:X,setSelectedMicId:ee,setSelectedSpeakerId:te,resetVoiceState:re,resetIndicatorState:ne}=W.actions,se=W.reducer,ce=e(null),ae=()=>{const e=t(ce);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 ie(){const e={};for(const[t,r]of Object.entries(m()))r&&(e[t]=r);const t=g();t&&(e[E.ACCESS_TOKEN]=t);const r=S();return r&&(e[I]=r),e}async function oe(e){try{const t=await fetch(d.WEBRTC_ICE(e),{headers:ie()});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 ue="voice-user-",le=(e,t=`${ue}${y()}`)=>({id:t,sender:"user",message:e,timestamp:(new Date).toISOString()}),de=(e,t)=>{const r=`${e}${y()}`;return{id:r,msg_id:r,sender:"agent",message:"",timestamp:(new Date).toISOString(),...t}};function pe(){const e=i(e=>e.voice.connectionStatus),t=i(e=>e.voice.pipelineStatus),r=i(e=>e.voice.listeningEnabled),n=i(e=>e.voice.muted);return e===D.CONNECTING?R.CONNECTING:e!==D.CONNECTED?R.OFFLINE:r?n?R.MUTED:t===x.PROCESSING?R.PROCESSING:t===x.SPEAKING?R.SPEAKING:t===x.LISTENING?R.LISTENING:R.IDLE:R.READY}const me=e(null),ge=({conversationId:e,voiceBridge:t,transportFactory:o,assistantStartsConversation:u})=>{const l=a(),m=ae(),g=i(e=>e.voice.muted),E=i(e=>e.voice.serverMuted),S=i(e=>e.voice.pipelineStatus),I=i(e=>e.voice.isAgentWorking),{connect:N,disconnect:_,sendText:w,setMicEnabled:R}=function(e,t,o,u){const l=a(),m=ae(),g=i(e=>e.auth.accessToken),E=r(t);n(()=>{E.current=t});const S=r(null),I=r(new Map),N=r(""),_=r(!1),w=r(!1),R=r(null),P=r(""),O=r(null),G=r(null),B=r(null),F=r(0),W=r(0),$=r(!1),ee=r(0),te=r(null),re=r(null),ne=r(0),se=r(0),ce=r(null),pe=s(()=>{if(null!=te.current)return;const e=Date.now();te.current=e,l(Y(e)),l(v())},[l]),me=s(()=>{const e=te.current;te.current=null,l(Y(null)),null!=e&&l(T(Date.now()-e))},[l]),ge=s(()=>{te.current=null,l(Y(null))},[l]),Ee=s((e=f.KEEP)=>{l(Z(null)),l(H(!1)),e===f.FINALIZE?me():e===f.CANCEL&&ge()},[l,me,ge]),[Se]=c(()=>({setStatusText:e=>l(Z(e||null)),setIsAgentWorking:e=>l(H(e)),appendStreamText:()=>{},pushWidget:e=>{const t=de("voice-widget-",{widget:[e],message_type:["widget"]});E.current.setMessages(e=>[...e,t])},onPayment:e=>E.current.onPaymentInit?.(e),onConversationTitle:e=>E.current.onConversationTitle?.(e),onError:e=>{p.error({message:"Voice RTVI server error",api_name:"voice-rtvi",error_message:e});const t=de("voice-error-",{message:e,message_type:["error"]});E.current.setMessages(e=>[...e,t])},onStatusActive:()=>{pe()}})),Ie=s(()=>{B.current&&(clearTimeout(B.current),B.current=null)},[]),ve=s(()=>[...I.current.entries()].sort((e,t)=>e[0]-t[0]).map(([,e])=>e.text).join(" ").trim(),[]),Te=s(()=>{S.current=null,I.current=new Map,N.current="",l(X())},[l]),fe=s(()=>{const e=S.current;if(e){const t=ve();t&&E.current.updateStreamingMessage(e,t),E.current.commitStreamingMessages()}Te()},[ve,Te]),ye=s(e=>{Ie(),G.current?.(),G.current=null,O.current=null,$.current=!1,l(j(!1)),l(U(x.IDLE)),l(K(D.DISCONNECTED)),l(z(null)),l(q(!1)),R.current=null,P.current="",_.current=!1,w.current=!1,e?fe():(E.current.discardStreamingMessages(),Te()),Ee(f.CANCEL)},[l,Ee,Ie,fe,Te]),Ce=s(()=>{if(S.current)return;const e=`voice-agent-${y()}`;S.current=e,I.current=new Map,N.current="",l(J({chatMsgId:e}))},[l]),he=s(e=>{const t=e.spoken_progress?(e.spoken_progress?.accumulated_text??"")+(e.spoken_progress?.remaining_text??""):e.text??"";if(!t)return;Ce();const r=_.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}}(I.current,t):function(e,t,r,n){const s=t.segment_id??0,c="completed"===t.spoken_status;if(c&&!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 a=e.get(s);return e.set(s,{text:r,completed:c||!0===a?.completed}),{id:s,text:r,spokenChars:n&&t.spoken_progress?(t.spoken_progress.accumulated_text??"").length:r.length}}(I.current,e,t,w.current);l(Q(r));const n=ve();n&&S.current&&n!==N.current&&(N.current=n,E.current.updateStreamingMessage(S.current,n))},[l,Ce,ve]),ke=s(e=>{const t=e,r=t?.type,n=t?.data;switch(r){case"status_update":C({kind:"status_update",statusText:n?.text??""},Se);break;case"started-thinking":l(H(!0)),pe();break;case"stopped-thinking":Ee(f.FINALIZE);break;case"clear_status":break;case"widget":n&&C({kind:"widget",widget:n},Se);break;case"payment":n&&C({kind:"payment",payment:n},Se);break;case"conversation_title":{const e="string"==typeof n?.text?n.text.trim():"";e&&C({kind:"conversation_title",title:e},Se);break}default:p.debug({message:"Voice RTVI unhandled server-message",api_name:"voice-rtvi",event_type:r??"unknown"})}},[Se,l,pe,Ee]),be=r(()=>{}),Me=r(async()=>{}),Ne=s(t=>{const r=()=>t===F.current;return{onConnected:()=>{r()&&(ne.current=0,se.current=0,ee.current=0,l(K(D.CONNECTED)),l(j(!0)),l(U(x.IDLE)))},onBotReady:e=>{if(!r())return;const t=e?.about;_.current=t?.pipeline_mode===L.REALTIME,w.current=!0===t?.tts_word_timestamps,Ie(),u&&(l(H(!0)),pe())},onDisconnected:()=>{if(!r())return;ye(!0);const t=W.current,n=ce.current;if(ce.current=null,"terminal"!==n){if("auth"===n){if(se.current>=1||null==e)return;return se.current+=1,void(async()=>{let e=!1;try{e=!!await(h()?.())}catch{}t===W.current&&e&&Me.current()})()}ne.current>=3||null==e?ne.current>=3&&k("Voice connection lost. Please try again."):(ne.current+=1,re.current&&clearTimeout(re.current),re.current=setTimeout(()=>{re.current=null,Me.current()},1e3))}else p.warn({message:"Voice RTVI terminal error — no retry",api_name:"voice-rtvi"})},onError:e=>{if(!r())return;const t=e?.data,n=t?.error??t?.message??"Voice error";C({kind:"error",error:n},Se),l(U(x.IDLE)),Ee(f.CANCEL),t?.fatal&&(A.some(e=>e.test(n))?ce.current="auth":V.some(e=>e.test(n))&&(ce.current="terminal"),O.current?.disconnect().catch(()=>{}))},onTrackStarted:(e,t)=>{r()&&(t?.local||"audio"!==e.kind||(G.current?.(),G.current=m.attachBotAudioTrack(e)))},onUserMuteStarted:()=>{r()&&l(q(!0))},onUserMuteStopped:()=>{r()&&l(q(!1))},onUserStartedSpeaking:()=>{r()&&(R.current=null,P.current="",l(z(null)),l(U(x.LISTENING)))},onUserStoppedSpeaking:()=>{if(!r())return;l(U(x.PROCESSING)),l(H(!0)),l(Z(null)),pe();const e=P.current.trim();if(e){const t=`${ue}${y()}`;R.current=t,l(z(null)),E.current.setMessages(r=>[...r,le(e,t)])}},onUserTranscript:e=>{if(!r())return;if(!e.final)return void(e.text&&(P.current=e.text,_.current||l(z(e.text))));l(z(null));const t=e.text&&e.text.trim()?e.text:"",n=R.current;R.current=null,n?t&&E.current.setMessages(e=>e.map(e=>e.id===n?{...e,message:t}:e)):t&&E.current.setMessages(e=>[...e,le(t)])},onBotStartedSpeaking:()=>{r()&&(Ce(),l(U(x.SPEAKING)),Ee(f.FINALIZE))},onBotOutput:e=>{r()&&he(e)},onBotStoppedSpeaking:()=>{r()&&(fe(),l(U(x.IDLE)),Ee(f.FINALIZE))},onServerMessage:e=>{r()&&ke(e)}}},[l,m,Ie,ye,Ee,pe,he,Ce,fe,ke,Se,u,e]),_e=s(async()=>{if(O.current||$.current)return;if(!e)return;if($.current=!0,b(M())&&ee.current<1){ee.current+=1;const e=W.current;try{await Promise.resolve(h()?.())}catch{}if(e!==W.current)return}const t=++F.current,r=W.current;l(K(D.CONNECTING));const n=()=>r!==W.current||t!==F.current;try{const[{PipecatClient:r,LogLevel:s},c]=await Promise.all([import("./index.module-S2alA3-r.js"),oe(e)]);if(n())return;const a=new r({transport:o(),enableMic:!0,enableCam:!1,callbacks:Ne(t)});if(a.setLogLevel(s.WARN),n())return void a.disconnect().catch(()=>{});O.current=a,Ie(),B.current=setTimeout(()=>{p.warn({message:"Voice RTVI connect timed out (no bot-ready)",api_name:"voice-rtvi"}),k("Voice connection timed out. Please try again."),be.current()},15e3),await a.connect({webrtcRequestParams:{endpoint:d.WEBRTC_OFFER(e),headers:new Headers(ie())},iceConfig:{iceServers:c}})}catch(e){p.error({message:"Voice RTVI connect failed",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)}),t===F.current&&(k("Could not connect voice. Please try again."),ye(!1))}},[e,l,o,Ne,Ie,ye]),we=s((e=!0)=>{W.current+=1,F.current+=1,ee.current=0,re.current&&(clearTimeout(re.current),re.current=null),ne.current=0,se.current=0,ce.current=null;const t=O.current;t?.disconnect().catch(e=>{p.warn({message:"Voice RTVI disconnect threw",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}),ye(e)},[ye]),De=s(e=>{O.current?.enableMic(e)},[]),xe=s(e=>{const t=O.current;if(t){try{t.sendText(e,{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&&fe(),E.current.setMessages(t=>[...t,le(e)]),l(H(!0)),l(Z(null)),pe(),l(U(x.PROCESSING))}},[l,pe,fe]);n(()=>{be.current=we},[we]),n(()=>{Me.current=_e},[_e]);const Le=r(e),Re=r(!1);n(()=>{const t=Le.current;if(Le.current=e,t===e)return;const r=$.current;r&&(Re.current=!0),r&&we(!1),Re.current&&null!=e&&(Re.current=!1,_e())},[e,we,_e]);const Pe=r(g);return n(()=>{const e=Pe.current;Pe.current=g,e&&!g&&$.current&&we(!1)},[g,we]),n(()=>()=>{be.current()},[]),{connect:_e,disconnect:we,sendText:xe,setMicEnabled:De}}(e,t,o,u);n(()=>{R(!g&&!E)},[g,E,R]);const P=pe();n(()=>(t.setVoiceSendText(w),()=>t.setVoiceSendText(null)),[t,w]);const O=r(!1);return{displayState:P,pipelineStatus:S,isAgentWorking:I,enterVoiceMode:s(async()=>{if(O.current)return k("Microphone permission changed — please reload the page to apply the new setting."),!1;const e=await m.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}),O.current=!0,k("Microphone access is required for voice mode. Please allow it in your browser's site settings and reload the page."),!1}return N(),!0},[m,N]),exitVoiceMode:s(()=>{_()},[_]),toggleMute:s(()=>{l($(!g))},[l,g]),sendText:w,muted:g,serverMuted:E}},Ee=()=>{const e=t(me);if(!e)throw new Error("useVoice must be used within VoiceProvider");return e},Se=e({strategyActive:!1,voiceSlot:null,onEnterVoice:null,onFloatingOrbClick:null,voiceInputHidden:!1,headerCenterSlot:null,floatOrbPositionRef:null}),Ie=()=>t(Se);function ve(){const e=ae(),[t,r]=c([]),[a,o]=c([]),u=i(e=>e.voice.listeningEnabled),l=s(async()=>{const t=await e.devices.enumerate();r(t.microphones),o(t.speakers)},[e]);return n(()=>(l(),e.devices.onChange(l)),[e,l]),n(()=>{u&&l()},[u,l]),{microphones:t,speakers:a,refresh:l}}const Te=u();Te.startListening({actionCreator:ee,effect:async e=>{const t=N();t&&await t.setItem(O.SELECTED_MIC_ID,e.payload)}}),Te.startListening({actionCreator:te,effect:async e=>{const t=N();t&&await t.setItem(O.SELECTED_SPEAKER_ID,e.payload)}}),Te.startListening({matcher:l(_,w),effect:async(e,t)=>{const r=N();r&&await Promise.all([r.removeItem(O.SELECTED_MIC_ID),r.removeItem(O.SELECTED_SPEAKER_ID)]),t.dispatch(re())}});class fe{async enumerate(){const e={microphones:[],speakers:[]};if(!navigator.mediaDevices?.enumerateDevices)return e;try{const e=await navigator.mediaDevices.enumerateDevices();let t=0,r=0;return{microphones:e.filter(e=>"audioinput"===e.kind).map(e=>({deviceId:e.deviceId,label:e.label||"Microphone "+ ++t})),speakers:e.filter(e=>"audiooutput"===e.kind).map(e=>({deviceId:e.deviceId,label:e.label||"Speaker "+ ++r}))}}catch{return e}}onChange(e){const t=navigator.mediaDevices;return t?.addEventListener?(t.addEventListener("devicechange",e),()=>t.removeEventListener("devicechange",e)):()=>{}}}const ye=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{}}},Ce={attachBotAudioTrack:ye,devices:new fe,requestMicPermission:async function(){try{return(await navigator.mediaDevices.getUserMedia({audio:!0})).getTracks().forEach(e=>e.stop()),{granted:!0}}catch(e){return{granted:!1,error:e}}}};export{ce as AudioPlatformContext,fe as BrowserAudioDevices,G as ORB_STATE_CONFIG,D as VOICE_CONNECTION_STATUSES,P as VOICE_CONNECT_TIMEOUT_MS,R as VOICE_DISPLAY_STATES,L as VOICE_PIPELINE_MODES,x as VOICE_PIPELINE_STATUSES,O as VOICE_STORAGE_KEYS,me as VoiceContext,Se as VoiceUIContext,ye as attachBrowserBotAudioTrack,Ce as browserAudioPlatform,X as clearBotTurn,F as hydrateVoice,J as openBotTurn,ne as resetIndicatorState,K as setConnectionStatus,z as setInterimTranscript,H as setIsAgentWorking,j as setListeningEnabled,$ as setMuted,U as setPipelineStatus,Y as setResponseStartTime,ee as setSelectedMicId,te as setSelectedSpeakerId,q as setServerMuted,Z as setStatusText,Q as upsertBotSegment,ae as useAudioPlatform,ve as useMediaDevices,Ee as useVoice,pe as useVoiceDisplayState,ge as useVoiceProviderValue,Ie as useVoiceUI,Te as voiceListenerMiddleware,se as voiceReducer};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1interface/voice-core",
3
- "version": "0.2.0-pipecat.1",
3
+ "version": "0.2.1",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -26,8 +26,8 @@
26
26
  "access": "public"
27
27
  },
28
28
  "dependencies": {
29
- "@pipecat-ai/client-js": "^1.12.0",
30
- "@1interface/shared-core": "^0.2.0-pipecat.1"
29
+ "@pipecat-ai/client-js": "^1.11.0",
30
+ "@1interface/shared-core": "^0.2.1"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "@reduxjs/toolkit": "^2.0.0",