@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 +3 -7
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +3 -3
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(
|
|
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
|
|
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.
|
|
30
|
-
"@1interface/shared-core": "^0.2.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",
|