@1interface/voice-core 0.2.0-pipecat.0 → 0.2.0

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";
@@ -171,7 +168,6 @@ export declare type VoiceAppDispatch = Dispatch<UnknownAction>;
171
168
  export declare interface VoiceChatBridge {
172
169
  setMessages: React.Dispatch<React.SetStateAction<Message[]>>;
173
170
  updateStreamingMessage: (chatMsgId: string, content: string) => void;
174
- addStreamingWidget: (chatMsgId: string, widget: unknown) => void;
175
171
  commitStreamingMessages: () => void;
176
172
  discardStreamingMessages: () => void;
177
173
  setVoiceSendText: (fn: ((text: string) => void) | null) => void;
@@ -213,6 +209,7 @@ export declare interface VoiceProviderParams {
213
209
  conversationId: string | null;
214
210
  voiceBridge: VoiceChatBridge;
215
211
  transportFactory: VoiceTransportFactory;
212
+ assistantStartsConversation: boolean;
216
213
  }
217
214
 
218
215
  export declare const voiceReducer: Reducer<VoiceState>;
@@ -233,7 +230,6 @@ export declare interface VoiceState {
233
230
  statusText: string | null;
234
231
  isAgentWorking: boolean;
235
232
  responseStartTime: number | null;
236
- error: string | null;
237
233
  interimTranscript: string | null;
238
234
  botTurn: BotTurnState;
239
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:x,clearBotTurn:N,setSelectedMicId:h,setSelectedSpeakerId:R,resetVoiceState:y,resetIndicatorState:_}=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:h,effect:async e=>{const t=n.getStorageRef();t&&await t.setItem(i.SELECTED_MIC_ID,e.payload)}}),B.startListening({actionCreator:R,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(y())}});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=N,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=_,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=h,exports.setSelectedSpeakerId=R,exports.setServerMuted=I,exports.setStatusText=m,exports.upsertBotSegment=x,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),h=t.useSelector(e=>e.voice.serverMuted),R=t.useSelector(e=>e.voice.pipelineStatus),y=t.useSelector(e=>e.voice.isAgentWorking),{connect:_,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 h=e.useRef(null),R=e.useRef(new Map),y=e.useRef(""),_=e.useRef(!1),b=e.useRef([]),M=e.useRef(null),L=e.useRef(""),V=e.useRef(null),G=e.useRef(null),B=e.useRef(null),U=e.useRef(0),F=e.useRef(0),q=e.useRef(!1),W=e.useRef(0),K=e.useRef(null),j=e.useCallback(()=>{if(null!=K.current)return;const e=Date.now();K.current=e,u(C(e)),u(n.clearLastResponseTime())},[u]),$=e.useCallback(()=>{const e=K.current;K.current=null,u(C(null)),null!=e&&u(n.setLastResponseTime(Date.now()-e))},[u]),H=e.useCallback(()=>{K.current=null,u(C(null))},[u]),Y=e.useCallback((e=n.INDICATOR_TIMER_ACTIONS.KEEP)=>{u(m(null)),u(T(!1)),e===n.INDICATOR_TIMER_ACTIONS.FINALIZE?$():e===n.INDICATOR_TIMER_ACTIONS.CANCEL&&H()},[u,$,H]),Z=e.useRef({setStatusText:e=>u(m(e||null)),setIsAgentWorking:e=>u(T(e)),appendStreamText:()=>{},pushWidget:e=>{b.current.push(e)},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:()=>{j()}}),X=e.useCallback(()=>{B.current&&(clearTimeout(B.current),B.current=null)},[]),z=e.useCallback(()=>[...R.current.entries()].sort((e,t)=>e[0]-t[0]).map(([,e])=>e).join(" ").trim(),[]),J=e.useCallback(()=>{const e=h.current;if(e){const t=z();t&&S.current.updateStreamingMessage(e,t),S.current.commitStreamingMessages()}h.current=null,R.current=new Map,y.current="",u(N())},[u,z]),Q=e.useCallback(()=>{const e=b.current;if(!e.length)return;b.current=[];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"]}])},[]),ee=e.useCallback(e=>{X(),G.current?.(),G.current=null,V.current=null,q.current=!1,u(E(!1)),u(g(a.IDLE)),u(p(s.DISCONNECTED)),u(v(null)),u(I(!1)),M.current=null,L.current="",_.current=!1,e?(J(),Q()):(S.current.discardStreamingMessages(),h.current=null,R.current=new Map,y.current="",b.current=[],u(N())),Y(n.INDICATOR_TIMER_ACTIONS.CANCEL)},[u,Y,X,J,Q]),te=e.useCallback(()=>{if(h.current)return;const e=`voice-agent-${n.generateUniqueId()}`;if(h.current=e,R.current=new Map,y.current="",u(k({chatMsgId:e})),b.current.length){const t=b.current;b.current=[];for(const r of t)S.current.addStreamingWidget(e,r)}},[u]),re=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(te(),_.current){const e=(R.current.get(0)??"")+r;R.current.set(0,e),u(x({id:0,text:e,spokenChars:e.length}))}else{const n=e.segment_id??0;R.current.set(n,r),u(x({id:n,text:r,spokenChars:e.spoken_progress?t.length:r.length}))}const n=z();n&&h.current&&n!==y.current&&(y.current=n,S.current.updateStreamingMessage(h.current,n))},[u,te,z]),ne=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??""},Z.current);break;case"clear_status":Y(n.INDICATOR_TIMER_ACTIONS.KEEP);break;case"widget":s&&n.handleSharedEvent({kind:"widget",widget:s},Z.current);break;case"payment":s&&n.handleSharedEvent({kind:"payment",payment:s},Z.current);break;case"conversation_title":{const e="string"==typeof s?.text?s.text.trim():"";e&&n.handleSharedEvent({kind:"conversation_title",title:e},Z.current);break}default:n.logger.debug({message:"Voice RTVI unhandled server-message",api_name:"voice-rtvi",event_type:r??"unknown"})}},[Y]),se=e.useRef(()=>{}),ae=e.useCallback(e=>{const t=()=>e===U.current;return{onConnected:()=>{t()&&(u(p(s.CONNECTED)),u(E(!0)),u(g(a.IDLE)))},onBotReady:e=>{if(!t())return;const r=e?.about;_.current=r?.pipeline_mode===o.REALTIME,X()},onDisconnected:()=>{t()&&ee(!0)},onError:e=>{if(!t())return;const r=e?.data;n.handleSharedEvent({kind:"error",error:r?.error??r?.message??"Voice error"},Z.current),u(g(a.IDLE)),Y(n.INDICATOR_TIMER_ACTIONS.CANCEL),r?.fatal&&se.current()},onTrackStarted:(e,r)=>{t()&&(r?.local||"audio"!==e.kind||(G.current?.(),G.current=l.attachBotAudioTrack(e)))},onUserMuteStarted:()=>{t()&&u(I(!0))},onUserMuteStopped:()=>{t()&&u(I(!1))},onUserStartedSpeaking:()=>{t()&&(Q(),M.current=null,L.current="",u(v(null)),u(g(a.LISTENING)))},onUserStoppedSpeaking:()=>{if(!t())return;u(g(a.PROCESSING)),u(T(!0)),u(m(null)),j();const e=L.current.trim();if(e){const t=`${w}${n.generateUniqueId()}`;M.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&&(L.current=e.text,_.current||u(v(e.text))));u(v(null));const r=e.text&&e.text.trim()?e.text:"",n=M.current;M.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()&&(te(),u(g(a.SPEAKING)),Y(n.INDICATOR_TIMER_ACTIONS.FINALIZE))},onBotOutput:e=>{t()&&re(e)},onBotStoppedSpeaking:()=>{t()&&(J(),u(g(a.IDLE)),Y(n.INDICATOR_TIMER_ACTIONS.FINALIZE))},onServerMessage:e=>{t()&&ne(e)}}},[u,l,X,ee,Y,j,re,te,J,Q,ne]),oe=e.useCallback(async()=>{if(V.current||q.current)return;if(!r)return;if(q.current=!0,n.isTokenAboutToExpire(n.readTokenExpiresAt())&&W.current<1){W.current+=1;const e=F.current;try{await Promise.resolve(n.getTokenRefreshHandler()?.())}catch{}if(e!==F.current)return}const e=++U.current,t=F.current;u(p(s.CONNECTING));const a=()=>t!==F.current||e!==U.current;try{const[{PipecatClient:t},s]=await Promise.all([Promise.resolve().then(()=>require("./index.module-DCaqEXL5.cjs")),O(r)]);if(a())return;const o=new t({transport:i(),enableMic:!0,enableCam:!1,callbacks:ae(e)});if(a())return void o.disconnect();V.current=o,X(),B.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."),se.current()},15e3),await o.connect({webrtcRequestParams:{endpoint:n.API_ENDPOINTS.WEBRTC_OFFER(r),headers:new Headers(D())},iceConfig:{iceServers:s}})}catch(t){n.logger.error({message:"Voice RTVI connect failed",api_name:"voice-rtvi",error_message:t instanceof Error?t.message:String(t)}),e===U.current&&(n.showErrorToast("Could not connect voice. Please try again."),ee(!1))}},[r,u,i,ae,X,ee]),ce=e.useCallback((e=!0)=>{F.current+=1,U.current+=1,W.current=0;const t=V.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)})}ee(e)},[ee]),ie=e.useCallback(e=>{V.current?.enableMic(e)},[]),ue=e.useCallback(e=>{const t=V.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)})}S.current.setMessages(t=>[...t,P(e)]),u(T(!0)),u(m(null)),j(),u(g(a.PROCESSING))}},[u,j]);e.useEffect(()=>{se.current=ce},[ce]);const le=e.useRef(r),de=e.useRef(!1);e.useEffect(()=>{const e=le.current;if(le.current=r,e===r)return;const t=q.current;t&&(de.current=!0),t&&ce(!1),de.current&&null!=r&&(de.current=!1,oe())},[r,ce,oe]);const pe=e.useRef(d);return e.useEffect(()=>{const e=pe.current;pe.current=d,e&&!d&&q.current&&ce(!1)},[d,ce]),e.useEffect(()=>()=>{se.current()},[]),{connect:oe,disconnect:ce,sendText:ue,setMicEnabled:ie}}(r,c,i);e.useEffect(()=>{V(!d&&!h)},[d,h,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:R,isAgentWorking:y,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 _(),!0},[l,_]),exitVoiceMode:e.useCallback(()=>{b()},[b]),toggleMute:e.useCallback(()=>{u(S(!d))},[u,d]),sendText:M,muted:d,serverMuted:h}},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:x,clearBotTurn:_,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();return t&&(e[n.AUTH_HEADERS.ACCESS_TOKEN]=t),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 W=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{}}},K={attachBotAudioTrack:W,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=W,exports.browserAudioPlatform=K,exports.clearBotTurn=_,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=x,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),W=e.useRef(0),K=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(_())},[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(x(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===W.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=K.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===K.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=K.current;try{await Promise.resolve(n.getTokenRefreshHandler()?.())}catch{}if(e!==K.current)return}const e=++W.current,t=K.current;p(E(s.CONNECTING));const a=()=>t!==K.current||e!==W.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===W.current&&(n.showErrorToast("Could not connect voice. Please try again."),ce(!1))}},[r,p,l,ge,ne,ce]),Se=e.useCallback((e=!0)=>{K.current+=1,W.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 f,readTokenExpiresAt as y,getTokenRefreshHandler as C,showErrorToast as k,getStorageRef as h,clearSession as M,sessionExpired as N}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"},P={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 x(){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 A=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:x(),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:()=>x(),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:M,disconnect:N,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),M=r(""),N=r(!1),D=r([]),L=r(null),R=r(""),P=r(null),x=r(null),A=r(null),O=r(0),F=r(0),X=r(!1),z=r(0),J=r(null),Q=s(()=>{if(null!=J.current)return;const e=Date.now();J.current=e,o(j(e)),o(E())},[o]),ee=s(()=>{const e=J.current;J.current=null,o(j(null)),null!=e&&o(S(Date.now()-e))},[o]),te=s(()=>{J.current=null,o(j(null))},[o]),ie=s((e=I.KEEP)=>{o(K(null)),o(U(!1)),e===I.FINALIZE?ee():e===I.CANCEL&&te()},[o,ee,te]),oe=r({setStatusText:e=>o(K(e||null)),setIsAgentWorking:e=>o(U(e)),appendStreamText:()=>{},pushWidget:e=>{D.current.push(e)},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:()=>{Q()}}),ue=s(()=>{A.current&&(clearTimeout(A.current),A.current=null)},[]),le=s(()=>[...h.current.entries()].sort((e,t)=>e[0]-t[0]).map(([,e])=>e).join(" ").trim(),[]),de=s(()=>{const e=m.current;if(e){const t=le();t&&g.current.updateStreamingMessage(e,t),g.current.commitStreamingMessages()}m.current=null,h.current=new Map,M.current="",o(Y())},[o,le]),pe=s(()=>{const e=D.current;if(!e.length)return;D.current=[];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"]}])},[]),ge=s(e=>{ue(),x.current?.(),x.current=null,P.current=null,X.current=!1,o(B(!1)),o(G(w.IDLE)),o(V(b.DISCONNECTED)),o(q(null)),o(W(!1)),L.current=null,R.current="",N.current=!1,e?(de(),pe()):(g.current.discardStreamingMessages(),m.current=null,h.current=new Map,M.current="",D.current=[],o(Y())),ie(I.CANCEL)},[o,ie,ue,de,pe]),me=s(()=>{if(m.current)return;const e=`voice-agent-${v()}`;if(m.current=e,h.current=new Map,M.current="",o(Z({chatMsgId:e})),D.current.length){const t=D.current;D.current=[];for(const r of t)g.current.addStreamingWidget(e,r)}},[o]),Ee=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(me(),N.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=le();n&&m.current&&n!==M.current&&(M.current=n,g.current.updateStreamingMessage(m.current,n))},[o,me,le]),Se=s(e=>{const t=e,r=t?.type,n=t?.data;switch(r){case"status_update":T({kind:"status_update",statusText:n?.text??""},oe.current);break;case"clear_status":ie(I.KEEP);break;case"widget":n&&T({kind:"widget",widget:n},oe.current);break;case"payment":n&&T({kind:"payment",payment:n},oe.current);break;case"conversation_title":{const e="string"==typeof n?.text?n.text.trim():"";e&&T({kind:"conversation_title",title:e},oe.current);break}default:p.debug({message:"Voice RTVI unhandled server-message",api_name:"voice-rtvi",event_type:r??"unknown"})}},[ie]),Ie=r(()=>{}),ve=s(e=>{const t=()=>e===O.current;return{onConnected:()=>{t()&&(o(V(b.CONNECTED)),o(B(!0)),o(G(w.IDLE)))},onBotReady:e=>{if(!t())return;const r=e?.about;N.current=r?.pipeline_mode===_.REALTIME,ue()},onDisconnected:()=>{t()&&ge(!0)},onError:e=>{if(!t())return;const r=e?.data;T({kind:"error",error:r?.error??r?.message??"Voice error"},oe.current),o(G(w.IDLE)),ie(I.CANCEL),r?.fatal&&Ie.current()},onTrackStarted:(e,r)=>{t()&&(r?.local||"audio"!==e.kind||(x.current?.(),x.current=u.attachBotAudioTrack(e)))},onUserMuteStarted:()=>{t()&&o(W(!0))},onUserMuteStopped:()=>{t()&&o(W(!1))},onUserStartedSpeaking:()=>{t()&&(pe(),L.current=null,R.current="",o(q(null)),o(G(w.LISTENING)))},onUserStoppedSpeaking:()=>{if(!t())return;o(G(w.PROCESSING)),o(U(!0)),o(K(null)),Q();const e=R.current.trim();if(e){const t=`${ce}${v()}`;L.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&&(R.current=e.text,N.current||o(q(e.text))));o(q(null));const r=e.text&&e.text.trim()?e.text:"",n=L.current;L.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()&&(me(),o(G(w.SPEAKING)),ie(I.FINALIZE))},onBotOutput:e=>{t()&&Ee(e)},onBotStoppedSpeaking:()=>{t()&&(de(),o(G(w.IDLE)),ie(I.FINALIZE))},onServerMessage:e=>{t()&&Se(e)}}},[o,u,ue,ge,ie,Q,Ee,me,de,pe,Se]),Te=s(async()=>{if(P.current||X.current)return;if(!e)return;if(X.current=!0,f(y())&&z.current<1){z.current+=1;const e=F.current;try{await Promise.resolve(C()?.())}catch{}if(e!==F.current)return}const t=++O.current,r=F.current;o(V(b.CONNECTING));const n=()=>r!==F.current||t!==O.current;try{const[{PipecatClient:r},s]=await Promise.all([import("./index.module-S2alA3-r.js"),se(e)]);if(n())return;const a=new r({transport:c(),enableMic:!0,enableCam:!1,callbacks:ve(t)});if(n())return void a.disconnect();P.current=a,ue(),A.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."),Ie.current()},15e3),await a.connect({webrtcRequestParams:{endpoint:d.WEBRTC_OFFER(e),headers:new Headers(ne())},iceConfig:{iceServers:s}})}catch(e){p.error({message:"Voice RTVI connect failed",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)}),t===O.current&&(k("Could not connect voice. Please try again."),ge(!1))}},[e,o,c,ve,ue,ge]),fe=s((e=!0)=>{F.current+=1,O.current+=1,z.current=0;const t=P.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)})}ge(e)},[ge]),ye=s(e=>{P.current?.enableMic(e)},[]),Ce=s(e=>{const t=P.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)})}g.current.setMessages(t=>[...t,ae(e)]),o(U(!0)),o(K(null)),Q(),o(G(w.PROCESSING))}},[o,Q]);n(()=>{Ie.current=fe},[fe]);const ke=r(e),he=r(!1);n(()=>{const t=ke.current;if(ke.current=e,t===e)return;const r=X.current;r&&(he.current=!0),r&&fe(!1),he.current&&null!=e&&(he.current=!1,Te())},[e,fe,Te]);const Me=r(l);return n(()=>{const e=Me.current;Me.current=l,e&&!l&&X.current&&fe(!1)},[l,fe]),n(()=>()=>{Ie.current()},[]),{connect:Te,disconnect:fe,sendText:Ce,setMicEnabled:ye}}(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 P=r(!1);return{displayState:R,pipelineStatus:m,isAgentWorking:h,enterVoiceMode:s(async()=>{if(P.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}),P.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 M(),!0},[u,M]),exitVoiceMode:s(()=>{N()},[N]),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(M,N),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,P 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,A 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,clearLastResponseTime as S,setLastResponseTime as I,INDICATOR_TIMER_ACTIONS as v,generateUniqueId as T,handleSharedEvent as f,getTokenRefreshHandler as y,showErrorToast as C,isTokenAboutToExpire as h,readTokenExpiresAt as k,getStorageRef as b,clearSession as M,sessionExpired as N}from"@1interface/shared-core";const _={DISCONNECTED:"disconnected",CONNECTING:"connecting",CONNECTED:"connected"},w={IDLE:"idle",LISTENING:"listening",PROCESSING:"processing",SPEAKING:"speaking"},D={CASCADE:"cascade",REALTIME:"realtime"},x={OFFLINE:"offline",CONNECTING:"connecting",READY:"ready",IDLE:"idle",LISTENING:"listening",MUTED:"muted",PROCESSING:"processing",SPEAKING:"speaking"},L=15e3,R=[/rejected/i,/unauthori[sz]ed/i,/forbidden/i,/\b40[13]\b/],P=[/\b402\b/,/\b429\b/,/rate[\s-]?limit/i],A={SELECTED_MIC_ID:"voice_selectedMicId",SELECTED_SPEAKER_ID:"voice_selectedSpeakerId"},V={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 O(){return{connectionStatus:_.DISCONNECTED,pipelineStatus:w.IDLE,listeningEnabled:!1,muted:!1,serverMuted:!1,statusText:null,isAgentWorking:!1,responseStartTime:null,interimTranscript:null,botTurn:{active:!1,chatMsgId:null,segments:[]},selectedMicId:"",selectedSpeakerId:""}}const G=e=>async t=>{const[r,n]=await Promise.all([e.getItem(A.SELECTED_MIC_ID),e.getItem(A.SELECTED_SPEAKER_ID)]);r&&t(B.actions.setSelectedMicId(r)),n&&t(B.actions.setSelectedSpeakerId(n))},B=o({name:"voice",initialState:O(),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:()=>O(),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:W,setListeningEnabled:K,setMuted:U,setServerMuted:j,setStatusText:$,setIsAgentWorking:q,setResponseStartTime:Z,setInterimTranscript:H,openBotTurn:Y,upsertBotSegment:z,clearBotTurn:J,setSelectedMicId:Q,setSelectedSpeakerId:X,resetVoiceState:ee,resetIndicatorState:te}=B.actions,re=B.reducer,ne=e(null),se=()=>{const e=t(ne);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 ce(){const e={};for(const[t,r]of Object.entries(m()))r&&(e[t]=r);const t=g();return t&&(e[E.ACCESS_TOKEN]=t),e}async function ae(e){try{const t=await fetch(d.WEBRTC_ICE(e),{headers:ce()});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 ie="voice-user-",oe=(e,t=`${ie}${T()}`)=>({id:t,sender:"user",message:e,timestamp:(new Date).toISOString()}),ue=(e,t)=>{const r=`${e}${T()}`;return{id:r,msg_id:r,sender:"agent",message:"",timestamp:(new Date).toISOString(),...t}};function le(){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===_.CONNECTING?x.CONNECTING:e!==_.CONNECTED?x.OFFLINE:r?n?x.MUTED:t===w.PROCESSING?x.PROCESSING:t===w.SPEAKING?x.SPEAKING:t===w.LISTENING?x.LISTENING:x.IDLE:x.READY}const de=e(null),pe=({conversationId:e,voiceBridge:t,transportFactory:o,assistantStartsConversation:u})=>{const l=a(),m=se(),g=i(e=>e.voice.muted),E=i(e=>e.voice.serverMuted),b=i(e=>e.voice.pipelineStatus),M=i(e=>e.voice.isAgentWorking),{connect:N,disconnect:x,sendText:L,setMicEnabled:A}=function(e,t,o,u){const l=a(),m=se(),g=i(e=>e.auth.accessToken),E=r(t);n(()=>{E.current=t});const b=r(null),M=r(new Map),N=r(""),x=r(!1),L=r(!1),A=r(null),V=r(""),O=r(null),G=r(null),B=r(null),U=r(0),Q=r(0),X=r(!1),ee=r(0),te=r(null),re=r(null),ne=r(0),le=r(0),de=r(null),pe=s(()=>{if(null!=te.current)return;const e=Date.now();te.current=e,l(Z(e)),l(S())},[l]),me=s(()=>{const e=te.current;te.current=null,l(Z(null)),null!=e&&l(I(Date.now()-e))},[l]),ge=s(()=>{te.current=null,l(Z(null))},[l]),Ee=s((e=v.KEEP)=>{l($(null)),l(q(!1)),e===v.FINALIZE?me():e===v.CANCEL&&ge()},[l,me,ge]),[Se]=c(()=>({setStatusText:e=>l($(e||null)),setIsAgentWorking:e=>l(q(e)),appendStreamText:()=>{},pushWidget:e=>{const t=ue("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=ue("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(()=>[...M.current.entries()].sort((e,t)=>e[0]-t[0]).map(([,e])=>e.text).join(" ").trim(),[]),Te=s(()=>{b.current=null,M.current=new Map,N.current="",l(J())},[l]),fe=s(()=>{const e=b.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,X.current=!1,l(K(!1)),l(W(w.IDLE)),l(F(_.DISCONNECTED)),l(H(null)),l(j(!1)),A.current=null,V.current="",x.current=!1,L.current=!1,e?fe():(E.current.discardStreamingMessages(),Te()),Ee(v.CANCEL)},[l,Ee,Ie,fe,Te]),Ce=s(()=>{if(b.current)return;const e=`voice-agent-${T()}`;b.current=e,M.current=new Map,N.current="",l(Y({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=x.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}}(M.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}}(M.current,e,t,L.current);l(z(r));const n=ve();n&&b.current&&n!==N.current&&(N.current=n,E.current.updateStreamingMessage(b.current,n))},[l,Ce,ve]),ke=s(e=>{const t=e,r=t?.type,n=t?.data;switch(r){case"status_update":f({kind:"status_update",statusText:n?.text??""},Se);break;case"started-thinking":l(q(!0)),pe();break;case"stopped-thinking":Ee(v.FINALIZE);break;case"clear_status":break;case"widget":n&&f({kind:"widget",widget:n},Se);break;case"payment":n&&f({kind:"payment",payment:n},Se);break;case"conversation_title":{const e="string"==typeof n?.text?n.text.trim():"";e&&f({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===U.current;return{onConnected:()=>{r()&&(ne.current=0,le.current=0,ee.current=0,l(F(_.CONNECTED)),l(K(!0)),l(W(w.IDLE)))},onBotReady:e=>{if(!r())return;const t=e?.about;x.current=t?.pipeline_mode===D.REALTIME,L.current=!0===t?.tts_word_timestamps,Ie(),u&&(l(q(!0)),pe())},onDisconnected:()=>{if(!r())return;ye(!0);const t=Q.current,n=de.current;if(de.current=null,"terminal"!==n){if("auth"===n){if(le.current>=1||null==e)return;return le.current+=1,void(async()=>{let e=!1;try{e=!!await(y()?.())}catch{}t===Q.current&&e&&Me.current()})()}ne.current>=3||null==e?ne.current>=3&&C("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";f({kind:"error",error:n},Se),l(W(w.IDLE)),Ee(v.CANCEL),t?.fatal&&(R.some(e=>e.test(n))?de.current="auth":P.some(e=>e.test(n))&&(de.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(j(!0))},onUserMuteStopped:()=>{r()&&l(j(!1))},onUserStartedSpeaking:()=>{r()&&(A.current=null,V.current="",l(H(null)),l(W(w.LISTENING)))},onUserStoppedSpeaking:()=>{if(!r())return;l(W(w.PROCESSING)),l(q(!0)),l($(null)),pe();const e=V.current.trim();if(e){const t=`${ie}${T()}`;A.current=t,l(H(null)),E.current.setMessages(r=>[...r,oe(e,t)])}},onUserTranscript:e=>{if(!r())return;if(!e.final)return void(e.text&&(V.current=e.text,x.current||l(H(e.text))));l(H(null));const t=e.text&&e.text.trim()?e.text:"",n=A.current;A.current=null,n?t&&E.current.setMessages(e=>e.map(e=>e.id===n?{...e,message:t}:e)):t&&E.current.setMessages(e=>[...e,oe(t)])},onBotStartedSpeaking:()=>{r()&&(Ce(),l(W(w.SPEAKING)),Ee(v.FINALIZE))},onBotOutput:e=>{r()&&he(e)},onBotStoppedSpeaking:()=>{r()&&(fe(),l(W(w.IDLE)),Ee(v.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||X.current)return;if(!e)return;if(X.current=!0,h(k())&&ee.current<1){ee.current+=1;const e=Q.current;try{await Promise.resolve(y()?.())}catch{}if(e!==Q.current)return}const t=++U.current,r=Q.current;l(F(_.CONNECTING));const n=()=>r!==Q.current||t!==U.current;try{const[{PipecatClient:r,LogLevel:s},c]=await Promise.all([import("./index.module-S2alA3-r.js"),ae(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"}),C("Voice connection timed out. Please try again."),be.current()},15e3),await a.connect({webrtcRequestParams:{endpoint:d.WEBRTC_OFFER(e),headers:new Headers(ce())},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===U.current&&(C("Could not connect voice. Please try again."),ye(!1))}},[e,l,o,Ne,Ie,ye]),we=s((e=!0)=>{Q.current+=1,U.current+=1,ee.current=0,re.current&&(clearTimeout(re.current),re.current=null),ne.current=0,le.current=0,de.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)})}b.current&&fe(),E.current.setMessages(t=>[...t,oe(e)]),l(q(!0)),l($(null)),pe(),l(W(w.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=X.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&&X.current&&we(!1)},[g,we]),n(()=>()=>{be.current()},[]),{connect:_e,disconnect:we,sendText:xe,setMicEnabled:De}}(e,t,o,u);n(()=>{A(!g&&!E)},[g,E,A]);const V=le();n(()=>(t.setVoiceSendText(L),()=>t.setVoiceSendText(null)),[t,L]);const O=r(!1);return{displayState:V,pipelineStatus:b,isAgentWorking:M,enterVoiceMode:s(async()=>{if(O.current)return C("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,C("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(()=>{x()},[x]),toggleMute:s(()=>{l(U(!g))},[l,g]),sendText:L,muted:g,serverMuted:E}},me=()=>{const e=t(de);if(!e)throw new Error("useVoice must be used within VoiceProvider");return e},ge=e({strategyActive:!1,voiceSlot:null,onEnterVoice:null,onFloatingOrbClick:null,voiceInputHidden:!1,headerCenterSlot:null,floatOrbPositionRef:null}),Ee=()=>t(ge);function Se(){const e=se(),[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 Ie=u();Ie.startListening({actionCreator:Q,effect:async e=>{const t=b();t&&await t.setItem(A.SELECTED_MIC_ID,e.payload)}}),Ie.startListening({actionCreator:X,effect:async e=>{const t=b();t&&await t.setItem(A.SELECTED_SPEAKER_ID,e.payload)}}),Ie.startListening({matcher:l(M,N),effect:async(e,t)=>{const r=b();r&&await Promise.all([r.removeItem(A.SELECTED_MIC_ID),r.removeItem(A.SELECTED_SPEAKER_ID)]),t.dispatch(ee())}});class ve{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 Te=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{}}},fe={attachBotAudioTrack:Te,devices:new ve,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{ne as AudioPlatformContext,ve as BrowserAudioDevices,V as ORB_STATE_CONFIG,_ as VOICE_CONNECTION_STATUSES,L as VOICE_CONNECT_TIMEOUT_MS,x as VOICE_DISPLAY_STATES,D as VOICE_PIPELINE_MODES,w as VOICE_PIPELINE_STATUSES,A as VOICE_STORAGE_KEYS,de as VoiceContext,ge as VoiceUIContext,Te as attachBrowserBotAudioTrack,fe as browserAudioPlatform,J as clearBotTurn,G as hydrateVoice,Y as openBotTurn,te as resetIndicatorState,F as setConnectionStatus,H as setInterimTranscript,q as setIsAgentWorking,K as setListeningEnabled,U as setMuted,W as setPipelineStatus,Z as setResponseStartTime,Q as setSelectedMicId,X as setSelectedSpeakerId,j as setServerMuted,$ as setStatusText,z as upsertBotSegment,se as useAudioPlatform,Se as useMediaDevices,me as useVoice,le as useVoiceDisplayState,pe as useVoiceProviderValue,Ee as useVoiceUI,Ie as voiceListenerMiddleware,re as voiceReducer};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1interface/voice-core",
3
- "version": "0.2.0-pipecat.0",
3
+ "version": "0.2.0",
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.0"
29
+ "@pipecat-ai/client-js": "^1.11.0",
30
+ "@1interface/shared-core": "^0.2.0"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "@reduxjs/toolkit": "^2.0.0",