@1interface/voice-core 0.2.0-pipecat.0 → 0.2.0-pipecat.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +0 -1
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -171,7 +171,6 @@ export declare type VoiceAppDispatch = Dispatch<UnknownAction>;
|
|
|
171
171
|
export declare interface VoiceChatBridge {
|
|
172
172
|
setMessages: React.Dispatch<React.SetStateAction<Message[]>>;
|
|
173
173
|
updateStreamingMessage: (chatMsgId: string, content: string) => void;
|
|
174
|
-
addStreamingWidget: (chatMsgId: string, widget: unknown) => void;
|
|
175
174
|
commitStreamingMessages: () => void;
|
|
176
175
|
discardStreamingMessages: () => void;
|
|
177
176
|
setVoiceSendText: (fn: ((text: string) => void) | null) => void;
|
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",LOADING:"loading",IDLE:"idle",LISTENING:"listening",MUTED:"muted",PROCESSING:"processing",SPEAKING:"speaking"},i={SELECTED_MIC_ID:"voice_selectedMicId",SELECTED_SPEAKER_ID:"voice_selectedSpeakerId"};function u(){return{connectionStatus:s.DISCONNECTED,pipelineStatus:a.IDLE,listeningEnabled:!1,muted:!1,serverMuted:!1,statusText:null,isAgentWorking:!1,responseStartTime:null,error:null,interimTranscript:null,botTurn:{active:!1,chatMsgId:null,segments:[]},selectedMicId:"",selectedSpeakerId:""}}const l=u(),d=r.createSlice({name:"voice",initialState:l,reducers:{setConnectionStatus(e,t){e.connectionStatus=t.payload},setPipelineStatus(e,t){e.pipelineStatus=t.payload},setListeningEnabled(e,t){e.listeningEnabled=t.payload},setStatusText(e,t){e.statusText=t.payload},setIsAgentWorking(e,t){e.isAgentWorking=t.payload},setResponseStartTime(e,t){e.responseStartTime=t.payload},setError(e,t){e.error=t.payload},setInterimTranscript(e,t){e.interimTranscript=t.payload},openBotTurn(e,t){e.botTurn={active:!0,chatMsgId:t.payload?.chatMsgId??null,segments:[]}},upsertBotSegment(e,t){const{id:r,text:n,spokenChars:s}=t.payload;e.botTurn.active=!0;const a=e.botTurn.segments.find(e=>e.id===r);a?(a.text=n,a.spokenChars=Math.max(a.spokenChars,s)):(e.botTurn.segments.push({id:r,text:n,spokenChars:s}),e.botTurn.segments.sort((e,t)=>e.id-t.id))},clearBotTurn(e){e.botTurn={active:!1,chatMsgId:null,segments:[]}},setMuted(e,t){e.muted=t.payload},setServerMuted(e,t){e.serverMuted=t.payload},setSelectedMicId(e,t){e.selectedMicId=t.payload},setSelectedSpeakerId(e,t){e.selectedSpeakerId=t.payload},resetVoiceState:()=>u(),resetIndicatorState(e){e.statusText=null,e.isAgentWorking=!1,e.responseStartTime=null,e.interimTranscript=null,e.serverMuted=!1,e.botTurn={active:!1,chatMsgId:null,segments:[]}}}}),{setConnectionStatus:p,setPipelineStatus:g,setListeningEnabled:E,setMuted:S,setServerMuted:I,setStatusText:m,setIsAgentWorking:T,setResponseStartTime:C,setError:f,setInterimTranscript:v,openBotTurn:k,upsertBotSegment:N,clearBotTurn:x,setSelectedMicId:R,setSelectedSpeakerId:y,resetVoiceState:_,resetIndicatorState:h}=d.actions,b=d.reducer,M=e.createContext(null),A=()=>{const t=e.useContext(M);if(!t)throw new Error("useAudioPlatform must be used inside <AudioPlatformProvider>. <VoiceProvider> mounts one by default; if you replaced VoiceProvider, you must wire your own.");return t};function D(){const e={};for(const[t,r]of Object.entries(n.getCustomHeaders()))r&&(e[t]=r);const t=n.readAccessToken();return t&&(e["X-Access-Token"]=t),e}async function O(e){try{const t=await fetch(n.API_ENDPOINTS.WEBRTC_ICE(e),{headers:D()});return t.ok?(await t.json()).iceServers??[]:(n.logger.warn({message:"Voice ICE fetch failed — continuing without TURN",api_name:"voice-webrtc-ice",status:t.status}),[])}catch(e){return n.logger.warn({message:"Voice ICE fetch error — continuing without TURN",api_name:"voice-webrtc-ice",error_message:e instanceof Error?e.message:String(e)}),[]}}const w="voice-user-",P=(e,t=`${w}${n.generateUniqueId()}`)=>({id:t,sender:"user",message:e,timestamp:(new Date).toISOString()});function L(e,r,n){const o=t.useSelector(e=>e.voice.connectionStatus),i=t.useSelector(e=>e.voice.pipelineStatus),u=t.useSelector(e=>e.voice.listeningEnabled),l=t.useSelector(e=>e.voice.muted);return o===s.CONNECTING?c.CONNECTING:o===s.CONNECTED?u?e?c.LOADING:r?c.OFFLINE:n??l?c.MUTED:i===a.PROCESSING?c.PROCESSING:i===a.SPEAKING?c.SPEAKING:i===a.LISTENING?c.LISTENING:c.IDLE:c.READY:c.OFFLINE}const V=e.createContext(null),G=e.createContext({strategyActive:!1,voiceSlot:null,onEnterVoice:null,onFloatingOrbClick:null,voiceInputHidden:!1,headerCenterSlot:null,floatOrbPositionRef:null}),B=r.createListenerMiddleware();B.startListening({actionCreator:R,effect:async e=>{const t=n.getStorageRef();t&&await t.setItem(i.SELECTED_MIC_ID,e.payload)}}),B.startListening({actionCreator:y,effect:async e=>{const t=n.getStorageRef();t&&await t.setItem(i.SELECTED_SPEAKER_ID,e.payload)}}),B.startListening({matcher:r.isAnyOf(n.clearSession,n.sessionExpired),effect:async(e,t)=>{const r=n.getStorageRef();r&&await Promise.all([r.removeItem(i.SELECTED_MIC_ID),r.removeItem(i.SELECTED_SPEAKER_ID)]),t.dispatch(_())}});class U{async enumerate(){const e={microphones:[],speakers:[]};if(!navigator.mediaDevices?.enumerateDevices)return e;try{const e=await navigator.mediaDevices.enumerateDevices();let t=0,r=0;return{microphones:e.filter(e=>"audioinput"===e.kind).map(e=>({deviceId:e.deviceId,label:e.label||"Microphone "+ ++t})),speakers:e.filter(e=>"audiooutput"===e.kind).map(e=>({deviceId:e.deviceId,label:e.label||"Speaker "+ ++r}))}}catch{return e}}onChange(e){const t=navigator.mediaDevices;return t?.addEventListener?(t.addEventListener("devicechange",e),()=>t.removeEventListener("devicechange",e)):()=>{}}}const F=e=>{const t=document.createElement("audio");return t.autoplay=!0,t.style.display="none",t.srcObject=new MediaStream([e]),document.body.appendChild(t),()=>{try{t.pause(),t.srcObject=null,t.remove()}catch{}}},q={attachBotAudioTrack:F,devices:new U,requestMicPermission:async function(){try{return(await navigator.mediaDevices.getUserMedia({audio:!0})).getTracks().forEach(e=>e.stop()),{granted:!0}}catch(e){return{granted:!1,error:e}}}};exports.AudioPlatformContext=M,exports.BrowserAudioDevices=U,exports.ORB_STATE_CONFIG={offline:{opacity:.15,playbackRate:.3,scale:1},connecting:{opacity:.4,playbackRate:.5,scale:1},ready:{opacity:.4,playbackRate:.4,scale:1},loading:{opacity:.6,playbackRate:.6,scale:1},idle:{opacity:.6,playbackRate:.6,scale:1},listening:{opacity:.7,playbackRate:.7,scale:1},muted:{opacity:.7,playbackRate:.5,scale:1},processing:{opacity:1,playbackRate:1,scale:1},speaking:{opacity:1,playbackRate:1,scale:1.3}},exports.VOICE_CONNECTION_STATUSES=s,exports.VOICE_CONNECT_TIMEOUT_MS=15e3,exports.VOICE_DISPLAY_STATES=c,exports.VOICE_PIPELINE_MODES=o,exports.VOICE_PIPELINE_STATUSES=a,exports.VOICE_STORAGE_KEYS=i,exports.VoiceContext=V,exports.VoiceUIContext=G,exports.attachBrowserBotAudioTrack=F,exports.browserAudioPlatform=q,exports.clearBotTurn=x,exports.hydrateVoice=e=>async t=>{const[r,n]=await Promise.all([e.getItem(i.SELECTED_MIC_ID),e.getItem(i.SELECTED_SPEAKER_ID)]);r&&t(d.actions.setSelectedMicId(r)),n&&t(d.actions.setSelectedSpeakerId(n))},exports.openBotTurn=k,exports.resetIndicatorState=h,exports.setConnectionStatus=p,exports.setError=f,exports.setInterimTranscript=v,exports.setIsAgentWorking=T,exports.setListeningEnabled=E,exports.setMuted=S,exports.setPipelineStatus=g,exports.setResponseStartTime=C,exports.setSelectedMicId=R,exports.setSelectedSpeakerId=y,exports.setServerMuted=I,exports.setStatusText=m,exports.upsertBotSegment=N,exports.useAudioPlatform=A,exports.useMediaDevices=function(){const r=A(),[n,s]=e.useState([]),[a,o]=e.useState([]),c=t.useSelector(e=>e.voice.listeningEnabled),i=e.useCallback(async()=>{const e=await r.devices.enumerate();s(e.microphones),o(e.speakers)},[r]);return e.useEffect(()=>(i(),r.devices.onChange(i)),[r,i]),e.useEffect(()=>{c&&i()},[c,i]),{microphones:n,speakers:a,refresh:i}},exports.useVoice=()=>{const t=e.useContext(V);if(!t)throw new Error("useVoice must be used within VoiceProvider");return t},exports.useVoiceDisplayState=L,exports.useVoiceProviderValue=({conversationId:r,voiceBridge:c,transportFactory:i})=>{const u=t.useDispatch(),l=A(),d=t.useSelector(e=>e.voice.muted),R=t.useSelector(e=>e.voice.serverMuted),y=t.useSelector(e=>e.voice.pipelineStatus),_=t.useSelector(e=>e.voice.isAgentWorking),{connect:h,disconnect:b,sendText:M,setMicEnabled:V}=function(r,c,i){const u=t.useDispatch(),l=A(),d=t.useSelector(e=>e.auth.accessToken),S=e.useRef(c);e.useEffect(()=>{S.current=c});const R=e.useRef(null),y=e.useRef(new Map),_=e.useRef(""),h=e.useRef(!1),b=e.useRef(null),M=e.useRef(""),L=e.useRef(null),V=e.useRef(null),G=e.useRef(null),B=e.useRef(0),U=e.useRef(0),F=e.useRef(!1),q=e.useRef(0),W=e.useRef(null),K=e.useCallback(()=>{if(null!=W.current)return;const e=Date.now();W.current=e,u(C(e)),u(n.clearLastResponseTime())},[u]),j=e.useCallback(()=>{const e=W.current;W.current=null,u(C(null)),null!=e&&u(n.setLastResponseTime(Date.now()-e))},[u]),$=e.useCallback(()=>{W.current=null,u(C(null))},[u]),H=e.useCallback((e=n.INDICATOR_TIMER_ACTIONS.KEEP)=>{u(m(null)),u(T(!1)),e===n.INDICATOR_TIMER_ACTIONS.FINALIZE?j():e===n.INDICATOR_TIMER_ACTIONS.CANCEL&&$()},[u,j,$]),Y=e.useRef({setStatusText:e=>u(m(e||null)),setIsAgentWorking:e=>u(T(e)),appendStreamText:()=>{},pushWidget:e=>{const t=`voice-widget-${n.generateUniqueId()}`;S.current.setMessages(r=>[...r,{id:t,msg_id:t,sender:"agent",message:"",timestamp:(new Date).toISOString(),widget:[e],message_type:["widget"]}])},onPayment:e=>S.current.onPaymentInit?.(e),onConversationTitle:e=>S.current.onConversationTitle?.(e),onError:e=>{n.logger.error({message:"Voice RTVI server error",api_name:"voice-rtvi",error_message:e}),u(f(e))},onStatusActive:()=>{K()}}),Z=e.useCallback(()=>{G.current&&(clearTimeout(G.current),G.current=null)},[]),X=e.useCallback(()=>[...y.current.entries()].sort((e,t)=>e[0]-t[0]).map(([,e])=>e).join(" ").trim(),[]),z=e.useCallback(()=>{const e=R.current;if(e){const t=X();t&&S.current.updateStreamingMessage(e,t),S.current.commitStreamingMessages()}R.current=null,y.current=new Map,_.current="",u(x())},[u,X]),J=e.useCallback(e=>{Z(),V.current?.(),V.current=null,L.current=null,F.current=!1,u(E(!1)),u(g(a.IDLE)),u(p(s.DISCONNECTED)),u(v(null)),u(I(!1)),b.current=null,M.current="",h.current=!1,e?z():(S.current.discardStreamingMessages(),R.current=null,y.current=new Map,_.current="",u(x())),H(n.INDICATOR_TIMER_ACTIONS.CANCEL)},[u,H,Z,z]),Q=e.useCallback(()=>{if(R.current)return;const e=`voice-agent-${n.generateUniqueId()}`;R.current=e,y.current=new Map,_.current="",u(k({chatMsgId:e}))},[u]),ee=e.useCallback(e=>{const t=e.spoken_progress?.accumulated_text??"",r=e.spoken_progress?t+(e.spoken_progress?.remaining_text??""):e.text??"";if(!r)return;if(Q(),h.current){const e=(y.current.get(0)??"")+r;y.current.set(0,e),u(N({id:0,text:e,spokenChars:e.length}))}else{const n=e.segment_id??0;y.current.set(n,r),u(N({id:n,text:r,spokenChars:e.spoken_progress?t.length:r.length}))}const n=X();n&&R.current&&n!==_.current&&(_.current=n,S.current.updateStreamingMessage(R.current,n))},[u,Q,X]),te=e.useCallback(e=>{const t=e,r=t?.type,s=t?.data;switch(r){case"status_update":n.handleSharedEvent({kind:"status_update",statusText:s?.text??""},Y.current);break;case"clear_status":H(n.INDICATOR_TIMER_ACTIONS.KEEP);break;case"widget":s&&n.handleSharedEvent({kind:"widget",widget:s},Y.current);break;case"payment":s&&n.handleSharedEvent({kind:"payment",payment:s},Y.current);break;case"conversation_title":{const e="string"==typeof s?.text?s.text.trim():"";e&&n.handleSharedEvent({kind:"conversation_title",title:e},Y.current);break}default:n.logger.debug({message:"Voice RTVI unhandled server-message",api_name:"voice-rtvi",event_type:r??"unknown"})}},[H]),re=e.useRef(()=>{}),ne=e.useCallback(e=>{const t=()=>e===B.current;return{onConnected:()=>{t()&&(u(p(s.CONNECTED)),u(E(!0)),u(g(a.IDLE)))},onBotReady:e=>{if(!t())return;const r=e?.about;h.current=r?.pipeline_mode===o.REALTIME,Z()},onDisconnected:()=>{t()&&J(!0)},onError:e=>{if(!t())return;const r=e?.data;n.handleSharedEvent({kind:"error",error:r?.error??r?.message??"Voice error"},Y.current),u(g(a.IDLE)),H(n.INDICATOR_TIMER_ACTIONS.CANCEL),r?.fatal&&re.current()},onTrackStarted:(e,r)=>{t()&&(r?.local||"audio"!==e.kind||(V.current?.(),V.current=l.attachBotAudioTrack(e)))},onUserMuteStarted:()=>{t()&&u(I(!0))},onUserMuteStopped:()=>{t()&&u(I(!1))},onUserStartedSpeaking:()=>{t()&&(b.current=null,M.current="",u(v(null)),u(g(a.LISTENING)))},onUserStoppedSpeaking:()=>{if(!t())return;u(g(a.PROCESSING)),u(T(!0)),u(m(null)),K();const e=M.current.trim();if(e){const t=`${w}${n.generateUniqueId()}`;b.current=t,u(v(null)),S.current.setMessages(r=>[...r,P(e,t)])}},onUserTranscript:e=>{if(!t())return;if(!e.final)return void(e.text&&(M.current=e.text,h.current||u(v(e.text))));u(v(null));const r=e.text&&e.text.trim()?e.text:"",n=b.current;b.current=null,n?r&&S.current.setMessages(e=>e.map(e=>e.id===n?{...e,message:r}:e)):r&&S.current.setMessages(e=>[...e,P(r)])},onBotStartedSpeaking:()=>{t()&&(Q(),u(g(a.SPEAKING)),H(n.INDICATOR_TIMER_ACTIONS.FINALIZE))},onBotOutput:e=>{t()&&ee(e)},onBotStoppedSpeaking:()=>{t()&&(z(),u(g(a.IDLE)),H(n.INDICATOR_TIMER_ACTIONS.FINALIZE))},onServerMessage:e=>{t()&&te(e)}}},[u,l,Z,J,H,K,ee,Q,z,te]),se=e.useCallback(async()=>{if(L.current||F.current)return;if(!r)return;if(F.current=!0,n.isTokenAboutToExpire(n.readTokenExpiresAt())&&q.current<1){q.current+=1;const e=U.current;try{await Promise.resolve(n.getTokenRefreshHandler()?.())}catch{}if(e!==U.current)return}const e=++B.current,t=U.current;u(p(s.CONNECTING));const a=()=>t!==U.current||e!==B.current;try{const[{PipecatClient:t,LogLevel:s},o]=await Promise.all([Promise.resolve().then(()=>require("./index.module-DCaqEXL5.cjs")),O(r)]);if(a())return;const c=new t({transport:i(),enableMic:!0,enableCam:!1,callbacks:ne(e)});if(c.setLogLevel(s.WARN),a())return void c.disconnect();L.current=c,Z(),G.current=setTimeout(()=>{n.logger.warn({message:"Voice RTVI connect timed out (no bot-ready)",api_name:"voice-rtvi"}),n.showErrorToast("Voice connection timed out. Please try again."),re.current()},15e3),await c.connect({webrtcRequestParams:{endpoint:n.API_ENDPOINTS.WEBRTC_OFFER(r),headers:new Headers(D())},iceConfig:{iceServers:o}})}catch(t){n.logger.error({message:"Voice RTVI connect failed",api_name:"voice-rtvi",error_message:t instanceof Error?t.message:String(t)}),e===B.current&&(n.showErrorToast("Could not connect voice. Please try again."),J(!1))}},[r,u,i,ne,Z,J]),ae=e.useCallback((e=!0)=>{U.current+=1,B.current+=1,q.current=0;const t=L.current;if(t)try{t.disconnect()}catch(e){n.logger.warn({message:"Voice RTVI disconnect threw",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}J(e)},[J]),oe=e.useCallback(e=>{L.current?.enableMic(e)},[]),ce=e.useCallback(e=>{const t=L.current;if(t){try{t.sendText(e,{run_immediately:!0,audio_response:!0})}catch(e){return void n.logger.error({message:"Voice RTVI send-text failed",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}R.current&&z(),S.current.setMessages(t=>[...t,P(e)]),u(T(!0)),u(m(null)),K(),u(g(a.PROCESSING))}},[u,K,z]);e.useEffect(()=>{re.current=ae},[ae]);const ie=e.useRef(r),ue=e.useRef(!1);e.useEffect(()=>{const e=ie.current;if(ie.current=r,e===r)return;const t=F.current;t&&(ue.current=!0),t&&ae(!1),ue.current&&null!=r&&(ue.current=!1,se())},[r,ae,se]);const le=e.useRef(d);return e.useEffect(()=>{const e=le.current;le.current=d,e&&!d&&F.current&&ae(!1)},[d,ae]),e.useEffect(()=>()=>{re.current()},[]),{connect:se,disconnect:ae,sendText:ce,setMicEnabled:oe}}(r,c,i);e.useEffect(()=>{V(!d&&!R)},[d,R,V]);const G=L(!1,!1,d);e.useEffect(()=>(c.setVoiceSendText(M),()=>c.setVoiceSendText(null)),[c,M]);const B=e.useRef(!1);return{displayState:G,pipelineStatus:y,isAgentWorking:_,enterVoiceMode:e.useCallback(async()=>{if(B.current)return n.showErrorToast("Microphone permission changed — please reload the page to apply the new setting."),!1;const e=await l.requestMicPermission();if(!e.granted){const t=e.error;return n.logger.error({message:"Mic permission denied",api_name:"voice-mic",error_message:t instanceof Error?t.message:t?String(t):void 0}),B.current=!0,n.showErrorToast("Microphone access is required for voice mode. Please allow it in your browser's site settings and reload the page."),!1}return h(),!0},[l,h]),exitVoiceMode:e.useCallback(()=>{b()},[b]),toggleMute:e.useCallback(()=>{u(S(!d))},[u,d]),sendText:M,muted:d,serverMuted:R}},exports.useVoiceUI=()=>e.useContext(G),exports.voiceListenerMiddleware=B,exports.voiceReducer=b;
|
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 g,readAccessToken as m,clearLastResponseTime as E,setLastResponseTime as S,INDICATOR_TIMER_ACTIONS as I,generateUniqueId as v,handleSharedEvent as T,isTokenAboutToExpire as y,readTokenExpiresAt as f,getTokenRefreshHandler as C,showErrorToast as k,getStorageRef as h,clearSession as N,sessionExpired as M}from"@1interface/shared-core";const b={DISCONNECTED:"disconnected",CONNECTING:"connecting",CONNECTED:"connected"},w={IDLE:"idle",LISTENING:"listening",PROCESSING:"processing",SPEAKING:"speaking"},_={CASCADE:"cascade",REALTIME:"realtime"},D={OFFLINE:"offline",CONNECTING:"connecting",READY:"ready",LOADING:"loading",IDLE:"idle",LISTENING:"listening",MUTED:"muted",PROCESSING:"processing",SPEAKING:"speaking"},L=15e3,R={SELECTED_MIC_ID:"voice_selectedMicId",SELECTED_SPEAKER_ID:"voice_selectedSpeakerId"},A={offline:{opacity:.15,playbackRate:.3,scale:1},connecting:{opacity:.4,playbackRate:.5,scale:1},ready:{opacity:.4,playbackRate:.4,scale:1},loading:{opacity:.6,playbackRate:.6,scale:1},idle:{opacity:.6,playbackRate:.6,scale:1},listening:{opacity:.7,playbackRate:.7,scale:1},muted:{opacity:.7,playbackRate:.5,scale:1},processing:{opacity:1,playbackRate:1,scale:1},speaking:{opacity:1,playbackRate:1,scale:1.3}};function P(){return{connectionStatus:b.DISCONNECTED,pipelineStatus:w.IDLE,listeningEnabled:!1,muted:!1,serverMuted:!1,statusText:null,isAgentWorking:!1,responseStartTime:null,error:null,interimTranscript:null,botTurn:{active:!1,chatMsgId:null,segments:[]},selectedMicId:"",selectedSpeakerId:""}}const x=e=>async t=>{const[r,n]=await Promise.all([e.getItem(R.SELECTED_MIC_ID),e.getItem(R.SELECTED_SPEAKER_ID)]);r&&t(O.actions.setSelectedMicId(r)),n&&t(O.actions.setSelectedSpeakerId(n))},O=o({name:"voice",initialState:P(),reducers:{setConnectionStatus(e,t){e.connectionStatus=t.payload},setPipelineStatus(e,t){e.pipelineStatus=t.payload},setListeningEnabled(e,t){e.listeningEnabled=t.payload},setStatusText(e,t){e.statusText=t.payload},setIsAgentWorking(e,t){e.isAgentWorking=t.payload},setResponseStartTime(e,t){e.responseStartTime=t.payload},setError(e,t){e.error=t.payload},setInterimTranscript(e,t){e.interimTranscript=t.payload},openBotTurn(e,t){e.botTurn={active:!0,chatMsgId:t.payload?.chatMsgId??null,segments:[]}},upsertBotSegment(e,t){const{id:r,text:n,spokenChars:s}=t.payload;e.botTurn.active=!0;const c=e.botTurn.segments.find(e=>e.id===r);c?(c.text=n,c.spokenChars=Math.max(c.spokenChars,s)):(e.botTurn.segments.push({id:r,text:n,spokenChars:s}),e.botTurn.segments.sort((e,t)=>e.id-t.id))},clearBotTurn(e){e.botTurn={active:!1,chatMsgId:null,segments:[]}},setMuted(e,t){e.muted=t.payload},setServerMuted(e,t){e.serverMuted=t.payload},setSelectedMicId(e,t){e.selectedMicId=t.payload},setSelectedSpeakerId(e,t){e.selectedSpeakerId=t.payload},resetVoiceState:()=>P(),resetIndicatorState(e){e.statusText=null,e.isAgentWorking=!1,e.responseStartTime=null,e.interimTranscript=null,e.serverMuted=!1,e.botTurn={active:!1,chatMsgId:null,segments:[]}}}}),{setConnectionStatus:V,setPipelineStatus:G,setListeningEnabled:B,setMuted:F,setServerMuted:W,setStatusText:K,setIsAgentWorking:U,setResponseStartTime:j,setError:$,setInterimTranscript:q,openBotTurn:Z,upsertBotSegment:H,clearBotTurn:Y,setSelectedMicId:X,setSelectedSpeakerId:z,resetVoiceState:J,resetIndicatorState:Q}=O.actions,ee=O.reducer,te=e(null),re=()=>{const e=t(te);if(!e)throw new Error("useAudioPlatform must be used inside <AudioPlatformProvider>. <VoiceProvider> mounts one by default; if you replaced VoiceProvider, you must wire your own.");return e};function ne(){const e={};for(const[t,r]of Object.entries(g()))r&&(e[t]=r);const t=m();return t&&(e["X-Access-Token"]=t),e}async function se(e){try{const t=await fetch(d.WEBRTC_ICE(e),{headers:ne()});return t.ok?(await t.json()).iceServers??[]:(p.warn({message:"Voice ICE fetch failed — continuing without TURN",api_name:"voice-webrtc-ice",status:t.status}),[])}catch(e){return p.warn({message:"Voice ICE fetch error — continuing without TURN",api_name:"voice-webrtc-ice",error_message:e instanceof Error?e.message:String(e)}),[]}}const ce="voice-user-",ae=(e,t=`${ce}${v()}`)=>({id:t,sender:"user",message:e,timestamp:(new Date).toISOString()});function ie(e,t,r){const n=i(e=>e.voice.connectionStatus),s=i(e=>e.voice.pipelineStatus),c=i(e=>e.voice.listeningEnabled),a=i(e=>e.voice.muted);return n===b.CONNECTING?D.CONNECTING:n===b.CONNECTED?c?e?D.LOADING:t?D.OFFLINE:r??a?D.MUTED:s===w.PROCESSING?D.PROCESSING:s===w.SPEAKING?D.SPEAKING:s===w.LISTENING?D.LISTENING:D.IDLE:D.READY:D.OFFLINE}const oe=e(null),ue=({conversationId:e,voiceBridge:t,transportFactory:c})=>{const o=a(),u=re(),l=i(e=>e.voice.muted),g=i(e=>e.voice.serverMuted),m=i(e=>e.voice.pipelineStatus),h=i(e=>e.voice.isAgentWorking),{connect:N,disconnect:M,sendText:D,setMicEnabled:L}=function(e,t,c){const o=a(),u=re(),l=i(e=>e.auth.accessToken),g=r(t);n(()=>{g.current=t});const m=r(null),h=r(new Map),N=r(""),M=r(!1),D=r(null),L=r(""),R=r(null),A=r(null),P=r(null),x=r(0),O=r(0),F=r(!1),X=r(0),z=r(null),J=s(()=>{if(null!=z.current)return;const e=Date.now();z.current=e,o(j(e)),o(E())},[o]),Q=s(()=>{const e=z.current;z.current=null,o(j(null)),null!=e&&o(S(Date.now()-e))},[o]),ee=s(()=>{z.current=null,o(j(null))},[o]),te=s((e=I.KEEP)=>{o(K(null)),o(U(!1)),e===I.FINALIZE?Q():e===I.CANCEL&&ee()},[o,Q,ee]),ie=r({setStatusText:e=>o(K(e||null)),setIsAgentWorking:e=>o(U(e)),appendStreamText:()=>{},pushWidget:e=>{const t=`voice-widget-${v()}`;g.current.setMessages(r=>[...r,{id:t,msg_id:t,sender:"agent",message:"",timestamp:(new Date).toISOString(),widget:[e],message_type:["widget"]}])},onPayment:e=>g.current.onPaymentInit?.(e),onConversationTitle:e=>g.current.onConversationTitle?.(e),onError:e=>{p.error({message:"Voice RTVI server error",api_name:"voice-rtvi",error_message:e}),o($(e))},onStatusActive:()=>{J()}}),oe=s(()=>{P.current&&(clearTimeout(P.current),P.current=null)},[]),ue=s(()=>[...h.current.entries()].sort((e,t)=>e[0]-t[0]).map(([,e])=>e).join(" ").trim(),[]),le=s(()=>{const e=m.current;if(e){const t=ue();t&&g.current.updateStreamingMessage(e,t),g.current.commitStreamingMessages()}m.current=null,h.current=new Map,N.current="",o(Y())},[o,ue]),de=s(e=>{oe(),A.current?.(),A.current=null,R.current=null,F.current=!1,o(B(!1)),o(G(w.IDLE)),o(V(b.DISCONNECTED)),o(q(null)),o(W(!1)),D.current=null,L.current="",M.current=!1,e?le():(g.current.discardStreamingMessages(),m.current=null,h.current=new Map,N.current="",o(Y())),te(I.CANCEL)},[o,te,oe,le]),pe=s(()=>{if(m.current)return;const e=`voice-agent-${v()}`;m.current=e,h.current=new Map,N.current="",o(Z({chatMsgId:e}))},[o]),ge=s(e=>{const t=e.spoken_progress?.accumulated_text??"",r=e.spoken_progress?t+(e.spoken_progress?.remaining_text??""):e.text??"";if(!r)return;if(pe(),M.current){const e=(h.current.get(0)??"")+r;h.current.set(0,e),o(H({id:0,text:e,spokenChars:e.length}))}else{const n=e.segment_id??0;h.current.set(n,r),o(H({id:n,text:r,spokenChars:e.spoken_progress?t.length:r.length}))}const n=ue();n&&m.current&&n!==N.current&&(N.current=n,g.current.updateStreamingMessage(m.current,n))},[o,pe,ue]),me=s(e=>{const t=e,r=t?.type,n=t?.data;switch(r){case"status_update":T({kind:"status_update",statusText:n?.text??""},ie.current);break;case"clear_status":te(I.KEEP);break;case"widget":n&&T({kind:"widget",widget:n},ie.current);break;case"payment":n&&T({kind:"payment",payment:n},ie.current);break;case"conversation_title":{const e="string"==typeof n?.text?n.text.trim():"";e&&T({kind:"conversation_title",title:e},ie.current);break}default:p.debug({message:"Voice RTVI unhandled server-message",api_name:"voice-rtvi",event_type:r??"unknown"})}},[te]),Ee=r(()=>{}),Se=s(e=>{const t=()=>e===x.current;return{onConnected:()=>{t()&&(o(V(b.CONNECTED)),o(B(!0)),o(G(w.IDLE)))},onBotReady:e=>{if(!t())return;const r=e?.about;M.current=r?.pipeline_mode===_.REALTIME,oe()},onDisconnected:()=>{t()&&de(!0)},onError:e=>{if(!t())return;const r=e?.data;T({kind:"error",error:r?.error??r?.message??"Voice error"},ie.current),o(G(w.IDLE)),te(I.CANCEL),r?.fatal&&Ee.current()},onTrackStarted:(e,r)=>{t()&&(r?.local||"audio"!==e.kind||(A.current?.(),A.current=u.attachBotAudioTrack(e)))},onUserMuteStarted:()=>{t()&&o(W(!0))},onUserMuteStopped:()=>{t()&&o(W(!1))},onUserStartedSpeaking:()=>{t()&&(D.current=null,L.current="",o(q(null)),o(G(w.LISTENING)))},onUserStoppedSpeaking:()=>{if(!t())return;o(G(w.PROCESSING)),o(U(!0)),o(K(null)),J();const e=L.current.trim();if(e){const t=`${ce}${v()}`;D.current=t,o(q(null)),g.current.setMessages(r=>[...r,ae(e,t)])}},onUserTranscript:e=>{if(!t())return;if(!e.final)return void(e.text&&(L.current=e.text,M.current||o(q(e.text))));o(q(null));const r=e.text&&e.text.trim()?e.text:"",n=D.current;D.current=null,n?r&&g.current.setMessages(e=>e.map(e=>e.id===n?{...e,message:r}:e)):r&&g.current.setMessages(e=>[...e,ae(r)])},onBotStartedSpeaking:()=>{t()&&(pe(),o(G(w.SPEAKING)),te(I.FINALIZE))},onBotOutput:e=>{t()&&ge(e)},onBotStoppedSpeaking:()=>{t()&&(le(),o(G(w.IDLE)),te(I.FINALIZE))},onServerMessage:e=>{t()&&me(e)}}},[o,u,oe,de,te,J,ge,pe,le,me]),Ie=s(async()=>{if(R.current||F.current)return;if(!e)return;if(F.current=!0,y(f())&&X.current<1){X.current+=1;const e=O.current;try{await Promise.resolve(C()?.())}catch{}if(e!==O.current)return}const t=++x.current,r=O.current;o(V(b.CONNECTING));const n=()=>r!==O.current||t!==x.current;try{const[{PipecatClient:r,LogLevel:s},a]=await Promise.all([import("./index.module-S2alA3-r.js"),se(e)]);if(n())return;const i=new r({transport:c(),enableMic:!0,enableCam:!1,callbacks:Se(t)});if(i.setLogLevel(s.WARN),n())return void i.disconnect();R.current=i,oe(),P.current=setTimeout(()=>{p.warn({message:"Voice RTVI connect timed out (no bot-ready)",api_name:"voice-rtvi"}),k("Voice connection timed out. Please try again."),Ee.current()},15e3),await i.connect({webrtcRequestParams:{endpoint:d.WEBRTC_OFFER(e),headers:new Headers(ne())},iceConfig:{iceServers:a}})}catch(e){p.error({message:"Voice RTVI connect failed",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)}),t===x.current&&(k("Could not connect voice. Please try again."),de(!1))}},[e,o,c,Se,oe,de]),ve=s((e=!0)=>{O.current+=1,x.current+=1,X.current=0;const t=R.current;if(t)try{t.disconnect()}catch(e){p.warn({message:"Voice RTVI disconnect threw",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}de(e)},[de]),Te=s(e=>{R.current?.enableMic(e)},[]),ye=s(e=>{const t=R.current;if(t){try{t.sendText(e,{run_immediately:!0,audio_response:!0})}catch(e){return void p.error({message:"Voice RTVI send-text failed",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}m.current&&le(),g.current.setMessages(t=>[...t,ae(e)]),o(U(!0)),o(K(null)),J(),o(G(w.PROCESSING))}},[o,J,le]);n(()=>{Ee.current=ve},[ve]);const fe=r(e),Ce=r(!1);n(()=>{const t=fe.current;if(fe.current=e,t===e)return;const r=F.current;r&&(Ce.current=!0),r&&ve(!1),Ce.current&&null!=e&&(Ce.current=!1,Ie())},[e,ve,Ie]);const ke=r(l);return n(()=>{const e=ke.current;ke.current=l,e&&!l&&F.current&&ve(!1)},[l,ve]),n(()=>()=>{Ee.current()},[]),{connect:Ie,disconnect:ve,sendText:ye,setMicEnabled:Te}}(e,t,c);n(()=>{L(!l&&!g)},[l,g,L]);const R=ie(!1,!1,l);n(()=>(t.setVoiceSendText(D),()=>t.setVoiceSendText(null)),[t,D]);const A=r(!1);return{displayState:R,pipelineStatus:m,isAgentWorking:h,enterVoiceMode:s(async()=>{if(A.current)return k("Microphone permission changed — please reload the page to apply the new setting."),!1;const e=await u.requestMicPermission();if(!e.granted){const t=e.error;return p.error({message:"Mic permission denied",api_name:"voice-mic",error_message:t instanceof Error?t.message:t?String(t):void 0}),A.current=!0,k("Microphone access is required for voice mode. Please allow it in your browser's site settings and reload the page."),!1}return N(),!0},[u,N]),exitVoiceMode:s(()=>{M()},[M]),toggleMute:s(()=>{o(F(!l))},[o,l]),sendText:D,muted:l,serverMuted:g}},le=()=>{const e=t(oe);if(!e)throw new Error("useVoice must be used within VoiceProvider");return e},de=e({strategyActive:!1,voiceSlot:null,onEnterVoice:null,onFloatingOrbClick:null,voiceInputHidden:!1,headerCenterSlot:null,floatOrbPositionRef:null}),pe=()=>t(de);function ge(){const e=re(),[t,r]=c([]),[a,o]=c([]),u=i(e=>e.voice.listeningEnabled),l=s(async()=>{const t=await e.devices.enumerate();r(t.microphones),o(t.speakers)},[e]);return n(()=>(l(),e.devices.onChange(l)),[e,l]),n(()=>{u&&l()},[u,l]),{microphones:t,speakers:a,refresh:l}}const me=u();me.startListening({actionCreator:X,effect:async e=>{const t=h();t&&await t.setItem(R.SELECTED_MIC_ID,e.payload)}}),me.startListening({actionCreator:z,effect:async e=>{const t=h();t&&await t.setItem(R.SELECTED_SPEAKER_ID,e.payload)}}),me.startListening({matcher:l(N,M),effect:async(e,t)=>{const r=h();r&&await Promise.all([r.removeItem(R.SELECTED_MIC_ID),r.removeItem(R.SELECTED_SPEAKER_ID)]),t.dispatch(J())}});class Ee{async enumerate(){const e={microphones:[],speakers:[]};if(!navigator.mediaDevices?.enumerateDevices)return e;try{const e=await navigator.mediaDevices.enumerateDevices();let t=0,r=0;return{microphones:e.filter(e=>"audioinput"===e.kind).map(e=>({deviceId:e.deviceId,label:e.label||"Microphone "+ ++t})),speakers:e.filter(e=>"audiooutput"===e.kind).map(e=>({deviceId:e.deviceId,label:e.label||"Speaker "+ ++r}))}}catch{return e}}onChange(e){const t=navigator.mediaDevices;return t?.addEventListener?(t.addEventListener("devicechange",e),()=>t.removeEventListener("devicechange",e)):()=>{}}}const Se=e=>{const t=document.createElement("audio");return t.autoplay=!0,t.style.display="none",t.srcObject=new MediaStream([e]),document.body.appendChild(t),()=>{try{t.pause(),t.srcObject=null,t.remove()}catch{}}},Ie={attachBotAudioTrack:Se,devices:new Ee,requestMicPermission:async function(){try{return(await navigator.mediaDevices.getUserMedia({audio:!0})).getTracks().forEach(e=>e.stop()),{granted:!0}}catch(e){return{granted:!1,error:e}}}};export{te as AudioPlatformContext,Ee as BrowserAudioDevices,A as ORB_STATE_CONFIG,b as VOICE_CONNECTION_STATUSES,L as VOICE_CONNECT_TIMEOUT_MS,D as VOICE_DISPLAY_STATES,_ as VOICE_PIPELINE_MODES,w as VOICE_PIPELINE_STATUSES,R as VOICE_STORAGE_KEYS,oe as VoiceContext,de as VoiceUIContext,Se as attachBrowserBotAudioTrack,Ie as browserAudioPlatform,Y as clearBotTurn,x as hydrateVoice,Z as openBotTurn,Q as resetIndicatorState,V as setConnectionStatus,$ as setError,q as setInterimTranscript,U as setIsAgentWorking,B as setListeningEnabled,F as setMuted,G as setPipelineStatus,j as setResponseStartTime,X as setSelectedMicId,z as setSelectedSpeakerId,W as setServerMuted,K as setStatusText,H as upsertBotSegment,re as useAudioPlatform,ge as useMediaDevices,le as useVoice,ie as useVoiceDisplayState,ue as useVoiceProviderValue,pe as useVoiceUI,me as voiceListenerMiddleware,ee as voiceReducer};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1interface/voice-core",
|
|
3
|
-
"version": "0.2.0-pipecat.
|
|
3
|
+
"version": "0.2.0-pipecat.1",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@pipecat-ai/client-js": "^1.12.0",
|
|
30
|
-
"@1interface/shared-core": "^0.2.0-pipecat.
|
|
30
|
+
"@1interface/shared-core": "^0.2.0-pipecat.1"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"@reduxjs/toolkit": "^2.0.0",
|