@1interface/voice-core 0.8.2 → 0.8.3
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/{browserAudioPlatform-BlQ_Ssdk.js → browserAudioPlatform-BanqRaQm.js} +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{sessionHelpers-BIwU9Fpt.js → sessionHelpers-CK66mJIw.js} +1 -1
- package/dist/{useVoiceSessionCore-Cwkj3eVq.js → useVoiceSessionCore-C9xo14Pi.js} +1 -1
- package/dist/webrtc.js +1 -1
- package/dist/webrtc.mjs +1 -1
- package/dist/ws.mjs +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getBaseUrl as e,logger as t}from"@1interface/shared-core";import{X as n,Y as a}from"./sessionHelpers-
|
|
1
|
+
import{getBaseUrl as e,logger as t}from"@1interface/shared-core";import{X as n,Y as a}from"./sessionHelpers-CK66mJIw.js";const o=e=>{const t=document.createElement("audio");return t.autoplay=!0,t.style.display="none",t.srcObject=new MediaStream([e]),document.body.appendChild(t),()=>{try{t.pause(),t.srcObject=null,t.remove()}catch{}}};let r=null;function s(){const t=new Audio(`${e()}${n}`);return t.loop=!0,t.volume=a,t.preload="auto",t.load(),t}function i(e){const t=e.volume;e.volume=0,e.muted=!0;const n=()=>{e.pause(),e.currentTime=0,e.volume=t,e.muted=!1},a=e.play();return a&&"function"==typeof a.then?a.then(n,n):(n(),null)}function c(){const e=r;return r=null,e}const u={attachBotAudioTrack:o,requestMicPermission:async function(){try{const e=await(navigator.permissions?.query({name:"microphone"}).catch(()=>null));return t.debug({message:"Voice mic permission probe",api_name:"voice-mic",permission_state:e?.state??"unavailable"}),"granted"===e?.state||(await navigator.mediaDevices.getUserMedia({audio:!0})).getTracks().forEach(e=>e.stop()),{granted:!0}}catch(e){return t.warn({message:"Voice mic permission denied",api_name:"voice-mic",error_message:e instanceof Error?e.message:String(e)}),{granted:!1,error:e}}},primeGestureAudio:function(){if(r)return;const e=s(),t={audio:e,settled:null},n=i(e);t.settled=n?.then(()=>{t.settled=null})??null,r=t}};export{o as a,u as b,s as c,c as t,i as u};
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{x as e,L as t,y as s,M as a}from"./sessionHelpers-
|
|
1
|
+
import{x as e,L as t,y as s,M as a}from"./sessionHelpers-CK66mJIw.js";import{N as o,O as r,e as i,i as n,v as c,P as E,t as l,V as I,A as S,Q as m,E as d,R as u,G as C,S as p,d as _,f as T,k as f,b as O,w as A,c as P,C as L,g as M,l as V,H as v,u as D,T as N,U as R,W as g}from"./sessionHelpers-CK66mJIw.js";import{createContext as y,useContext as h}from"react";import{useSelector as k}from"react-redux";import{createListenerMiddleware as x,isAnyOf as b}from"@reduxjs/toolkit";import{getStorageRef as w,clearSession as B,sessionExpired as j}from"@1interface/shared-core";import{a as H,b as U}from"./browserAudioPlatform-BanqRaQm.js";const F=y({strategyActive:!1,voiceSlot:null,onEnterVoice:null,onFloatingOrbClick:null,voiceInputHidden:!1,headerCenterSlot:null,floatOrbPositionRef:null}),G=()=>h(F);function K(){return{microphones:k(e=>e.voice.microphones),speakers:k(e=>e.voice.speakers),selectedMicId:k(e=>e.voice.selectedMicId),selectedSpeakerId:k(e=>e.voice.selectedSpeakerId)}}const W=x();W.startListening({actionCreator:e,effect:async e=>{const s=w();s&&await s.setItem(t.SELECTED_MIC_ID,e.payload)}}),W.startListening({actionCreator:s,effect:async e=>{const s=w();s&&await s.setItem(t.SELECTED_SPEAKER_ID,e.payload)}}),W.startListening({matcher:b(B,j),effect:async(e,s)=>{const o=w();o&&await Promise.all([o.removeItem(t.SELECTED_MIC_ID),o.removeItem(t.SELECTED_SPEAKER_ID)]),s.dispatch(a())}});export{o as AudioPlatformContext,r as ORB_STATE_CONFIG,i as VOICE_CONNECTION_STATUSES,n as VOICE_CONNECT_ERROR_MESSAGE,c as VOICE_CONNECT_TIMEOUT_MS,E as VOICE_DISPLAY_STATES,l as VOICE_PIPELINE_MODES,I as VOICE_PIPELINE_STATUSES,S as VOICE_SAMPLE_RATE,t as VOICE_STORAGE_KEYS,m as VoiceContext,F as VoiceUIContext,H as attachBrowserBotAudioTrack,U as browserAudioPlatform,d as clearBotTurn,u as hydrateVoice,C as openBotTurn,p as resetIndicatorState,_ as setConnectionStatus,T as setInterimTranscript,f as setIsAgentWorking,O as setListeningEnabled,A as setMuted,P as setPipelineStatus,L as setResponseStartTime,e as setSelectedMicId,s as setSelectedSpeakerId,M as setServerMuted,V as setStatusText,v as upsertBotSegment,D as useAudioPlatform,K as useMediaDevices,N as useVoice,R as useVoiceDisplayState,G as useVoiceUI,W as voiceListenerMiddleware,g as voiceReducer};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createContext as e,useContext as t,useEffect as s,useRef as a,useCallback as n,useMemo as i}from"react";import{useSelector as o,useDispatch as r}from"react-redux";import{createSlice as c}from"@reduxjs/toolkit";import{showErrorToast as d,logger as l,generateUniqueId as u}from"@1interface/shared-core";const p={DISCONNECTED:"disconnected",CONNECTING:"connecting",CONNECTED:"connected"},S={IDLE:"idle",LISTENING:"listening",PROCESSING:"processing",SPEAKING:"speaking"},g={CASCADE:"cascade",REALTIME:"realtime"},E={STATUS_UPDATE:"status_update",STARTED_THINKING:"started-thinking",STOPPED_THINKING:"stopped-thinking",CLEAR_STATUS:"clear_status",WIDGET:"widget",PAYMENT:"payment",REQUEST_DOCUMENTS:"request_documents",CONVERSATION_TITLE:"conversation_title",SESSION_ENDED:"session-ended"},m={OFFLINE:"offline",CONNECTING:"connecting",READY:"ready",IDLE:"idle",LISTENING:"listening",MUTED:"muted",PROCESSING:"processing",SPEAKING:"speaking"},I=15e3,T=1,N=[/rejected/i,/unauthori[sz]ed/i,/forbidden/i,/\b40[13]\b/],y=[/\b402\b/,/\b429\b/,/rate[\s-]?limit/i],v=1e3,C=3,M=24e3,k="/sounds/thinking-sound.mp3",D=.5,b={SELECTED_MIC_ID:"voice_selectedMicId",SELECTED_SPEAKER_ID:"voice_selectedSpeakerId"},h={offline:{opacity:.15,playbackRate:.3,scale:1},connecting:{opacity:.4,playbackRate:.5,scale:1},ready:{opacity:.4,playbackRate:.4,scale:1},idle:{opacity:.6,playbackRate:.6,scale:1},listening:{opacity:.7,playbackRate:.7,scale:1},muted:{opacity:.7,playbackRate:.5,scale:1},processing:{opacity:1,playbackRate:1,scale:1},speaking:{opacity:1,playbackRate:1,scale:1.3}};function A(){return{connectionStatus:p.DISCONNECTED,pipelineStatus:S.IDLE,listeningEnabled:!1,muted:!1,serverMuted:!1,statusText:null,isAgentWorking:!1,responseStartTime:null,interimTranscript:null,botTurn:{active:!1,chatMsgId:null,segments:[]},sessionEndedSignal:0,selectedMicId:"",selectedSpeakerId:"",microphones:[],speakers:[]}}const P=e=>async t=>{const[s,a]=await Promise.all([e.getItem(b.SELECTED_MIC_ID),e.getItem(b.SELECTED_SPEAKER_ID)]);s&&t(_.actions.setSelectedMicId(s)),a&&t(_.actions.setSelectedSpeakerId(a))},_=c({name:"voice",initialState:A(),reducers:{setConnectionStatus(e,t){e.connectionStatus=t.payload},setPipelineStatus(e,t){e.pipelineStatus=t.payload},setListeningEnabled(e,t){e.listeningEnabled=t.payload},setStatusText(e,t){e.statusText=t.payload},setIsAgentWorking(e,t){e.isAgentWorking=t.payload},setResponseStartTime(e,t){e.responseStartTime=t.payload},setInterimTranscript(e,t){e.interimTranscript=t.payload},openBotTurn(e,t){e.botTurn={active:!0,chatMsgId:t.payload?.chatMsgId??null,segments:[]}},upsertBotSegment(e,t){const{id:s,text:a,spokenChars:n}=t.payload;e.botTurn.active=!0;const i=e.botTurn.segments.find(e=>e.id===s);i?(i.text=a,i.spokenChars=Math.max(i.spokenChars,n)):(e.botTurn.segments.push({id:s,text:a,spokenChars:n}),e.botTurn.segments.sort((e,t)=>e.id-t.id))},clearBotTurn(e){e.botTurn={active:!1,chatMsgId:null,segments:[]}},setMuted(e,t){e.muted=t.payload},setServerMuted(e,t){e.serverMuted=t.payload},setSelectedMicId(e,t){e.selectedMicId=t.payload},setSelectedSpeakerId(e,t){e.selectedSpeakerId=t.payload},setMicrophones(e,t){e.microphones=t.payload},setSpeakers(e,t){e.speakers=t.payload},signalSessionEnded(e){e.sessionEndedSignal+=1},resetVoiceState:()=>A(),resetIndicatorState(e){e.statusText=null,e.isAgentWorking=!1,e.responseStartTime=null,e.interimTranscript=null,e.serverMuted=!1,e.botTurn={active:!1,chatMsgId:null,segments:[]}}}}),{setConnectionStatus:f,setPipelineStatus:O,setListeningEnabled:R,setMuted:G,setServerMuted:L,setStatusText:w,setIsAgentWorking:x,setResponseStartTime:V,setInterimTranscript:K,openBotTurn:W,upsertBotSegment:B,clearBotTurn:U,setSelectedMicId:F,setSelectedSpeakerId:q,setMicrophones:Y,setSpeakers:$,signalSessionEnded:j,resetVoiceState:H,resetIndicatorState:z}=_.actions,Q=_.reducer;function J(){const e=o(e=>e.voice.connectionStatus),t=o(e=>e.voice.pipelineStatus),s=o(e=>e.voice.listeningEnabled),a=o(e=>e.voice.muted);return e===p.CONNECTING?m.CONNECTING:e!==p.CONNECTED?m.OFFLINE:s?a?m.MUTED:t===S.PROCESSING?m.PROCESSING:t===S.SPEAKING?m.SPEAKING:t===S.LISTENING?m.LISTENING:m.IDLE:m.READY}const X=e(null),Z=()=>{const e=t(X);if(!e)throw new Error("useAudioPlatform must be used inside <AudioPlatformProvider>. <VoiceProvider> mounts one by default; if you replaced VoiceProvider, you must wire your own.");return e},ee=e(null),te=(e,{voiceBridge:t})=>{const c=r(),u=Z(),p=o(e=>e.voice.muted),S=o(e=>e.voice.serverMuted),g=o(e=>e.voice.pipelineStatus),E=o(e=>e.voice.isAgentWorking),m=o(e=>e.voice.sessionEndedSignal),{connect:I,disconnect:T,sendText:N,setMicEnabled:y,setInputDevice:v,setOutputDevice:C,primeAudio:M}=e;s(()=>{y(!p&&!S)},[p,S,y]);const k=J();s(()=>(t.setVoiceSendText(N),()=>t.setVoiceSendText(null)),[t,N]);const D=a(!1),b=n(async()=>{if(D.current)return d("Microphone permission changed — please reload the page to apply the new setting."),!1;M?.();const e=await u.requestMicPermission();if(!e.granted){const t=e.error;return l.error({message:"Mic permission denied",api_name:"voice-mic",error_message:t instanceof Error?t.message:t?String(t):void 0}),D.current=!0,d("Microphone access is required for voice mode. Please allow it in your browser's site settings and reload the page."),!1}return I(),!0},[u,I,M]),h=n(()=>{T()},[T]),A=n(()=>{c(G(!p))},[c,p]);return i(()=>({displayState:k,pipelineStatus:g,isAgentWorking:E,enterVoiceMode:b,exitVoiceMode:h,toggleMute:A,sendText:N,muted:p,serverMuted:S,sessionEndedSignal:m,setInputDevice:v,setOutputDevice:C}),[k,g,E,b,h,A,N,p,S,m,v,C])},se=()=>{const e=t(ee);if(!e)throw new Error("useVoice must be used within VoiceProvider");return e},ae="voice-user-",ne="voice-widget-",ie="voice-agent-",oe="voice-error-",re="Something went wrong. Please try again.",ce="Could not connect voice. Please try again later.",de=(e,t=`${ae}${u()}`,s,a)=>({id:t,sender:"user",message:e,timestamp:(new Date).toISOString(),...s?.length?{documents:s}:{},...a?.length?{message_type:a}:{}}),le=(e,t)=>{const s=`${e}${u()}`;return{id:s,msg_id:s,sender:"agent",message:"",timestamp:(new Date).toISOString(),...t}};export{M as A,E as B,V as C,le as D,U as E,ie as F,W as G,B as H,re as I,oe as J,ne as K,b as L,H as M,X as N,h as O,m as P,ee as Q,P as R,z as S,se as T,J as U,S as V,Q as W,k as X,D as Y,$ as a,R as b,O as c,f as d,p as e,K as f,L as g,
|
|
1
|
+
import{createContext as e,useContext as t,useEffect as s,useRef as a,useCallback as n,useMemo as i}from"react";import{useSelector as o,useDispatch as r}from"react-redux";import{createSlice as c}from"@reduxjs/toolkit";import{showErrorToast as d,logger as l,generateUniqueId as u}from"@1interface/shared-core";const p={DISCONNECTED:"disconnected",CONNECTING:"connecting",CONNECTED:"connected"},S={IDLE:"idle",LISTENING:"listening",PROCESSING:"processing",SPEAKING:"speaking"},g={CASCADE:"cascade",REALTIME:"realtime"},E={STATUS_UPDATE:"status_update",STARTED_THINKING:"started-thinking",STOPPED_THINKING:"stopped-thinking",CLEAR_STATUS:"clear_status",WIDGET:"widget",PAYMENT:"payment",REQUEST_DOCUMENTS:"request_documents",CONVERSATION_TITLE:"conversation_title",SESSION_ENDED:"session-ended"},m={OFFLINE:"offline",CONNECTING:"connecting",READY:"ready",IDLE:"idle",LISTENING:"listening",MUTED:"muted",PROCESSING:"processing",SPEAKING:"speaking"},I=15e3,T=1,N=[/rejected/i,/unauthori[sz]ed/i,/forbidden/i,/\b40[13]\b/],y=[/\b402\b/,/\b429\b/,/rate[\s-]?limit/i],v=1e3,C=3,M=24e3,k="/sounds/thinking-sound.mp3",D=.5,b={SELECTED_MIC_ID:"voice_selectedMicId",SELECTED_SPEAKER_ID:"voice_selectedSpeakerId"},h={offline:{opacity:.15,playbackRate:.3,scale:1},connecting:{opacity:.4,playbackRate:.5,scale:1},ready:{opacity:.4,playbackRate:.4,scale:1},idle:{opacity:.6,playbackRate:.6,scale:1},listening:{opacity:.7,playbackRate:.7,scale:1},muted:{opacity:.7,playbackRate:.5,scale:1},processing:{opacity:1,playbackRate:1,scale:1},speaking:{opacity:1,playbackRate:1,scale:1.3}};function A(){return{connectionStatus:p.DISCONNECTED,pipelineStatus:S.IDLE,listeningEnabled:!1,muted:!1,serverMuted:!1,statusText:null,isAgentWorking:!1,responseStartTime:null,interimTranscript:null,botTurn:{active:!1,chatMsgId:null,segments:[]},sessionEndedSignal:0,selectedMicId:"",selectedSpeakerId:"",microphones:[],speakers:[]}}const P=e=>async t=>{const[s,a]=await Promise.all([e.getItem(b.SELECTED_MIC_ID),e.getItem(b.SELECTED_SPEAKER_ID)]);s&&t(_.actions.setSelectedMicId(s)),a&&t(_.actions.setSelectedSpeakerId(a))},_=c({name:"voice",initialState:A(),reducers:{setConnectionStatus(e,t){e.connectionStatus=t.payload},setPipelineStatus(e,t){e.pipelineStatus=t.payload},setListeningEnabled(e,t){e.listeningEnabled=t.payload},setStatusText(e,t){e.statusText=t.payload},setIsAgentWorking(e,t){e.isAgentWorking=t.payload},setResponseStartTime(e,t){e.responseStartTime=t.payload},setInterimTranscript(e,t){e.interimTranscript=t.payload},openBotTurn(e,t){e.botTurn={active:!0,chatMsgId:t.payload?.chatMsgId??null,segments:[]}},upsertBotSegment(e,t){const{id:s,text:a,spokenChars:n}=t.payload;e.botTurn.active=!0;const i=e.botTurn.segments.find(e=>e.id===s);i?(i.text=a,i.spokenChars=Math.max(i.spokenChars,n)):(e.botTurn.segments.push({id:s,text:a,spokenChars:n}),e.botTurn.segments.sort((e,t)=>e.id-t.id))},clearBotTurn(e){e.botTurn={active:!1,chatMsgId:null,segments:[]}},setMuted(e,t){e.muted=t.payload},setServerMuted(e,t){e.serverMuted=t.payload},setSelectedMicId(e,t){e.selectedMicId=t.payload},setSelectedSpeakerId(e,t){e.selectedSpeakerId=t.payload},setMicrophones(e,t){e.microphones=t.payload},setSpeakers(e,t){e.speakers=t.payload},signalSessionEnded(e){e.sessionEndedSignal+=1},resetVoiceState:()=>A(),resetIndicatorState(e){e.statusText=null,e.isAgentWorking=!1,e.responseStartTime=null,e.interimTranscript=null,e.serverMuted=!1,e.botTurn={active:!1,chatMsgId:null,segments:[]}}}}),{setConnectionStatus:f,setPipelineStatus:O,setListeningEnabled:R,setMuted:G,setServerMuted:L,setStatusText:w,setIsAgentWorking:x,setResponseStartTime:V,setInterimTranscript:K,openBotTurn:W,upsertBotSegment:B,clearBotTurn:U,setSelectedMicId:F,setSelectedSpeakerId:q,setMicrophones:Y,setSpeakers:$,signalSessionEnded:j,resetVoiceState:H,resetIndicatorState:z}=_.actions,Q=_.reducer;function J(){const e=o(e=>e.voice.connectionStatus),t=o(e=>e.voice.pipelineStatus),s=o(e=>e.voice.listeningEnabled),a=o(e=>e.voice.muted);return e===p.CONNECTING?m.CONNECTING:e!==p.CONNECTED?m.OFFLINE:s?a?m.MUTED:t===S.PROCESSING?m.PROCESSING:t===S.SPEAKING?m.SPEAKING:t===S.LISTENING?m.LISTENING:m.IDLE:m.READY}const X=e(null),Z=()=>{const e=t(X);if(!e)throw new Error("useAudioPlatform must be used inside <AudioPlatformProvider>. <VoiceProvider> mounts one by default; if you replaced VoiceProvider, you must wire your own.");return e},ee=e(null),te=(e,{voiceBridge:t})=>{const c=r(),u=Z(),p=o(e=>e.voice.muted),S=o(e=>e.voice.serverMuted),g=o(e=>e.voice.pipelineStatus),E=o(e=>e.voice.isAgentWorking),m=o(e=>e.voice.sessionEndedSignal),{connect:I,disconnect:T,sendText:N,setMicEnabled:y,setInputDevice:v,setOutputDevice:C,primeAudio:M}=e;s(()=>{y(!p&&!S)},[p,S,y]);const k=J();s(()=>(t.setVoiceSendText(N),()=>t.setVoiceSendText(null)),[t,N]);const D=a(!1),b=n(async()=>{if(D.current)return d("Microphone permission changed — please reload the page to apply the new setting."),!1;M?.();const e=await u.requestMicPermission();if(!e.granted){const t=e.error;return l.error({message:"Mic permission denied",api_name:"voice-mic",error_message:t instanceof Error?t.message:t?String(t):void 0}),D.current=!0,d("Microphone access is required for voice mode. Please allow it in your browser's site settings and reload the page."),!1}return I(),!0},[u,I,M]),h=n(()=>{T()},[T]),A=n(()=>{c(G(!p))},[c,p]);return i(()=>({displayState:k,pipelineStatus:g,isAgentWorking:E,enterVoiceMode:b,exitVoiceMode:h,toggleMute:A,sendText:N,muted:p,serverMuted:S,sessionEndedSignal:m,setInputDevice:v,setOutputDevice:C}),[k,g,E,b,h,A,N,p,S,m,v,C])},se=()=>{const e=t(ee);if(!e)throw new Error("useVoice must be used within VoiceProvider");return e},ae="voice-user-",ne="voice-widget-",ie="voice-agent-",oe="voice-error-",re="Something went wrong. Please try again.",ce="Could not connect voice. Please try again later.",de=(e,t=`${ae}${u()}`,s,a)=>({id:t,sender:"user",message:e,timestamp:(new Date).toISOString(),...s?.length?{documents:s}:{},...a?.length?{message_type:a}:{}}),le=(e,t)=>{const s=`${e}${u()}`;return{id:s,msg_id:s,sender:"agent",message:"",timestamp:(new Date).toISOString(),...t}};export{M as A,E as B,V as C,le as D,U as E,ie as F,W as G,B as H,re as I,oe as J,ne as K,b as L,H as M,X as N,h as O,m as P,ee as Q,P as R,z as S,se as T,J as U,S as V,Q as W,k as X,D as Y,$ as a,R as b,O as c,f as d,p as e,K as f,L as g,T as h,ce as i,j,x as k,w as l,de as m,ae as n,N as o,y as p,C as q,v as r,Y as s,g as t,Z as u,I as v,G as w,F as x,q as y,te as z};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useRef as e,useEffect as t,useCallback as r,useState as n}from"react";import{useDispatch as s}from"react-redux";import{C as a,
|
|
1
|
+
import{useRef as e,useEffect as t,useCallback as r,useState as n}from"react";import{useDispatch as s}from"react-redux";import{C as a,l as o,k as c,D as u,E as i,F as m,G as d,H as p,B as l,I as g,J as T,K as S}from"./sessionHelpers-CK66mJIw.js";import{clearLastResponseTime as k,setLastResponseTime as _,INDICATOR_TIMER_ACTIONS as f,logger as E,generateUniqueId as I,handleSharedEvent as x}from"@1interface/shared-core";function N(e,t,r,n){const s=t.segment_id??0,a="completed"===t.spoken_status;if(a&&!e.has(s)){const t=[...e.entries()].find(([,e])=>e.text===r&&!e.completed);if(t)return t[1].completed=!0,{id:t[0],text:r,spokenChars:r.length}}const o=e.get(s);return e.set(s,{text:r,completed:a||!0===o?.completed}),{id:s,text:r,spokenChars:n&&t.spoken_progress?(t.spoken_progress.accumulated_text??"").length:r.length}}function b(e,t){const r=(e.get(0)?.text??"")+t;return e.set(0,{text:r,completed:!1}),{id:0,text:r,spokenChars:r.length}}function h(N,b,h){const v=s(),D=e(b),M=e(h);t(()=>{D.current=b,M.current=h});const w=e(null),A=e(new Map),C=e(""),R=e(null),y=r(()=>{if(null!=R.current)return;const e=Date.now();R.current=e,v(a(e)),v(k())},[v]),P=r(()=>{const e=R.current;R.current=null,v(a(null)),null!=e&&v(_(Date.now()-e))},[v]),B=r(()=>{R.current=null,v(a(null))},[v]),L=r((e=f.KEEP)=>{v(o(null)),v(c(!1)),e===f.FINALIZE?P():e===f.CANCEL&&B()},[v,P,B]),[O]=n(()=>({setStatusText:e=>v(o(e||null)),setIsAgentWorking:e=>v(c(e)),appendStreamText:()=>{},pushWidget:e=>{const t=u(S,{widget:[e],message_type:["widget"]});D.current.setMessages(e=>[...e,t])},onPayment:e=>D.current.onPaymentInit?.(e),onRequestDocuments:e=>D.current.onRequestDocuments?.(e),onConversationTitle:e=>D.current.onConversationTitle?.(e),onError:e=>{E.error({message:"Voice server error",api_name:M.current.apiName,error_message:e});const t=u(T,{message:g,message_type:["error"]});D.current.setMessages(e=>[...e,t])},onStatusActive:()=>{y()}})),U=r(()=>[...A.current.entries()].sort((e,t)=>e[0]-t[0]).map(([,e])=>e.text).join(" ").trim(),[]),q=r(()=>{w.current=null,A.current=new Map,C.current="",v(i())},[v]),G=r(()=>{const e=w.current;if(e){const t=U();t&&D.current.updateStreamingMessage(e,t),D.current.commitStreamingMessages()}q()},[U,q]),H=r(()=>{D.current.discardStreamingMessages(),q()},[q]),K=r(()=>{if(w.current)return;const e=`${m}${I()}`;w.current=e,A.current=new Map,C.current="",v(d({chatMsgId:e}))},[v]),V=r(e=>{v(p(e));const t=U();t&&w.current&&t!==C.current&&(C.current=t,D.current.updateStreamingMessage(w.current,t))},[v,U]),F=r(e=>{const t=e,r=t?.type,n=t?.data;switch(E.debug({message:"Voice server-message received",api_name:M.current.apiName,conversation_id:N??void 0,event_type:r??"unknown"}),r){case l.STATUS_UPDATE:x({kind:"status_update",statusText:n?.text??""},O);break;case l.STARTED_THINKING:v(c(!0)),y();break;case l.STOPPED_THINKING:L(f.FINALIZE);break;case l.CLEAR_STATUS:M.current.onClearStatus?.();break;case l.WIDGET:n&&x({kind:"widget",widget:n},O);break;case l.PAYMENT:n&&x({kind:"payment",payment:n},O);break;case l.REQUEST_DOCUMENTS:n&&x({kind:"request_documents",requestDocuments:n},O);break;case l.CONVERSATION_TITLE:{const e="string"==typeof n?.text?n.text.trim():"";e&&x({kind:"conversation_title",title:e},O);break}case l.SESSION_ENDED:{const e="string"==typeof n?.reason?n.reason:"ended";M.current.onSessionEnded?.(e);break}default:E.debug({message:"Voice unhandled server-message",api_name:M.current.apiName,event_type:r??"unknown"})}},[O,v,y,L,N]);return{bridgeRef:D,botTurnMsgIdRef:w,liveSegmentsRef:A,startResponseTimer:y,finalizeResponseTimer:P,cancelResponseTimer:B,clearIndicator:L,sharedDeps:O,buildTurnText:U,clearTurnState:q,commitBotTurn:G,discardBotTurn:H,openBotTurnIfNeeded:K,ingestBotSegment:V,routeServerMessage:F}}export{N as a,b as f,h as u};
|
package/dist/webrtc.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./sessionHelpers-ALyIwLPk.cjs"),r=require("react"),t=require("react-redux"),n=require("./useVoiceSessionCore-Bir8eqjh.cjs"),i=require("@1interface/shared-core");function s(){const e={};for(const[r,t]of Object.entries(i.getCustomHeaders()))t&&(e[r]=t);const r=i.readAccessToken();r&&(e[i.AUTH_HEADERS.ACCESS_TOKEN]=r);const t=i.readDeviceToken();return t&&(e[i.DEVICE_TOKEN_HEADER]=t),e}async function o(e){try{const r=await fetch(i.API_ENDPOINTS.WEBRTC_ICE(e),{headers:s()});return r.ok?(await r.json()).iceServers??[]:(i.logger.warn({message:"Voice ICE fetch failed — continuing without TURN",api_name:"voice-webrtc-ice",status:r.status}),[])}catch(e){return i.logger.warn({message:"Voice ICE fetch error — continuing without TURN",api_name:"voice-webrtc-ice",error_message:e instanceof Error?e.message:String(e)}),[]}}exports.useVoiceProviderValue=({conversationId:c,voiceBridge:a,transportFactory:u,assistantStartsConversation:d})=>{const l=function(c,a,u,d){const l=t.useDispatch(),g=t.useStore(),m=e.useAudioPlatform(),v=t.useSelector(e=>e.auth.accessToken),_=r.useRef(null),{bridgeRef:p,botTurnMsgIdRef:E,liveSegmentsRef:I,startResponseTimer:T,clearIndicator:S,sharedDeps:f,commitBotTurn:R,discardBotTurn:V,openBotTurnIfNeeded:C,ingestBotSegment:b,routeServerMessage:N}=n.useVoiceSessionCore(c,a,{apiName:"voice-rtvi",onSessionEnded:e=>{_.current={reason:e}}}),h=r.useRef(!1),O=r.useRef(!1),A=r.useRef(null),k=r.useRef(""),M=r.useRef(null),P=r.useRef(null),w=r.useRef(null),U=r.useRef(null),y=r.useRef(!0),L=r.useRef(null),x=r.useRef(!1),D=r.useRef(""),B=r.useRef(0),H=r.useRef(0),j=r.useRef(!1),q=r.useRef(0),F=r.useRef(null),G=r.useRef(0),W=r.useRef(0),X=r.useRef(null),K=r.useCallback(()=>{U.current&&(clearTimeout(U.current),U.current=null)},[]),Y=r.useCallback(r=>{K(),L.current&&(clearTimeout(L.current),L.current=null),x.current=!1,D.current="",l(e.setMicrophones([])),l(e.setSpeakers([])),w.current?.(),w.current=null,M.current=null,j.current=!1,l(e.setListeningEnabled(!1)),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.IDLE)),l(e.setConnectionStatus(e.VOICE_CONNECTION_STATUSES.DISCONNECTED)),l(e.setInterimTranscript(null)),l(e.setServerMuted(!1)),A.current=null,k.current="",h.current=!1,O.current=!1,r?R():V(),S(i.INDICATOR_TIMER_ACTIONS.CANCEL)},[l,S,K,R,V]),Z=r.useCallback(e=>{const r=e.spoken_progress?(e.spoken_progress?.accumulated_text??"")+(e.spoken_progress?.remaining_text??""):e.text??"";if(!r)return;C();const t=h.current?n.foldRealtimeBotOutput(I.current,r):n.foldCascadeBotOutput(I.current,e,r,O.current);b(t)},[C,b,I]),$=r.useRef(()=>{}),z=r.useRef(async()=>{}),J=r.useCallback(r=>{const t=()=>r===B.current;return{onConnected:()=>{if(!t())return;i.logger.info({message:"Voice RTVI connected",api_name:"voice-rtvi",conversation_id:c??void 0}),l(e.setConnectionStatus(e.VOICE_CONNECTION_STATUSES.CONNECTED)),l(e.setListeningEnabled(!0)),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.IDLE)),M.current&&M.current.isMicEnabled!==y.current&&(i.logger.debug({message:"Voice re-asserting mic state on reconnect",api_name:"voice-rtvi",conversation_id:c??void 0,enabled:y.current}),M.current.enableMic(y.current));const{selectedMicId:r,selectedSpeakerId:n}=g.getState().voice;r&&M.current?.updateMic(r),n&&M.current?.updateSpeaker(n);const s=e=>r=>i.logger.warn({message:"Voice device enumeration failed",api_name:"voice-mic",conversation_id:c??void 0,device_kind:e,error_message:r instanceof Error?r.message:String(r)});M.current?.getAllMics().then(r=>{t()&&l(e.setMicrophones(r.map(e=>({deviceId:e.deviceId,label:e.label}))))}).catch(s("mics")),M.current?.getAllSpeakers().then(r=>{t()&&l(e.setSpeakers(r.map(e=>({deviceId:e.deviceId,label:e.label}))))}).catch(s("speakers")),x.current=!0},onBotReady:r=>{if(!t())return;const n=r?.about;h.current=n?.pipeline_mode===e.VOICE_PIPELINE_MODES.REALTIME,O.current=!0===n?.tts_word_timestamps,G.current=0,W.current=0,q.current=0,i.logger.info({message:"Voice RTVI bot-ready",api_name:"voice-rtvi",conversation_id:c??void 0,pipeline_mode:n?.pipeline_mode??"cascade",tts_word_timestamps:O.current,assistant_starts:d}),K(),d&&(l(e.setIsAgentWorking(!0)),T())},onDisconnected:()=>{if(!t())return;i.logger.info({message:"Voice RTVI disconnected",api_name:"voice-rtvi",conversation_id:c??void 0,drop_outcome:X.current??"transient",reconnect_attempts:G.current}),Y(!0);const r=H.current,n=X.current;X.current=null;const s=_.current;return _.current=null,s?(i.logger.info({message:"Voice session ended by server — no reconnect",api_name:"voice-rtvi",conversation_id:c??void 0,reason:s.reason}),"idle_timeout"===s.reason&&i.showToast({title:"Voice ended",description:"It got quiet for a while, so we wrapped up. Start again anytime."}),void l(e.signalSessionEnded())):"terminal"===n?(i.logger.warn({message:"Voice RTVI terminal error — no retry",api_name:"voice-rtvi"}),void l(e.signalSessionEnded())):"auth"===n?W.current>=e.VOICE_MAX_AUTH_REFRESHES||null==c?(i.logger.warn({message:"Voice RTVI auth refresh budget exhausted — giving up",api_name:"voice-rtvi",conversation_id:c??void 0}),void l(e.signalSessionEnded())):(W.current+=1,i.logger.info({message:"Voice RTVI auth drop — refreshing token then reconnecting",api_name:"voice-rtvi",conversation_id:c??void 0,auth_refresh_count:W.current}),void(async()=>{let t=!1;try{t=!!await(i.getTokenRefreshHandler()?.())}catch{}r===H.current&&(t?z.current():(i.logger.warn({message:"Voice RTVI token refresh failed — no reconnect",api_name:"voice-rtvi",conversation_id:c??void 0}),l(e.signalSessionEnded())))})()):G.current>=e.VOICE_MAX_AUTO_RECONNECTS||null==c?(G.current>=e.VOICE_MAX_AUTO_RECONNECTS&&(i.logger.warn({message:"Voice RTVI auto-reconnect cap reached — giving up",api_name:"voice-rtvi",conversation_id:c??void 0,reconnect_attempts:G.current}),i.showErrorToast("Voice connection lost. Please try again.")),void l(e.signalSessionEnded())):(G.current+=1,i.logger.info({message:"Voice RTVI scheduling auto-reconnect",api_name:"voice-rtvi",conversation_id:c??void 0,reconnect_attempts:G.current,delay_ms:e.VOICE_RECONNECT_DELAY_MS}),F.current&&clearTimeout(F.current),void(F.current=setTimeout(()=>{F.current=null,z.current()},e.VOICE_RECONNECT_DELAY_MS)))},onError:r=>{if(!t())return;const n=r?.data,s=n?.error??n?.message??"Voice error";i.handleSharedEvent({kind:"error",error:s},f),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.IDLE)),S(i.INDICATOR_TIMER_ACTIONS.CANCEL),n?.fatal&&(e.VOICE_AUTH_ERROR_PATTERNS.some(e=>e.test(s))?X.current="auth":e.VOICE_TERMINAL_ERROR_PATTERNS.some(e=>e.test(s))&&(X.current="terminal"),i.logger.warn({message:"Voice RTVI fatal error — closing transport",api_name:"voice-rtvi",conversation_id:c??void 0,drop_outcome:X.current??"generic"}),M.current?.disconnect().catch(()=>{}))},onTrackStarted:(e,r)=>{t()&&(r?.local||"audio"!==e.kind||(i.logger.debug({message:"Voice RTVI bot audio track started",api_name:"voice-rtvi",conversation_id:c??void 0}),w.current?.(),w.current=m.attachBotAudioTrack(e)))},onAvailableMicsUpdated:r=>{t()&&(l(e.setMicrophones(r.map(e=>({deviceId:e.deviceId,label:e.label})))),x.current&&(L.current&&clearTimeout(L.current),L.current=setTimeout(()=>{if(L.current=null,!M.current)return;const{selectedMicId:e,microphones:r}=g.getState().voice;i.logger.info({message:"Voice audio devices changed",api_name:"voice-mic",microphones:r.map(e=>e.label).join(", ")}),i.logger.debug({message:"Voice re-acquiring mic (device change)",api_name:"voice-rtvi",enabled:y.current,device:e||"default"}),M.current.updateMic(e)},300)))},onAvailableSpeakersUpdated:r=>{if(!t())return;if(l(e.setSpeakers(r.map(e=>({deviceId:e.deviceId,label:e.label})))),!x.current)return;const n=r.map(e=>e.label).join(", ");n!==D.current&&(D.current=n,i.logger.info({message:"Voice audio output devices changed",api_name:"voice-mic",speakers:n}))},onMicUpdated:e=>{if(!t())return;const r=M.current;r&&r.isMicEnabled!==y.current&&(i.logger.debug({message:"Voice re-asserting mic state after track swap",api_name:"voice-rtvi",conversation_id:c??void 0,enabled:y.current,device:e?.label}),r.enableMic(y.current))},onUserMuteStarted:()=>{t()&&i.logger.debug({message:"Voice RTVI user-mute-started (ignored)",api_name:"voice-rtvi",conversation_id:c??void 0})},onUserMuteStopped:()=>{t()&&(i.logger.debug({message:"Voice RTVI user-mute-stopped",api_name:"voice-rtvi",conversation_id:c??void 0,honored:d}),d&&l(e.setServerMuted(!1)))},onUserStartedSpeaking:()=>{t()&&(i.logger.debug({message:"Voice RTVI user started speaking",api_name:"voice-rtvi",conversation_id:c??void 0}),A.current=null,k.current="",l(e.setInterimTranscript(null)),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.LISTENING)))},onUserStoppedSpeaking:()=>{if(!t())return;i.logger.debug({message:"Voice RTVI user stopped speaking",api_name:"voice-rtvi",conversation_id:c??void 0,has_interim:!!k.current.trim()}),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.PROCESSING)),l(e.setIsAgentWorking(!0)),l(e.setStatusText(null)),T();const r=k.current.trim();if(r){const t=`${e.VOICE_USER_ID_PREFIX}${i.generateUniqueId()}`;A.current=t,l(e.setInterimTranscript(null)),p.current.setMessages(n=>[...n,e.makeUserMessage(r,t)])}},onUserTranscript:r=>{if(!t())return;if(!r.final)return void(r.text&&(k.current=r.text,h.current||l(e.setInterimTranscript(r.text))));l(e.setInterimTranscript(null));const n=r.text&&r.text.trim()?r.text:"";i.logger.debug({message:"Voice RTVI user transcript (final)",api_name:"voice-rtvi",conversation_id:c??void 0,has_text:!!n});const s=A.current;A.current=null,s?n&&p.current.setMessages(e=>e.map(e=>e.id===s?{...e,message:n}:e)):n&&p.current.setMessages(r=>[...r,e.makeUserMessage(n)])},onBotStartedSpeaking:()=>{t()&&(i.logger.debug({message:"Voice RTVI bot started speaking",api_name:"voice-rtvi",conversation_id:c??void 0}),C(),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.SPEAKING)),S(i.INDICATOR_TIMER_ACTIONS.FINALIZE))},onBotOutput:e=>{t()&&Z(e)},onBotStoppedSpeaking:()=>{t()&&(i.logger.debug({message:"Voice RTVI bot stopped speaking",api_name:"voice-rtvi",conversation_id:c??void 0}),R(),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.IDLE)),S(i.INDICATOR_TIMER_ACTIONS.FINALIZE),d&&g.getState().voice.serverMuted&&(i.logger.warn({message:"Voice greeting mute-hold released on bot-turn end (server unmute missing)",api_name:"voice-rtvi",conversation_id:c??void 0}),l(e.setServerMuted(!1))))},onServerMessage:e=>{t()&&N(e)}}},[l,g,m,K,Y,S,T,Z,C,R,N,f,p,d,c]),Q=r.useCallback(async()=>{if(M.current||j.current)return;if(!c)return;if(j.current=!0,i.logger.info({message:"Voice RTVI connecting",api_name:"voice-rtvi",conversation_id:c,assistant_starts:d}),d&&(y.current=!1,l(e.setServerMuted(!0)),i.logger.debug({message:"Voice greeting mute-hold armed",api_name:"voice-rtvi",conversation_id:c})),i.isTokenAboutToExpire(i.readTokenExpiresAt())&&q.current<e.VOICE_MAX_AUTH_REFRESHES){q.current+=1,i.logger.debug({message:"Voice pre-connect token refresh (freshness gate)",api_name:"voice-rtvi",conversation_id:c,freshness_refresh_count:q.current});const e=H.current;try{await Promise.resolve(i.getTokenRefreshHandler()?.())}catch{}if(e!==H.current)return}const r=++B.current,t=H.current;_.current=null,X.current=null,l(e.setConnectionStatus(e.VOICE_CONNECTION_STATUSES.CONNECTING));const n=()=>t!==H.current||r!==B.current;try{const[{PipecatClient:t,LogLevel:a,RTVIMessage:d},g,m]=await Promise.all([import("@pipecat-ai/client-js"),o(c),u()]);if(n())return;P.current=d;const v=new t({transport:m,enableMic:!0,enableCam:!1,callbacks:J(r)});if(v.setLogLevel(a.WARN),n())return void v.disconnect().catch(()=>{});M.current=v,i.logger.debug({message:"Voice RTVI client constructed",api_name:"voice-rtvi",conversation_id:c,enable_mic:y.current}),K(),U.current=setTimeout(()=>{i.logger.warn({message:"Voice RTVI connect timed out (no bot-ready)",api_name:"voice-rtvi"}),i.showErrorToast("Voice connection timed out. Please try again."),l(e.signalSessionEnded()),$.current()},e.VOICE_CONNECT_TIMEOUT_MS),await v.connect({webrtcRequestParams:{endpoint:i.API_ENDPOINTS.WEBRTC_OFFER(c),headers:new Headers(s())},iceConfig:{iceServers:g}})}catch(t){const n=(e=>{const r="object"==typeof e&&null!==e&&"status"in e?e.status:void 0;return"number"==typeof r?r:void 0})(t);i.logger.error({message:"Voice RTVI connect failed",api_name:"voice-rtvi",status:n,error_message:t instanceof Error?t.message:String(t)});const s=(e=>void 0!==e&&e>=400&&e<500)(n),o=s?await(async(r,t)=>{const n=429===t?"Voice session limit reached. Please try again later.":e.VOICE_CONNECT_ERROR_MESSAGE,i="object"==typeof r&&null!==r&&"cause"in r?r.cause:void 0;if(!(i instanceof Response))return n;try{const e=await i.json(),r=e?.data?.error_message??e?.data?.message;return"string"==typeof r?r:n}catch{return n}})(t,n):e.VOICE_CONNECT_ERROR_MESSAGE;r===B.current&&(i.showErrorToast(o),s&&(F.current&&(clearTimeout(F.current),F.current=null),G.current=0,l(e.signalSessionEnded())),Y(!1))}},[c,l,u,J,K,Y,d]),ee=r.useCallback((r=!0)=>{i.logger.info({message:"Voice RTVI disconnect (intentional teardown)",api_name:"voice-rtvi",conversation_id:c??void 0,commit_turn:r}),H.current+=1,B.current+=1,q.current=0,F.current&&(clearTimeout(F.current),F.current=null),G.current=0,W.current=0,X.current=null,y.current=!0,l(e.setMuted(!1));const t=M.current;t?.disconnect().catch(e=>{i.logger.warn({message:"Voice RTVI disconnect threw",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}),Y(r)},[Y,l,c]),re=r.useCallback(e=>{y.current=e,M.current&&(i.logger.debug({message:"Voice mic enable toggled",api_name:"voice-rtvi",enabled:e}),M.current.enableMic(e))},[]),te=r.useCallback(r=>{l(e.setSelectedMicId(r)),M.current?.updateMic(r)},[l]),ne=r.useCallback(r=>{l(e.setSelectedSpeakerId(r)),M.current?.updateSpeaker(r)},[l]),ie=r.useCallback((r,t)=>{const n=M.current;if(!n)return;const s=t?.modelContext;i.logger.info({message:"Voice RTVI send-text",api_name:"voice-rtvi",conversation_id:c??void 0,run_immediately:!0,has_model_context:Boolean(s?.length)});try{const e=P.current;s?.length&&e?n.transport.sendMessage(new e("send-text",{content:r,options:{run_immediately:!0,audio_response:!0},model_context:s})):n.sendText(r,{run_immediately:!0,audio_response:!0})}catch(e){return void i.logger.error({message:"Voice RTVI send-text failed",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}E.current&&R(),p.current.setMessages(n=>[...n,e.makeUserMessage(r,void 0,t?.documents,t?.messageType)]),l(e.setIsAgentWorking(!0)),l(e.setStatusText(null)),T(),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.PROCESSING))},[l,T,R,E,p,c]);r.useEffect(()=>{$.current=ee},[ee]),r.useEffect(()=>{z.current=Q},[Q]);const se=r.useRef(c),oe=r.useRef(!1);r.useEffect(()=>{const e=se.current;if(se.current=c,e===c)return;const r=j.current;r&&(oe.current=!0),r&&ee(!1),oe.current&&null!=c&&(oe.current=!1,Q())},[c,ee,Q]);const ce=r.useRef(v);return r.useEffect(()=>{const e=ce.current;ce.current=v,e&&!v&&j.current&&ee(!1)},[v,ee]),r.useEffect(()=>()=>{$.current()},[]),{connect:Q,disconnect:ee,sendText:ie,setMicEnabled:re,setInputDevice:te,setOutputDevice:ne}}(c,a,u,d);return e.useVoiceContextValue(l,{voiceBridge:a})};
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./sessionHelpers-ALyIwLPk.cjs"),r=require("react"),t=require("react-redux"),n=require("./useVoiceSessionCore-Bir8eqjh.cjs"),i=require("@1interface/shared-core");function s(){const e={};for(const[r,t]of Object.entries(i.getCustomHeaders()))t&&(e[r]=t);const r=i.readAccessToken();r&&(e[i.AUTH_HEADERS.ACCESS_TOKEN]=r);const t=i.readDeviceToken();return t&&(e[i.DEVICE_TOKEN_HEADER]=t),e}async function o(e){try{const r=await fetch(i.API_ENDPOINTS.WEBRTC_ICE(e),{headers:s()});return r.ok?(await r.json()).iceServers??[]:(i.logger.warn({message:"Voice ICE fetch failed — continuing without TURN",api_name:"voice-webrtc-ice",status:r.status}),[])}catch(e){return i.logger.warn({message:"Voice ICE fetch error — continuing without TURN",api_name:"voice-webrtc-ice",error_message:e instanceof Error?e.message:String(e)}),[]}}exports.useVoiceProviderValue=({conversationId:c,voiceBridge:a,transportFactory:u,assistantStartsConversation:d})=>{const l=function(c,a,u,d){const l=t.useDispatch(),g=t.useStore(),_=e.useAudioPlatform(),m=t.useSelector(e=>e.auth.accessToken),v=r.useRef(null),{bridgeRef:p,botTurnMsgIdRef:E,liveSegmentsRef:I,startResponseTimer:T,clearIndicator:S,sharedDeps:f,commitBotTurn:R,discardBotTurn:V,openBotTurnIfNeeded:C,ingestBotSegment:b,routeServerMessage:O}=n.useVoiceSessionCore(c,a,{apiName:"voice-rtvi",onSessionEnded:e=>{v.current={reason:e}}}),h=r.useRef(!1),N=r.useRef(!1),A=r.useRef(null),k=r.useRef(""),M=r.useRef(null),P=r.useRef(null),w=r.useRef(null),U=r.useRef(null),y=r.useRef(!0),L=r.useRef(null),x=r.useRef(!1),D=r.useRef(""),B=r.useRef(0),H=r.useRef(0),j=r.useRef(!1),q=r.useRef(0),F=r.useRef(null),G=r.useRef(0),W=r.useRef(0),X=r.useRef(null),K=r.useCallback(()=>{U.current&&(clearTimeout(U.current),U.current=null)},[]),Y=r.useCallback(r=>{K(),L.current&&(clearTimeout(L.current),L.current=null),x.current=!1,D.current="",l(e.setMicrophones([])),l(e.setSpeakers([])),w.current?.(),w.current=null,M.current=null,j.current=!1,l(e.setListeningEnabled(!1)),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.IDLE)),l(e.setConnectionStatus(e.VOICE_CONNECTION_STATUSES.DISCONNECTED)),l(e.setInterimTranscript(null)),l(e.setServerMuted(!1)),A.current=null,k.current="",h.current=!1,N.current=!1,r?R():V(),S(i.INDICATOR_TIMER_ACTIONS.CANCEL)},[l,S,K,R,V]),Z=r.useCallback(e=>{const r=e.spoken_progress?(e.spoken_progress?.accumulated_text??"")+(e.spoken_progress?.remaining_text??""):e.text??"";if(!r)return;C();const t=h.current?n.foldRealtimeBotOutput(I.current,r):n.foldCascadeBotOutput(I.current,e,r,N.current);b(t)},[C,b,I]),$=r.useRef(()=>{}),z=r.useRef(async()=>{}),J=r.useCallback((r,t)=>W.current>=e.VOICE_MAX_AUTH_REFRESHES||null==c?(i.logger.warn({message:"Voice RTVI auth refresh budget exhausted — giving up",api_name:"voice-rtvi",conversation_id:c??void 0,trigger:t}),!1):(W.current+=1,i.logger.info({message:"Voice RTVI refreshing token then reconnecting",api_name:"voice-rtvi",conversation_id:c??void 0,auth_refresh_count:W.current,trigger:t}),(async()=>{let t=!1;try{t=!!await(i.getTokenRefreshHandler()?.())}catch{}r===H.current&&(t?z.current():(i.logger.warn({message:"Voice RTVI token refresh failed — no reconnect",api_name:"voice-rtvi",conversation_id:c??void 0}),i.showErrorToast(e.VOICE_CONNECT_ERROR_MESSAGE),l(e.signalSessionEnded())))})(),!0),[c,l]),Q=r.useCallback(r=>{const t=()=>r===B.current;return{onConnected:()=>{if(!t())return;i.logger.info({message:"Voice RTVI connected",api_name:"voice-rtvi",conversation_id:c??void 0}),l(e.setConnectionStatus(e.VOICE_CONNECTION_STATUSES.CONNECTED)),l(e.setListeningEnabled(!0)),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.IDLE)),M.current&&M.current.isMicEnabled!==y.current&&(i.logger.debug({message:"Voice re-asserting mic state on reconnect",api_name:"voice-rtvi",conversation_id:c??void 0,enabled:y.current}),M.current.enableMic(y.current));const{selectedMicId:r,selectedSpeakerId:n}=g.getState().voice;r&&M.current?.updateMic(r),n&&M.current?.updateSpeaker(n);const s=e=>r=>i.logger.warn({message:"Voice device enumeration failed",api_name:"voice-mic",conversation_id:c??void 0,device_kind:e,error_message:r instanceof Error?r.message:String(r)});M.current?.getAllMics().then(r=>{t()&&l(e.setMicrophones(r.map(e=>({deviceId:e.deviceId,label:e.label}))))}).catch(s("mics")),M.current?.getAllSpeakers().then(r=>{t()&&l(e.setSpeakers(r.map(e=>({deviceId:e.deviceId,label:e.label}))))}).catch(s("speakers")),x.current=!0},onBotReady:r=>{if(!t())return;const n=r?.about;h.current=n?.pipeline_mode===e.VOICE_PIPELINE_MODES.REALTIME,N.current=!0===n?.tts_word_timestamps,G.current=0,W.current=0,q.current=0,i.logger.info({message:"Voice RTVI bot-ready",api_name:"voice-rtvi",conversation_id:c??void 0,pipeline_mode:n?.pipeline_mode??"cascade",tts_word_timestamps:N.current,assistant_starts:d}),K(),d&&(l(e.setIsAgentWorking(!0)),T())},onDisconnected:()=>{if(!t())return;i.logger.info({message:"Voice RTVI disconnected",api_name:"voice-rtvi",conversation_id:c??void 0,drop_outcome:X.current??"transient",reconnect_attempts:G.current}),Y(!0);const r=H.current,n=X.current;X.current=null;const s=v.current;if(v.current=null,s)return i.logger.info({message:"Voice session ended by server — no reconnect",api_name:"voice-rtvi",conversation_id:c??void 0,reason:s.reason}),"idle_timeout"===s.reason&&i.showToast({title:"Voice ended",description:"It got quiet for a while, so we wrapped up. Start again anytime."}),void l(e.signalSessionEnded());if("terminal"===n)return i.logger.warn({message:"Voice RTVI terminal error — no retry",api_name:"voice-rtvi"}),void l(e.signalSessionEnded());if("auth"!==n){if(G.current>=e.VOICE_MAX_AUTO_RECONNECTS||null==c)return G.current>=e.VOICE_MAX_AUTO_RECONNECTS&&(i.logger.warn({message:"Voice RTVI auto-reconnect cap reached — giving up",api_name:"voice-rtvi",conversation_id:c??void 0,reconnect_attempts:G.current}),i.showErrorToast("Voice connection lost. Please try again.")),void l(e.signalSessionEnded());G.current+=1,i.logger.info({message:"Voice RTVI scheduling auto-reconnect",api_name:"voice-rtvi",conversation_id:c??void 0,reconnect_attempts:G.current,delay_ms:e.VOICE_RECONNECT_DELAY_MS}),F.current&&clearTimeout(F.current),F.current=setTimeout(()=>{F.current=null,z.current()},e.VOICE_RECONNECT_DELAY_MS)}else J(r,"auth_drop")||l(e.signalSessionEnded())},onError:r=>{if(!t())return;const n=r?.data,s=n?.error??n?.message??"Voice error";i.handleSharedEvent({kind:"error",error:s},f),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.IDLE)),S(i.INDICATOR_TIMER_ACTIONS.CANCEL),n?.fatal&&(e.VOICE_AUTH_ERROR_PATTERNS.some(e=>e.test(s))?X.current="auth":e.VOICE_TERMINAL_ERROR_PATTERNS.some(e=>e.test(s))&&(X.current="terminal"),i.logger.warn({message:"Voice RTVI fatal error — closing transport",api_name:"voice-rtvi",conversation_id:c??void 0,drop_outcome:X.current??"generic"}),M.current?.disconnect().catch(()=>{}))},onTrackStarted:(e,r)=>{t()&&(r?.local||"audio"!==e.kind||(i.logger.debug({message:"Voice RTVI bot audio track started",api_name:"voice-rtvi",conversation_id:c??void 0}),w.current?.(),w.current=_.attachBotAudioTrack(e)))},onAvailableMicsUpdated:r=>{t()&&(l(e.setMicrophones(r.map(e=>({deviceId:e.deviceId,label:e.label})))),x.current&&(L.current&&clearTimeout(L.current),L.current=setTimeout(()=>{if(L.current=null,!M.current)return;const{selectedMicId:e,microphones:r}=g.getState().voice;i.logger.info({message:"Voice audio devices changed",api_name:"voice-mic",microphones:r.map(e=>e.label).join(", ")}),i.logger.debug({message:"Voice re-acquiring mic (device change)",api_name:"voice-rtvi",enabled:y.current,device:e||"default"}),M.current.updateMic(e)},300)))},onAvailableSpeakersUpdated:r=>{if(!t())return;if(l(e.setSpeakers(r.map(e=>({deviceId:e.deviceId,label:e.label})))),!x.current)return;const n=r.map(e=>e.label).join(", ");n!==D.current&&(D.current=n,i.logger.info({message:"Voice audio output devices changed",api_name:"voice-mic",speakers:n}))},onMicUpdated:e=>{if(!t())return;const r=M.current;r&&r.isMicEnabled!==y.current&&(i.logger.debug({message:"Voice re-asserting mic state after track swap",api_name:"voice-rtvi",conversation_id:c??void 0,enabled:y.current,device:e?.label}),r.enableMic(y.current))},onUserMuteStarted:()=>{t()&&i.logger.debug({message:"Voice RTVI user-mute-started (ignored)",api_name:"voice-rtvi",conversation_id:c??void 0})},onUserMuteStopped:()=>{t()&&(i.logger.debug({message:"Voice RTVI user-mute-stopped",api_name:"voice-rtvi",conversation_id:c??void 0,honored:d}),d&&l(e.setServerMuted(!1)))},onUserStartedSpeaking:()=>{t()&&(i.logger.debug({message:"Voice RTVI user started speaking",api_name:"voice-rtvi",conversation_id:c??void 0}),A.current=null,k.current="",l(e.setInterimTranscript(null)),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.LISTENING)))},onUserStoppedSpeaking:()=>{if(!t())return;i.logger.debug({message:"Voice RTVI user stopped speaking",api_name:"voice-rtvi",conversation_id:c??void 0,has_interim:!!k.current.trim()}),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.PROCESSING)),l(e.setIsAgentWorking(!0)),l(e.setStatusText(null)),T();const r=k.current.trim();if(r){const t=`${e.VOICE_USER_ID_PREFIX}${i.generateUniqueId()}`;A.current=t,l(e.setInterimTranscript(null)),p.current.setMessages(n=>[...n,e.makeUserMessage(r,t)])}},onUserTranscript:r=>{if(!t())return;if(!r.final)return void(r.text&&(k.current=r.text,h.current||l(e.setInterimTranscript(r.text))));l(e.setInterimTranscript(null));const n=r.text&&r.text.trim()?r.text:"";i.logger.debug({message:"Voice RTVI user transcript (final)",api_name:"voice-rtvi",conversation_id:c??void 0,has_text:!!n});const s=A.current;A.current=null,s?n&&p.current.setMessages(e=>e.map(e=>e.id===s?{...e,message:n}:e)):n&&p.current.setMessages(r=>[...r,e.makeUserMessage(n)])},onBotStartedSpeaking:()=>{t()&&(i.logger.debug({message:"Voice RTVI bot started speaking",api_name:"voice-rtvi",conversation_id:c??void 0}),C(),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.SPEAKING)),S(i.INDICATOR_TIMER_ACTIONS.FINALIZE))},onBotOutput:e=>{t()&&Z(e)},onBotStoppedSpeaking:()=>{t()&&(i.logger.debug({message:"Voice RTVI bot stopped speaking",api_name:"voice-rtvi",conversation_id:c??void 0}),R(),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.IDLE)),S(i.INDICATOR_TIMER_ACTIONS.FINALIZE),d&&g.getState().voice.serverMuted&&(i.logger.warn({message:"Voice greeting mute-hold released on bot-turn end (server unmute missing)",api_name:"voice-rtvi",conversation_id:c??void 0}),l(e.setServerMuted(!1))))},onServerMessage:e=>{t()&&O(e)}}},[l,g,_,K,Y,S,T,Z,C,R,O,f,p,d,c,J]),ee=r.useCallback(async()=>{if(M.current||j.current)return;if(!c)return;if(j.current=!0,i.logger.info({message:"Voice RTVI connecting",api_name:"voice-rtvi",conversation_id:c,assistant_starts:d}),d&&(y.current=!1,l(e.setServerMuted(!0)),i.logger.debug({message:"Voice greeting mute-hold armed",api_name:"voice-rtvi",conversation_id:c})),i.isTokenAboutToExpire(i.readTokenExpiresAt())&&q.current<e.VOICE_MAX_AUTH_REFRESHES){q.current+=1,i.logger.debug({message:"Voice pre-connect token refresh (freshness gate)",api_name:"voice-rtvi",conversation_id:c,freshness_refresh_count:q.current});const e=H.current;try{await Promise.resolve(i.getTokenRefreshHandler()?.())}catch{}if(e!==H.current)return}const r=++B.current,t=H.current;v.current=null,X.current=null,l(e.setConnectionStatus(e.VOICE_CONNECTION_STATUSES.CONNECTING));const n=()=>t!==H.current||r!==B.current;try{const[{PipecatClient:t,LogLevel:a,RTVIMessage:d},g,_]=await Promise.all([import("@pipecat-ai/client-js"),o(c),u()]);if(n())return;P.current=d;const m=new t({transport:_,enableMic:!0,enableCam:!1,callbacks:Q(r)});if(m.setLogLevel(a.WARN),n())return void m.disconnect().catch(()=>{});M.current=m,i.logger.debug({message:"Voice RTVI client constructed",api_name:"voice-rtvi",conversation_id:c,enable_mic:y.current}),K(),U.current=setTimeout(()=>{i.logger.warn({message:"Voice RTVI connect timed out (no bot-ready)",api_name:"voice-rtvi"}),i.showErrorToast("Voice connection timed out. Please try again."),l(e.signalSessionEnded()),$.current()},e.VOICE_CONNECT_TIMEOUT_MS),await m.connect({webrtcRequestParams:{endpoint:i.API_ENDPOINTS.WEBRTC_OFFER(c),headers:new Headers(s())},iceConfig:{iceServers:g}})}catch(n){const s=(e=>{const r="object"==typeof e&&null!==e&&"status"in e?e.status:void 0;return"number"==typeof r?r:void 0})(n);if(401===s&&r===B.current&&W.current<e.VOICE_MAX_AUTH_REFRESHES)return i.logger.warn({message:"Voice RTVI offer refused with 401 — refreshing token",api_name:"voice-rtvi",conversation_id:c}),F.current&&(clearTimeout(F.current),F.current=null),G.current=0,Y(!1),void J(t,"offer_401");i.logger.error({message:"Voice RTVI connect failed",api_name:"voice-rtvi",status:s,error_message:n instanceof Error?n.message:String(n)});const o=(e=>void 0!==e&&e>=400&&e<500)(s),a=o?await(async(r,t)=>{const n=429===t?"Voice session limit reached. Please try again later.":e.VOICE_CONNECT_ERROR_MESSAGE,i="object"==typeof r&&null!==r&&"cause"in r?r.cause:void 0;if(!(i instanceof Response))return n;try{const e=await i.json(),r=e?.data?.error_message??e?.data?.message;return"string"==typeof r?r:n}catch{return n}})(n,s):e.VOICE_CONNECT_ERROR_MESSAGE;r===B.current&&(i.showErrorToast(a),o&&(F.current&&(clearTimeout(F.current),F.current=null),G.current=0,l(e.signalSessionEnded())),Y(!1))}},[c,l,u,Q,K,Y,d,J]),re=r.useCallback((r=!0)=>{i.logger.info({message:"Voice RTVI disconnect (intentional teardown)",api_name:"voice-rtvi",conversation_id:c??void 0,commit_turn:r}),H.current+=1,B.current+=1,q.current=0,F.current&&(clearTimeout(F.current),F.current=null),G.current=0,W.current=0,X.current=null,y.current=!0,l(e.setMuted(!1));const t=M.current;t?.disconnect().catch(e=>{i.logger.warn({message:"Voice RTVI disconnect threw",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}),Y(r)},[Y,l,c]),te=r.useCallback(e=>{y.current=e,M.current&&(i.logger.debug({message:"Voice mic enable toggled",api_name:"voice-rtvi",enabled:e}),M.current.enableMic(e))},[]),ne=r.useCallback(r=>{l(e.setSelectedMicId(r)),M.current?.updateMic(r)},[l]),ie=r.useCallback(r=>{l(e.setSelectedSpeakerId(r)),M.current?.updateSpeaker(r)},[l]),se=r.useCallback((r,t)=>{const n=M.current;if(!n)return;const s=t?.modelContext;i.logger.info({message:"Voice RTVI send-text",api_name:"voice-rtvi",conversation_id:c??void 0,run_immediately:!0,has_model_context:Boolean(s?.length)});try{const e=P.current;s?.length&&e?n.transport.sendMessage(new e("send-text",{content:r,options:{run_immediately:!0,audio_response:!0},model_context:s})):n.sendText(r,{run_immediately:!0,audio_response:!0})}catch(e){return void i.logger.error({message:"Voice RTVI send-text failed",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}E.current&&R(),p.current.setMessages(n=>[...n,e.makeUserMessage(r,void 0,t?.documents,t?.messageType)]),l(e.setIsAgentWorking(!0)),l(e.setStatusText(null)),T(),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.PROCESSING))},[l,T,R,E,p,c]);r.useEffect(()=>{$.current=re},[re]),r.useEffect(()=>{z.current=ee},[ee]);const oe=r.useRef(c),ce=r.useRef(!1);r.useEffect(()=>{const e=oe.current;if(oe.current=c,e===c)return;const r=j.current;r&&(ce.current=!0),r&&re(!1),ce.current&&null!=c&&(ce.current=!1,ee())},[c,re,ee]);const ae=r.useRef(m);return r.useEffect(()=>{const e=ae.current;ae.current=m,e&&!m&&j.current&&re(!1)},[m,re]),r.useEffect(()=>()=>{$.current()},[]),{connect:ee,disconnect:re,sendText:se,setMicEnabled:te,setInputDevice:ne,setOutputDevice:ie}}(c,a,u,d);return e.useVoiceContextValue(l,{voiceBridge:a})};
|
package/dist/webrtc.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{u as e,s as r,a as t,b as n,c as i,V as c,d as o,e as a,f as s,g as u,m as d,h as m,i as l,j as v,k as p,l as g,n as _,o as f,p as V,q as I,r as b,t as h,v as T,w as R,x as S,y as E,z as w}from"./sessionHelpers-BIwU9Fpt.js";import{useRef as k,useCallback as y,useEffect as M}from"react";import{useDispatch as C,useStore as x,useSelector as N}from"react-redux";import{u as B,f as L,a as A}from"./useVoiceSessionCore-Cwkj3eVq.js";import{API_ENDPOINTS as j,logger as D,getCustomHeaders as O,readAccessToken as P,AUTH_HEADERS as U,readDeviceToken as F,DEVICE_TOKEN_HEADER as G,INDICATOR_TIMER_ACTIONS as q,generateUniqueId as W,handleSharedEvent as H,showToast as K,getTokenRefreshHandler as Z,showErrorToast as $,isTokenAboutToExpire as z,readTokenExpiresAt as J}from"@1interface/shared-core";function Q(){const e={};for(const[r,t]of Object.entries(O()))t&&(e[r]=t);const r=P();r&&(e[U.ACCESS_TOKEN]=r);const t=F();return t&&(e[G]=t),e}async function X(e){try{const r=await fetch(j.WEBRTC_ICE(e),{headers:Q()});return r.ok?(await r.json()).iceServers??[]:(D.warn({message:"Voice ICE fetch failed — continuing without TURN",api_name:"voice-webrtc-ice",status:r.status}),[])}catch(e){return D.warn({message:"Voice ICE fetch error — continuing without TURN",api_name:"voice-webrtc-ice",error_message:e instanceof Error?e.message:String(e)}),[]}}const Y=({conversationId:O,voiceBridge:P,transportFactory:U,assistantStartsConversation:F})=>{const G=function(w,O,P,U){const F=C(),G=x(),Y=e(),ee=N(e=>e.auth.accessToken),re=k(null),{bridgeRef:te,botTurnMsgIdRef:ne,liveSegmentsRef:ie,startResponseTimer:ce,clearIndicator:oe,sharedDeps:ae,commitBotTurn:se,discardBotTurn:ue,openBotTurnIfNeeded:de,ingestBotSegment:me,routeServerMessage:le}=B(w,O,{apiName:"voice-rtvi",onSessionEnded:e=>{re.current={reason:e}}}),ve=k(!1),pe=k(!1),ge=k(null),_e=k(""),fe=k(null),Ve=k(null),Ie=k(null),be=k(null),he=k(!0),Te=k(null),Re=k(!1),Se=k(""),Ee=k(0),we=k(0),ke=k(!1),ye=k(0),Me=k(null),Ce=k(0),xe=k(0),Ne=k(null),Be=y(()=>{be.current&&(clearTimeout(be.current),be.current=null)},[]),Le=y(e=>{Be(),Te.current&&(clearTimeout(Te.current),Te.current=null),Re.current=!1,Se.current="",F(r([])),F(t([])),Ie.current?.(),Ie.current=null,fe.current=null,ke.current=!1,F(n(!1)),F(i(c.IDLE)),F(o(a.DISCONNECTED)),F(s(null)),F(u(!1)),ge.current=null,_e.current="",ve.current=!1,pe.current=!1,e?se():ue(),oe(q.CANCEL)},[F,oe,Be,se,ue]),Ae=y(e=>{const r=e.spoken_progress?(e.spoken_progress?.accumulated_text??"")+(e.spoken_progress?.remaining_text??""):e.text??"";if(!r)return;de();const t=ve.current?L(ie.current,r):A(ie.current,e,r,pe.current);me(t)},[de,me,ie]),je=k(()=>{}),De=k(async()=>{}),Oe=y(e=>{const h=()=>e===Ee.current;return{onConnected:()=>{if(!h())return;D.info({message:"Voice RTVI connected",api_name:"voice-rtvi",conversation_id:w??void 0}),F(o(a.CONNECTED)),F(n(!0)),F(i(c.IDLE)),fe.current&&fe.current.isMicEnabled!==he.current&&(D.debug({message:"Voice re-asserting mic state on reconnect",api_name:"voice-rtvi",conversation_id:w??void 0,enabled:he.current}),fe.current.enableMic(he.current));const{selectedMicId:e,selectedSpeakerId:s}=G.getState().voice;e&&fe.current?.updateMic(e),s&&fe.current?.updateSpeaker(s);const u=e=>r=>D.warn({message:"Voice device enumeration failed",api_name:"voice-mic",conversation_id:w??void 0,device_kind:e,error_message:r instanceof Error?r.message:String(r)});fe.current?.getAllMics().then(e=>{h()&&F(r(e.map(e=>({deviceId:e.deviceId,label:e.label}))))}).catch(u("mics")),fe.current?.getAllSpeakers().then(e=>{h()&&F(t(e.map(e=>({deviceId:e.deviceId,label:e.label}))))}).catch(u("speakers")),Re.current=!0},onBotReady:e=>{if(!h())return;const r=e?.about;ve.current=r?.pipeline_mode===b.REALTIME,pe.current=!0===r?.tts_word_timestamps,Ce.current=0,xe.current=0,ye.current=0,D.info({message:"Voice RTVI bot-ready",api_name:"voice-rtvi",conversation_id:w??void 0,pipeline_mode:r?.pipeline_mode??"cascade",tts_word_timestamps:pe.current,assistant_starts:U}),Be(),U&&(F(m(!0)),ce())},onDisconnected:()=>{if(!h())return;D.info({message:"Voice RTVI disconnected",api_name:"voice-rtvi",conversation_id:w??void 0,drop_outcome:Ne.current??"transient",reconnect_attempts:Ce.current}),Le(!0);const e=we.current,r=Ne.current;Ne.current=null;const t=re.current;return re.current=null,t?(D.info({message:"Voice session ended by server — no reconnect",api_name:"voice-rtvi",conversation_id:w??void 0,reason:t.reason}),"idle_timeout"===t.reason&&K({title:"Voice ended",description:"It got quiet for a while, so we wrapped up. Start again anytime."}),void F(_())):"terminal"===r?(D.warn({message:"Voice RTVI terminal error — no retry",api_name:"voice-rtvi"}),void F(_())):"auth"===r?xe.current>=f||null==w?(D.warn({message:"Voice RTVI auth refresh budget exhausted — giving up",api_name:"voice-rtvi",conversation_id:w??void 0}),void F(_())):(xe.current+=1,D.info({message:"Voice RTVI auth drop — refreshing token then reconnecting",api_name:"voice-rtvi",conversation_id:w??void 0,auth_refresh_count:xe.current}),void(async()=>{let r=!1;try{r=!!await(Z()?.())}catch{}e===we.current&&(r?De.current():(D.warn({message:"Voice RTVI token refresh failed — no reconnect",api_name:"voice-rtvi",conversation_id:w??void 0}),F(_())))})()):Ce.current>=V||null==w?(Ce.current>=V&&(D.warn({message:"Voice RTVI auto-reconnect cap reached — giving up",api_name:"voice-rtvi",conversation_id:w??void 0,reconnect_attempts:Ce.current}),$("Voice connection lost. Please try again.")),void F(_())):(Ce.current+=1,D.info({message:"Voice RTVI scheduling auto-reconnect",api_name:"voice-rtvi",conversation_id:w??void 0,reconnect_attempts:Ce.current,delay_ms:I}),Me.current&&clearTimeout(Me.current),void(Me.current=setTimeout(()=>{Me.current=null,De.current()},I)))},onError:e=>{if(!h())return;const r=e?.data,t=r?.error??r?.message??"Voice error";H({kind:"error",error:t},ae),F(i(c.IDLE)),oe(q.CANCEL),r?.fatal&&(p.some(e=>e.test(t))?Ne.current="auth":g.some(e=>e.test(t))&&(Ne.current="terminal"),D.warn({message:"Voice RTVI fatal error — closing transport",api_name:"voice-rtvi",conversation_id:w??void 0,drop_outcome:Ne.current??"generic"}),fe.current?.disconnect().catch(()=>{}))},onTrackStarted:(e,r)=>{h()&&(r?.local||"audio"!==e.kind||(D.debug({message:"Voice RTVI bot audio track started",api_name:"voice-rtvi",conversation_id:w??void 0}),Ie.current?.(),Ie.current=Y.attachBotAudioTrack(e)))},onAvailableMicsUpdated:e=>{h()&&(F(r(e.map(e=>({deviceId:e.deviceId,label:e.label})))),Re.current&&(Te.current&&clearTimeout(Te.current),Te.current=setTimeout(()=>{if(Te.current=null,!fe.current)return;const{selectedMicId:e,microphones:r}=G.getState().voice;D.info({message:"Voice audio devices changed",api_name:"voice-mic",microphones:r.map(e=>e.label).join(", ")}),D.debug({message:"Voice re-acquiring mic (device change)",api_name:"voice-rtvi",enabled:he.current,device:e||"default"}),fe.current.updateMic(e)},300)))},onAvailableSpeakersUpdated:e=>{if(!h())return;if(F(t(e.map(e=>({deviceId:e.deviceId,label:e.label})))),!Re.current)return;const r=e.map(e=>e.label).join(", ");r!==Se.current&&(Se.current=r,D.info({message:"Voice audio output devices changed",api_name:"voice-mic",speakers:r}))},onMicUpdated:e=>{if(!h())return;const r=fe.current;r&&r.isMicEnabled!==he.current&&(D.debug({message:"Voice re-asserting mic state after track swap",api_name:"voice-rtvi",conversation_id:w??void 0,enabled:he.current,device:e?.label}),r.enableMic(he.current))},onUserMuteStarted:()=>{h()&&D.debug({message:"Voice RTVI user-mute-started (ignored)",api_name:"voice-rtvi",conversation_id:w??void 0})},onUserMuteStopped:()=>{h()&&(D.debug({message:"Voice RTVI user-mute-stopped",api_name:"voice-rtvi",conversation_id:w??void 0,honored:U}),U&&F(u(!1)))},onUserStartedSpeaking:()=>{h()&&(D.debug({message:"Voice RTVI user started speaking",api_name:"voice-rtvi",conversation_id:w??void 0}),ge.current=null,_e.current="",F(s(null)),F(i(c.LISTENING)))},onUserStoppedSpeaking:()=>{if(!h())return;D.debug({message:"Voice RTVI user stopped speaking",api_name:"voice-rtvi",conversation_id:w??void 0,has_interim:!!_e.current.trim()}),F(i(c.PROCESSING)),F(m(!0)),F(l(null)),ce();const e=_e.current.trim();if(e){const r=`${v}${W()}`;ge.current=r,F(s(null)),te.current.setMessages(t=>[...t,d(e,r)])}},onUserTranscript:e=>{if(!h())return;if(!e.final)return void(e.text&&(_e.current=e.text,ve.current||F(s(e.text))));F(s(null));const r=e.text&&e.text.trim()?e.text:"";D.debug({message:"Voice RTVI user transcript (final)",api_name:"voice-rtvi",conversation_id:w??void 0,has_text:!!r});const t=ge.current;ge.current=null,t?r&&te.current.setMessages(e=>e.map(e=>e.id===t?{...e,message:r}:e)):r&&te.current.setMessages(e=>[...e,d(r)])},onBotStartedSpeaking:()=>{h()&&(D.debug({message:"Voice RTVI bot started speaking",api_name:"voice-rtvi",conversation_id:w??void 0}),de(),F(i(c.SPEAKING)),oe(q.FINALIZE))},onBotOutput:e=>{h()&&Ae(e)},onBotStoppedSpeaking:()=>{h()&&(D.debug({message:"Voice RTVI bot stopped speaking",api_name:"voice-rtvi",conversation_id:w??void 0}),se(),F(i(c.IDLE)),oe(q.FINALIZE),U&&G.getState().voice.serverMuted&&(D.warn({message:"Voice greeting mute-hold released on bot-turn end (server unmute missing)",api_name:"voice-rtvi",conversation_id:w??void 0}),F(u(!1))))},onServerMessage:e=>{h()&&le(e)}}},[F,G,Y,Be,Le,oe,ce,Ae,de,se,le,ae,te,U,w]),Pe=y(async()=>{if(fe.current||ke.current)return;if(!w)return;if(ke.current=!0,D.info({message:"Voice RTVI connecting",api_name:"voice-rtvi",conversation_id:w,assistant_starts:U}),U&&(he.current=!1,F(u(!0)),D.debug({message:"Voice greeting mute-hold armed",api_name:"voice-rtvi",conversation_id:w})),z(J())&&ye.current<f){ye.current+=1,D.debug({message:"Voice pre-connect token refresh (freshness gate)",api_name:"voice-rtvi",conversation_id:w,freshness_refresh_count:ye.current});const e=we.current;try{await Promise.resolve(Z()?.())}catch{}if(e!==we.current)return}const e=++Ee.current,r=we.current;re.current=null,Ne.current=null,F(o(a.CONNECTING));const t=()=>r!==we.current||e!==Ee.current;try{const[{PipecatClient:r,LogLevel:n,RTVIMessage:i},c,o]=await Promise.all([import("@pipecat-ai/client-js"),X(w),P()]);if(t())return;Ve.current=i;const a=new r({transport:o,enableMic:!0,enableCam:!1,callbacks:Oe(e)});if(a.setLogLevel(n.WARN),t())return void a.disconnect().catch(()=>{});fe.current=a,D.debug({message:"Voice RTVI client constructed",api_name:"voice-rtvi",conversation_id:w,enable_mic:he.current}),Be(),be.current=setTimeout(()=>{D.warn({message:"Voice RTVI connect timed out (no bot-ready)",api_name:"voice-rtvi"}),$("Voice connection timed out. Please try again."),F(_()),je.current()},h),await a.connect({webrtcRequestParams:{endpoint:j.WEBRTC_OFFER(w),headers:new Headers(Q())},iceConfig:{iceServers:c}})}catch(r){const t=(e=>{const r="object"==typeof e&&null!==e&&"status"in e?e.status:void 0;return"number"==typeof r?r:void 0})(r);D.error({message:"Voice RTVI connect failed",api_name:"voice-rtvi",status:t,error_message:r instanceof Error?r.message:String(r)});const n=(e=>void 0!==e&&e>=400&&e<500)(t),i=n?await(async(e,r)=>{const t=429===r?"Voice session limit reached. Please try again later.":T,n="object"==typeof e&&null!==e&&"cause"in e?e.cause:void 0;if(!(n instanceof Response))return t;try{const e=await n.json(),r=e?.data?.error_message??e?.data?.message;return"string"==typeof r?r:t}catch{return t}})(r,t):T;e===Ee.current&&($(i),n&&(Me.current&&(clearTimeout(Me.current),Me.current=null),Ce.current=0,F(_())),Le(!1))}},[w,F,P,Oe,Be,Le,U]),Ue=y((e=!0)=>{D.info({message:"Voice RTVI disconnect (intentional teardown)",api_name:"voice-rtvi",conversation_id:w??void 0,commit_turn:e}),we.current+=1,Ee.current+=1,ye.current=0,Me.current&&(clearTimeout(Me.current),Me.current=null),Ce.current=0,xe.current=0,Ne.current=null,he.current=!0,F(R(!1));const r=fe.current;r?.disconnect().catch(e=>{D.warn({message:"Voice RTVI disconnect threw",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}),Le(e)},[Le,F,w]),Fe=y(e=>{he.current=e,fe.current&&(D.debug({message:"Voice mic enable toggled",api_name:"voice-rtvi",enabled:e}),fe.current.enableMic(e))},[]),Ge=y(e=>{F(S(e)),fe.current?.updateMic(e)},[F]),qe=y(e=>{F(E(e)),fe.current?.updateSpeaker(e)},[F]),We=y((e,r)=>{const t=fe.current;if(!t)return;const n=r?.modelContext;D.info({message:"Voice RTVI send-text",api_name:"voice-rtvi",conversation_id:w??void 0,run_immediately:!0,has_model_context:Boolean(n?.length)});try{const r=Ve.current;n?.length&&r?t.transport.sendMessage(new r("send-text",{content:e,options:{run_immediately:!0,audio_response:!0},model_context:n})):t.sendText(e,{run_immediately:!0,audio_response:!0})}catch(e){return void D.error({message:"Voice RTVI send-text failed",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}ne.current&&se(),te.current.setMessages(t=>[...t,d(e,void 0,r?.documents,r?.messageType)]),F(m(!0)),F(l(null)),ce(),F(i(c.PROCESSING))},[F,ce,se,ne,te,w]);M(()=>{je.current=Ue},[Ue]),M(()=>{De.current=Pe},[Pe]);const He=k(w),Ke=k(!1);M(()=>{const e=He.current;if(He.current=w,e===w)return;const r=ke.current;r&&(Ke.current=!0),r&&Ue(!1),Ke.current&&null!=w&&(Ke.current=!1,Pe())},[w,Ue,Pe]);const Ze=k(ee);return M(()=>{const e=Ze.current;Ze.current=ee,e&&!ee&&ke.current&&Ue(!1)},[ee,Ue]),M(()=>()=>{je.current()},[]),{connect:Pe,disconnect:Ue,sendText:We,setMicEnabled:Fe,setInputDevice:Ge,setOutputDevice:qe}}(O,P,U,F);return w(G,{voiceBridge:P})};export{Y as useVoiceProviderValue};
|
|
1
|
+
import{u as e,s as r,a as t,b as n,c as i,V as c,d as o,e as a,f as s,g as u,h as d,i as m,j as l,m as v,k as g,l as p,n as _,o as f,p as V,q as I,r as b,t as h,v as T,w as R,x as S,y as w,z as E}from"./sessionHelpers-CK66mJIw.js";import{useRef as k,useCallback as y,useEffect as M}from"react";import{useDispatch as C,useStore as x,useSelector as N}from"react-redux";import{u as B,f as L,a as A}from"./useVoiceSessionCore-C9xo14Pi.js";import{API_ENDPOINTS as j,logger as D,getCustomHeaders as O,readAccessToken as P,AUTH_HEADERS as U,readDeviceToken as F,DEVICE_TOKEN_HEADER as G,INDICATOR_TIMER_ACTIONS as q,getTokenRefreshHandler as W,showErrorToast as H,generateUniqueId as K,handleSharedEvent as Z,showToast as $,isTokenAboutToExpire as z,readTokenExpiresAt as J}from"@1interface/shared-core";function Q(){const e={};for(const[r,t]of Object.entries(O()))t&&(e[r]=t);const r=P();r&&(e[U.ACCESS_TOKEN]=r);const t=F();return t&&(e[G]=t),e}async function X(e){try{const r=await fetch(j.WEBRTC_ICE(e),{headers:Q()});return r.ok?(await r.json()).iceServers??[]:(D.warn({message:"Voice ICE fetch failed — continuing without TURN",api_name:"voice-webrtc-ice",status:r.status}),[])}catch(e){return D.warn({message:"Voice ICE fetch error — continuing without TURN",api_name:"voice-webrtc-ice",error_message:e instanceof Error?e.message:String(e)}),[]}}const Y=({conversationId:O,voiceBridge:P,transportFactory:U,assistantStartsConversation:F})=>{const G=function(E,O,P,U){const F=C(),G=x(),Y=e(),ee=N(e=>e.auth.accessToken),re=k(null),{bridgeRef:te,botTurnMsgIdRef:ne,liveSegmentsRef:ie,startResponseTimer:ce,clearIndicator:oe,sharedDeps:ae,commitBotTurn:se,discardBotTurn:ue,openBotTurnIfNeeded:de,ingestBotSegment:me,routeServerMessage:le}=B(E,O,{apiName:"voice-rtvi",onSessionEnded:e=>{re.current={reason:e}}}),ve=k(!1),ge=k(!1),pe=k(null),_e=k(""),fe=k(null),Ve=k(null),Ie=k(null),be=k(null),he=k(!0),Te=k(null),Re=k(!1),Se=k(""),we=k(0),Ee=k(0),ke=k(!1),ye=k(0),Me=k(null),Ce=k(0),xe=k(0),Ne=k(null),Be=y(()=>{be.current&&(clearTimeout(be.current),be.current=null)},[]),Le=y(e=>{Be(),Te.current&&(clearTimeout(Te.current),Te.current=null),Re.current=!1,Se.current="",F(r([])),F(t([])),Ie.current?.(),Ie.current=null,fe.current=null,ke.current=!1,F(n(!1)),F(i(c.IDLE)),F(o(a.DISCONNECTED)),F(s(null)),F(u(!1)),pe.current=null,_e.current="",ve.current=!1,ge.current=!1,e?se():ue(),oe(q.CANCEL)},[F,oe,Be,se,ue]),Ae=y(e=>{const r=e.spoken_progress?(e.spoken_progress?.accumulated_text??"")+(e.spoken_progress?.remaining_text??""):e.text??"";if(!r)return;de();const t=ve.current?L(ie.current,r):A(ie.current,e,r,ge.current);me(t)},[de,me,ie]),je=k(()=>{}),De=k(async()=>{}),Oe=y((e,r)=>xe.current>=d||null==E?(D.warn({message:"Voice RTVI auth refresh budget exhausted — giving up",api_name:"voice-rtvi",conversation_id:E??void 0,trigger:r}),!1):(xe.current+=1,D.info({message:"Voice RTVI refreshing token then reconnecting",api_name:"voice-rtvi",conversation_id:E??void 0,auth_refresh_count:xe.current,trigger:r}),(async()=>{let r=!1;try{r=!!await(W()?.())}catch{}e===Ee.current&&(r?De.current():(D.warn({message:"Voice RTVI token refresh failed — no reconnect",api_name:"voice-rtvi",conversation_id:E??void 0}),H(m),F(l())))})(),!0),[E,F]),Pe=y(e=>{const d=()=>e===we.current;return{onConnected:()=>{if(!d())return;D.info({message:"Voice RTVI connected",api_name:"voice-rtvi",conversation_id:E??void 0}),F(o(a.CONNECTED)),F(n(!0)),F(i(c.IDLE)),fe.current&&fe.current.isMicEnabled!==he.current&&(D.debug({message:"Voice re-asserting mic state on reconnect",api_name:"voice-rtvi",conversation_id:E??void 0,enabled:he.current}),fe.current.enableMic(he.current));const{selectedMicId:e,selectedSpeakerId:s}=G.getState().voice;e&&fe.current?.updateMic(e),s&&fe.current?.updateSpeaker(s);const u=e=>r=>D.warn({message:"Voice device enumeration failed",api_name:"voice-mic",conversation_id:E??void 0,device_kind:e,error_message:r instanceof Error?r.message:String(r)});fe.current?.getAllMics().then(e=>{d()&&F(r(e.map(e=>({deviceId:e.deviceId,label:e.label}))))}).catch(u("mics")),fe.current?.getAllSpeakers().then(e=>{d()&&F(t(e.map(e=>({deviceId:e.deviceId,label:e.label}))))}).catch(u("speakers")),Re.current=!0},onBotReady:e=>{if(!d())return;const r=e?.about;ve.current=r?.pipeline_mode===h.REALTIME,ge.current=!0===r?.tts_word_timestamps,Ce.current=0,xe.current=0,ye.current=0,D.info({message:"Voice RTVI bot-ready",api_name:"voice-rtvi",conversation_id:E??void 0,pipeline_mode:r?.pipeline_mode??"cascade",tts_word_timestamps:ge.current,assistant_starts:U}),Be(),U&&(F(g(!0)),ce())},onDisconnected:()=>{if(!d())return;D.info({message:"Voice RTVI disconnected",api_name:"voice-rtvi",conversation_id:E??void 0,drop_outcome:Ne.current??"transient",reconnect_attempts:Ce.current}),Le(!0);const e=Ee.current,r=Ne.current;Ne.current=null;const t=re.current;if(re.current=null,t)return D.info({message:"Voice session ended by server — no reconnect",api_name:"voice-rtvi",conversation_id:E??void 0,reason:t.reason}),"idle_timeout"===t.reason&&$({title:"Voice ended",description:"It got quiet for a while, so we wrapped up. Start again anytime."}),void F(l());if("terminal"===r)return D.warn({message:"Voice RTVI terminal error — no retry",api_name:"voice-rtvi"}),void F(l());if("auth"!==r){if(Ce.current>=I||null==E)return Ce.current>=I&&(D.warn({message:"Voice RTVI auto-reconnect cap reached — giving up",api_name:"voice-rtvi",conversation_id:E??void 0,reconnect_attempts:Ce.current}),H("Voice connection lost. Please try again.")),void F(l());Ce.current+=1,D.info({message:"Voice RTVI scheduling auto-reconnect",api_name:"voice-rtvi",conversation_id:E??void 0,reconnect_attempts:Ce.current,delay_ms:b}),Me.current&&clearTimeout(Me.current),Me.current=setTimeout(()=>{Me.current=null,De.current()},b)}else Oe(e,"auth_drop")||F(l())},onError:e=>{if(!d())return;const r=e?.data,t=r?.error??r?.message??"Voice error";Z({kind:"error",error:t},ae),F(i(c.IDLE)),oe(q.CANCEL),r?.fatal&&(f.some(e=>e.test(t))?Ne.current="auth":V.some(e=>e.test(t))&&(Ne.current="terminal"),D.warn({message:"Voice RTVI fatal error — closing transport",api_name:"voice-rtvi",conversation_id:E??void 0,drop_outcome:Ne.current??"generic"}),fe.current?.disconnect().catch(()=>{}))},onTrackStarted:(e,r)=>{d()&&(r?.local||"audio"!==e.kind||(D.debug({message:"Voice RTVI bot audio track started",api_name:"voice-rtvi",conversation_id:E??void 0}),Ie.current?.(),Ie.current=Y.attachBotAudioTrack(e)))},onAvailableMicsUpdated:e=>{d()&&(F(r(e.map(e=>({deviceId:e.deviceId,label:e.label})))),Re.current&&(Te.current&&clearTimeout(Te.current),Te.current=setTimeout(()=>{if(Te.current=null,!fe.current)return;const{selectedMicId:e,microphones:r}=G.getState().voice;D.info({message:"Voice audio devices changed",api_name:"voice-mic",microphones:r.map(e=>e.label).join(", ")}),D.debug({message:"Voice re-acquiring mic (device change)",api_name:"voice-rtvi",enabled:he.current,device:e||"default"}),fe.current.updateMic(e)},300)))},onAvailableSpeakersUpdated:e=>{if(!d())return;if(F(t(e.map(e=>({deviceId:e.deviceId,label:e.label})))),!Re.current)return;const r=e.map(e=>e.label).join(", ");r!==Se.current&&(Se.current=r,D.info({message:"Voice audio output devices changed",api_name:"voice-mic",speakers:r}))},onMicUpdated:e=>{if(!d())return;const r=fe.current;r&&r.isMicEnabled!==he.current&&(D.debug({message:"Voice re-asserting mic state after track swap",api_name:"voice-rtvi",conversation_id:E??void 0,enabled:he.current,device:e?.label}),r.enableMic(he.current))},onUserMuteStarted:()=>{d()&&D.debug({message:"Voice RTVI user-mute-started (ignored)",api_name:"voice-rtvi",conversation_id:E??void 0})},onUserMuteStopped:()=>{d()&&(D.debug({message:"Voice RTVI user-mute-stopped",api_name:"voice-rtvi",conversation_id:E??void 0,honored:U}),U&&F(u(!1)))},onUserStartedSpeaking:()=>{d()&&(D.debug({message:"Voice RTVI user started speaking",api_name:"voice-rtvi",conversation_id:E??void 0}),pe.current=null,_e.current="",F(s(null)),F(i(c.LISTENING)))},onUserStoppedSpeaking:()=>{if(!d())return;D.debug({message:"Voice RTVI user stopped speaking",api_name:"voice-rtvi",conversation_id:E??void 0,has_interim:!!_e.current.trim()}),F(i(c.PROCESSING)),F(g(!0)),F(p(null)),ce();const e=_e.current.trim();if(e){const r=`${_}${K()}`;pe.current=r,F(s(null)),te.current.setMessages(t=>[...t,v(e,r)])}},onUserTranscript:e=>{if(!d())return;if(!e.final)return void(e.text&&(_e.current=e.text,ve.current||F(s(e.text))));F(s(null));const r=e.text&&e.text.trim()?e.text:"";D.debug({message:"Voice RTVI user transcript (final)",api_name:"voice-rtvi",conversation_id:E??void 0,has_text:!!r});const t=pe.current;pe.current=null,t?r&&te.current.setMessages(e=>e.map(e=>e.id===t?{...e,message:r}:e)):r&&te.current.setMessages(e=>[...e,v(r)])},onBotStartedSpeaking:()=>{d()&&(D.debug({message:"Voice RTVI bot started speaking",api_name:"voice-rtvi",conversation_id:E??void 0}),de(),F(i(c.SPEAKING)),oe(q.FINALIZE))},onBotOutput:e=>{d()&&Ae(e)},onBotStoppedSpeaking:()=>{d()&&(D.debug({message:"Voice RTVI bot stopped speaking",api_name:"voice-rtvi",conversation_id:E??void 0}),se(),F(i(c.IDLE)),oe(q.FINALIZE),U&&G.getState().voice.serverMuted&&(D.warn({message:"Voice greeting mute-hold released on bot-turn end (server unmute missing)",api_name:"voice-rtvi",conversation_id:E??void 0}),F(u(!1))))},onServerMessage:e=>{d()&&le(e)}}},[F,G,Y,Be,Le,oe,ce,Ae,de,se,le,ae,te,U,E,Oe]),Ue=y(async()=>{if(fe.current||ke.current)return;if(!E)return;if(ke.current=!0,D.info({message:"Voice RTVI connecting",api_name:"voice-rtvi",conversation_id:E,assistant_starts:U}),U&&(he.current=!1,F(u(!0)),D.debug({message:"Voice greeting mute-hold armed",api_name:"voice-rtvi",conversation_id:E})),z(J())&&ye.current<d){ye.current+=1,D.debug({message:"Voice pre-connect token refresh (freshness gate)",api_name:"voice-rtvi",conversation_id:E,freshness_refresh_count:ye.current});const e=Ee.current;try{await Promise.resolve(W()?.())}catch{}if(e!==Ee.current)return}const e=++we.current,r=Ee.current;re.current=null,Ne.current=null,F(o(a.CONNECTING));const t=()=>r!==Ee.current||e!==we.current;try{const[{PipecatClient:r,LogLevel:n,RTVIMessage:i},c,o]=await Promise.all([import("@pipecat-ai/client-js"),X(E),P()]);if(t())return;Ve.current=i;const a=new r({transport:o,enableMic:!0,enableCam:!1,callbacks:Pe(e)});if(a.setLogLevel(n.WARN),t())return void a.disconnect().catch(()=>{});fe.current=a,D.debug({message:"Voice RTVI client constructed",api_name:"voice-rtvi",conversation_id:E,enable_mic:he.current}),Be(),be.current=setTimeout(()=>{D.warn({message:"Voice RTVI connect timed out (no bot-ready)",api_name:"voice-rtvi"}),H("Voice connection timed out. Please try again."),F(l()),je.current()},T),await a.connect({webrtcRequestParams:{endpoint:j.WEBRTC_OFFER(E),headers:new Headers(Q())},iceConfig:{iceServers:c}})}catch(t){const n=(e=>{const r="object"==typeof e&&null!==e&&"status"in e?e.status:void 0;return"number"==typeof r?r:void 0})(t);if(401===n&&e===we.current&&xe.current<d)return D.warn({message:"Voice RTVI offer refused with 401 — refreshing token",api_name:"voice-rtvi",conversation_id:E}),Me.current&&(clearTimeout(Me.current),Me.current=null),Ce.current=0,Le(!1),void Oe(r,"offer_401");D.error({message:"Voice RTVI connect failed",api_name:"voice-rtvi",status:n,error_message:t instanceof Error?t.message:String(t)});const i=(e=>void 0!==e&&e>=400&&e<500)(n),c=i?await(async(e,r)=>{const t=429===r?"Voice session limit reached. Please try again later.":m,n="object"==typeof e&&null!==e&&"cause"in e?e.cause:void 0;if(!(n instanceof Response))return t;try{const e=await n.json(),r=e?.data?.error_message??e?.data?.message;return"string"==typeof r?r:t}catch{return t}})(t,n):m;e===we.current&&(H(c),i&&(Me.current&&(clearTimeout(Me.current),Me.current=null),Ce.current=0,F(l())),Le(!1))}},[E,F,P,Pe,Be,Le,U,Oe]),Fe=y((e=!0)=>{D.info({message:"Voice RTVI disconnect (intentional teardown)",api_name:"voice-rtvi",conversation_id:E??void 0,commit_turn:e}),Ee.current+=1,we.current+=1,ye.current=0,Me.current&&(clearTimeout(Me.current),Me.current=null),Ce.current=0,xe.current=0,Ne.current=null,he.current=!0,F(R(!1));const r=fe.current;r?.disconnect().catch(e=>{D.warn({message:"Voice RTVI disconnect threw",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}),Le(e)},[Le,F,E]),Ge=y(e=>{he.current=e,fe.current&&(D.debug({message:"Voice mic enable toggled",api_name:"voice-rtvi",enabled:e}),fe.current.enableMic(e))},[]),qe=y(e=>{F(S(e)),fe.current?.updateMic(e)},[F]),We=y(e=>{F(w(e)),fe.current?.updateSpeaker(e)},[F]),He=y((e,r)=>{const t=fe.current;if(!t)return;const n=r?.modelContext;D.info({message:"Voice RTVI send-text",api_name:"voice-rtvi",conversation_id:E??void 0,run_immediately:!0,has_model_context:Boolean(n?.length)});try{const r=Ve.current;n?.length&&r?t.transport.sendMessage(new r("send-text",{content:e,options:{run_immediately:!0,audio_response:!0},model_context:n})):t.sendText(e,{run_immediately:!0,audio_response:!0})}catch(e){return void D.error({message:"Voice RTVI send-text failed",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}ne.current&&se(),te.current.setMessages(t=>[...t,v(e,void 0,r?.documents,r?.messageType)]),F(g(!0)),F(p(null)),ce(),F(i(c.PROCESSING))},[F,ce,se,ne,te,E]);M(()=>{je.current=Fe},[Fe]),M(()=>{De.current=Ue},[Ue]);const Ke=k(E),Ze=k(!1);M(()=>{const e=Ke.current;if(Ke.current=E,e===E)return;const r=ke.current;r&&(Ze.current=!0),r&&Fe(!1),Ze.current&&null!=E&&(Ze.current=!1,Ue())},[E,Fe,Ue]);const $e=k(ee);return M(()=>{const e=$e.current;$e.current=ee,e&&!ee&&ke.current&&Fe(!1)},[ee,Fe]),M(()=>()=>{je.current()},[]),{connect:Ue,disconnect:Fe,sendText:He,setMicEnabled:Ge,setInputDevice:qe,setOutputDevice:We}}(O,P,U,F);return E(G,{voiceBridge:P})};export{Y as useVoiceProviderValue};
|
package/dist/ws.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{A as e,u as t,g as n,s as r,a as s,b as i,c,V as o,d as a,e as u,f as l,n as d,o as h,v as p,p as m,q as g,B as f,k as S,l as v,h as y,i as k,m as _,t as T,w as P,x as w,y as E,z as x}from"./sessionHelpers-BIwU9Fpt.js";import{useRef as b,useEffect as A,useCallback as C}from"react";import{useDispatch as I,useStore as N,useSelector as M}from"react-redux";import{c as W,u as V,t as D,b as R}from"./browserAudioPlatform-BlQ_Ssdk.js";import{logger as O,VOICE_TRANSPORTS as L,API_ENDPOINTS as G,readAccessToken as B,getCustomHeaders as U,showErrorToast as F,INDICATOR_TIMER_ACTIONS as H,getTokenRefreshHandler as K,handleSharedEvent as j,isTokenAboutToExpire as J,readTokenExpiresAt as $}from"@1interface/shared-core";import{u as q,f as z}from"./useVoiceSessionCore-Cwkj3eVq.js";const Z="audio-recorder",Q=new Set([1006,1011,1012,1013,1014]),X="Token expired",Y="stop_playback",ee=`\nconst TARGET_RATE = ${e};\nconst CHUNK_SAMPLES = 2048;\n\nclass RecorderProcessor extends AudioWorkletProcessor {\n constructor() {\n super();\n this.pending = new Int16Array(CHUNK_SAMPLES);\n this.filled = 0;\n // Fractional read position carried across blocks so resampling has no seams.\n this.phase = 0;\n this.port.onmessage = (e) => {\n if (e.data && e.data.type === 'reset') this.filled = 0;\n };\n }\n\n resample(samples) {\n const step = sampleRate / TARGET_RATE;\n const out = [];\n let pos = this.phase;\n while (pos < samples.length) {\n const lo = Math.floor(pos);\n const frac = pos - lo;\n const hi = lo + 1 < samples.length ? samples[lo + 1] : samples[lo];\n out.push(samples[lo] * (1 - frac) + hi * frac);\n pos += step;\n }\n this.phase = pos - samples.length;\n return out;\n }\n\n process(inputs) {\n const channel = inputs[0] && inputs[0][0];\n if (!channel || channel.length === 0) return true;\n const frame = sampleRate === TARGET_RATE ? channel : this.resample(channel);\n for (let i = 0; i < frame.length; i++) {\n const s = Math.max(-1, Math.min(1, frame[i]));\n this.pending[this.filled++] = s < 0 ? s * 32768 : s * 32767;\n if (this.filled === CHUNK_SAMPLES) {\n const buffer = this.pending.buffer.slice(0);\n this.port.postMessage({ type: 'chunk', buffer }, [buffer]);\n this.filled = 0;\n }\n }\n return true;\n }\n}\n\nregisterProcessor('${Z}', RecorderProcessor);\n`;let te=null;const ne="reset";class re{ctx=null;stream=null;source=null;gain=null;worklet=null;visibilityHandler=null;opening=!1;cancelled=!1;opts=null;muted=!1;open(t){this.opening||this.ctx||(this.cancelled=!1,this.opening=!0,this.opts=t,this.muted=Boolean(t.initialMuted),t.onLoadingChange?.(!0),(async()=>{let n=null,r=null,s=null,i=null,c=null;try{try{n=new AudioContext({sampleRate:e})}catch{n=new AudioContext}if(this.cancelled)return void await n.close().catch(()=>{});if(await n.audioWorklet.addModule((te||(te=URL.createObjectURL(new Blob([ee],{type:"text/javascript"}))),te)),this.cancelled)return void await n.close().catch(()=>{});if(r=await navigator.mediaDevices.getUserMedia({audio:{...t.micDeviceId?{deviceId:{exact:t.micDeviceId}}:{},echoCancellation:!0,noiseSuppression:!0,autoGainControl:!1,channelCount:1,sampleRate:e,echoCancellationType:{ideal:"system"}}}),this.cancelled)return r.getTracks().forEach(e=>e.stop()),void await n.close().catch(()=>{});if(s=n.createMediaStreamSource(r),i=n.createGain(),i.gain.setValueAtTime(this.muted?0:1,n.currentTime),c=new AudioWorkletNode(n,Z),c.port.onmessage=e=>{if(this.cancelled)return;const t=e.data;"chunk"===t?.type&&t.buffer instanceof ArrayBuffer&&this.opts?.onChunk(t.buffer)},s.connect(i).connect(c).connect(n.destination),"suspended"===n.state&&await n.resume().catch(()=>{}),this.cancelled)return c.disconnect(),i.disconnect(),s.disconnect(),r.getTracks().forEach(e=>e.stop()),void await n.close().catch(()=>{});this.ctx=n,this.stream=r,this.source=s,this.gain=i,this.worklet=c;const o=()=>{"visible"===document.visibilityState&&"suspended"===n.state&&n.resume().catch(()=>{})};document.addEventListener("visibilitychange",o),this.visibilityHandler=o,this.opts?.onLoadingChange?.(!1)}catch(e){O.error({message:"Mic capture failed",api_name:"voice-mic",error_message:e instanceof Error?e.message:String(e)}),n?.close().catch(()=>{}),r?.getTracks().forEach(e=>e.stop()),c?.disconnect(),i?.disconnect(),s?.disconnect(),this.cancelled||(this.opts?.onError?.(e),this.opts?.onLoadingChange?.(!1))}finally{this.opening=!1}})())}setMuted(e){this.muted=e;const t=this.ctx,n=this.gain;t&&n&&(n.gain.setValueAtTime(e?0:1,t.currentTime),e&&this.worklet?.port.postMessage({type:ne}))}close(){this.cancelled=!0,this.worklet?.port.postMessage({type:ne}),this.worklet?.disconnect(),this.gain?.disconnect(),this.source?.disconnect(),this.stream?.getTracks().forEach(e=>e.stop()),this.visibilityHandler&&(document.removeEventListener("visibilitychange",this.visibilityHandler),this.visibilityHandler=null),this.ctx?.close().catch(()=>{}),this.ctx=null,this.stream=null,this.source=null,this.gain=null,this.worklet=null,this.opts=null}}function se(e){if(e.length<5)return.2;let t=0,n=0;for(let r=1;r<e.length;r++){const s=e[r]-e[r-1];t+=s,s>n&&(n=s)}return Math.min(.5,Math.max(.1,(n-t/(e.length-1))/1e3))}class ie{ctx=null;masterGain=null;sinkId=null;nextPlayTime=0;doneTimer=null;isFirstChunk=!0;prerollProvider=null;activeSources=new Set;setPrerollProvider(e){this.prerollProvider=e}ensureContext(){if(!this.ctx){try{this.ctx=new AudioContext({sampleRate:e})}catch{this.ctx=new AudioContext}this.masterGain=this.ctx.createGain(),this.masterGain.connect(this.ctx.destination),this.sinkId&&this.applySink(this.ctx,this.sinkId)}return this.ctx}applySink(e,t){"function"==typeof e.setSinkId&&e.setSinkId(t).catch(e=>{O.warn({message:"Voice speaker switch failed",api_name:"voice-audio",error_message:e instanceof Error?e.message:String(e)})})}play(t){const n=this.ensureContext();"suspended"===n.state&&n.resume().catch(()=>{});const r=new Int16Array(t),s=new Float32Array(r.length);for(let e=0;e<r.length;e++)s[e]=r[e]/32768;const i=n.createBuffer(1,s.length,e);i.getChannelData(0).set(s);const c=n.createBufferSource();c.buffer=i,c.connect(this.masterGain),this.activeSources.add(c),c.onended=()=>this.activeSources.delete(c);const o=n.currentTime;let a=Math.max(o,this.nextPlayTime);if(this.isFirstChunk){this.isFirstChunk=!1;const e=this.prerollProvider?this.prerollProvider():.2;a=Math.max(a,o+e)}c.start(a),this.nextPlayTime=a+i.duration}get remainingSeconds(){return this.ctx?Math.max(0,this.nextPlayTime-this.ctx.currentTime):0}onDrained(e){this.doneTimer&&clearTimeout(this.doneTimer);const t=this.remainingSeconds;t<=0?e():this.doneTimer=setTimeout(()=>{this.doneTimer=null,e()},1e3*t)}resetForNewSegment(){this.isFirstChunk=!0}setSinkId(e){this.sinkId=e||null,this.ctx&&this.applySink(this.ctx,this.sinkId??"")}stop(){this.doneTimer&&(clearTimeout(this.doneTimer),this.doneTimer=null);for(const e of this.activeSources)try{e.stop()}catch{}this.activeSources.clear(),this.nextPlayTime=0,this.isFirstChunk=!0}destroy(){this.stop(),this.ctx&&(this.ctx.close().catch(()=>{}),this.ctx=null,this.masterGain=null)}}class ce{audio=null;timer=null;pendingPlay=null;pendingAction=null;pendingStartSeconds=0;ensureAudio(){if(!this.audio){const e=D();this.audio=e?.audio??W(),e?.settled&&this.trackUnlock(e.settled)}return this.audio}trackUnlock(e){this.pendingPlay=e,e.then(()=>{this.pendingPlay=null;const e=this.pendingAction;this.pendingAction=null,"start"===e&&this.schedule(this.pendingStartSeconds)})}unlock(){const e=this.ensureAudio();if(this.pendingPlay||!e.paused)return;const t=V(e);t&&this.trackUnlock(t)}start(e){if(this.ensureAudio(),this.pendingPlay)return this.pendingAction="start",void(this.pendingStartSeconds=e);this.timer||this.audio&&!this.audio.paused||(this.stop(),this.schedule(e))}pause(){this.clearTimer(),this.pendingPlay?this.pendingAction="pause":this.audio&&!this.audio.paused&&this.audio.pause()}resume(e=0){this.audio&&this.schedule(e)}stop(){this.clearTimer(),this.pendingPlay?this.pendingAction="stop":this.audio&&(this.audio.pause(),this.audio.currentTime=0)}destroy(){this.stop(),this.pendingAction=null,this.audio&&(this.audio.removeAttribute("src"),this.audio=null)}clearTimer(){this.timer&&(clearTimeout(this.timer),this.timer=null)}schedule(e){this.clearTimer();const t=Math.round(1e3*e)+300;this.timer=setTimeout(()=>{this.timer=null,this.doPlay()},t)}doPlay(){this.pendingAction=null;const e=this.audio?.play();e instanceof Promise?(this.pendingPlay=e,e.then(()=>{this.pendingPlay=null;const e=this.pendingAction;this.pendingAction=null,"pause"===e?this.audio?.pause():"stop"===e&&this.audio&&(this.audio.pause(),this.audio.currentTime=0)}).catch(()=>{this.pendingPlay=null,this.pendingAction=null})):this.pendingPlay=null}}const oe={...R,createPcmCapture:()=>new re,createPcmPlayer:()=>new ie,createThinkingSound:()=>new ce},ae="voice-ws",ue=1e3;const le=({conversationId:e,voiceBridge:W,transport:V,assistantStartsConversation:D})=>{const Z=function(e,x,W,V){const D=I(),Z=N(),ee=(e=>e===R?oe:e)(t()),te=b(ee);A(()=>{te.current=ee});const ne=M(e=>e.auth.accessToken),re=b(!1),ie=b(()=>{}),{bridgeRef:ce,botTurnMsgIdRef:le,liveSegmentsRef:de,startResponseTimer:he,clearIndicator:pe,sharedDeps:me,commitBotTurn:ge,discardBotTurn:fe,openBotTurnIfNeeded:Se,ingestBotSegment:ve,routeServerMessage:ye}=q(e,x,{apiName:ae,onClearStatus:()=>ie.current()}),ke=b(null),_e=b(null),Te=b(null),Pe=b(null),we=b(0),Ee=b(0),xe=b(!1),be=b(!0),Ae=b(null),Ce=b(null),Ie=b(null),Ne=b(null),Me=b(0),We=b(0),Ve=b(0),De=b(null),Re=b(0),Oe=b(null),Le=b(null),Ge=b(0),Be=b(!1),Ue=b(null),Fe=b(null),He=b(!1),Ke=b([]),je=b(null),Je=b(!1),$e=b(null),qe=b(!1),ze=e=>{e.current&&(clearTimeout(e.current),e.current=null)},Ze=()=>{He.current=!0,Fe.current&&(Fe.current.userSpokeSince=!0)},Qe=C(()=>{const e=le.current;e&&(Fe.current={id:e,at:Date.now(),userSpokeSince:He.current}),ge()},[le,ge]),Xe=C(()=>{ze($e),Z.getState().voice.serverMuted&&D(n(!1))},[D,Z]),Ye=C(async t=>{try{const e=await navigator.mediaDevices.enumerateDevices();if(t!==we.current)return;const n=e=>({deviceId:e.deviceId,label:e.label});D(r(e.filter(e=>"audioinput"===e.kind).map(n))),D(s(e.filter(e=>"audiooutput"===e.kind).map(n)))}catch(t){O.warn({message:"Voice device enumeration failed",api_name:ae,conversation_id:e??void 0,error_message:t instanceof Error?t.message:String(t)})}},[D,e]),et=C(()=>{_e.current?.close(),_e.current=null},[]),tt=C(t=>{et();const n=te.current.createPcmCapture;if(!n)return;const r=n.call(te.current);_e.current=r,r.open({micDeviceId:Z.getState().voice.selectedMicId||null,initialMuted:!be.current,onChunk:e=>{const n=ke.current;t===we.current&&n&&n.readyState===WebSocket.OPEN&&n.send(e)},onError:n=>{t===we.current&&(O.error({message:"Voice WS mic capture failed",api_name:ae,conversation_id:e??void 0,error_message:n instanceof Error?n.message:String(n)}),F("Microphone could not be started. Please check your input device."))}})},[et,Z,e]),nt=C(()=>{ze(Le),Ge.current+=1,Be.current=!0,Te.current?.stop()},[]),rt=C(()=>{Pe.current?.stop()},[]);A(()=>{ie.current=()=>{re.current=!1,Ue.current=null,rt(),pe(H.KEEP)}},[rt,pe]);const st=C(e=>{const t=Te.current,n=++Ge.current,r=()=>{e===we.current&&n===Ge.current&&(ze(Le),Z.getState().voice.pipelineStatus===o.SPEAKING&&D(c(o.IDLE)),re.current&&(D(y(!0)),D(k(Ue.current)),Pe.current?.resume()),qe.current||(qe.current=!0,V&&Xe()))};t?(ze(Le),Le.current=setTimeout(r,1e3*t.remainingSeconds+5e3),t.onDrained(r)):r()},[D,Z,V,Xe]),it=C(()=>ze(Ae),[]),ct=C(()=>{Ce.current&&(clearInterval(Ce.current),Ce.current=null)},[]),ot=C(e=>{it(),ct(),ze(Oe),ze($e),nt(),rt(),et(),ke.current=null,xe.current=!1,re.current=!1,Ue.current=null,Fe.current=null,He.current=!1,Re.current=0,Ke.current=[],Be.current=!1,D(r([])),D(s([])),D(i(!1)),D(c(o.IDLE)),D(a(u.DISCONNECTED)),D(l(null)),D(n(!1)),e?ge():fe(),pe(H.CANCEL)},[D,it,ct,nt,rt,et,ge,fe,pe]),at=b(async()=>{}),ut=b(()=>{}),lt=C((t,n,r,s)=>{if(t!==we.current)return;const i=De.current;De.current=null,O.info({message:"Voice WS closed",api_name:ae,conversation_id:e??void 0,code:n,reason:r||void 0,socket_opened:s,drop_outcome:i??"transient"}),we.current+=1,ot(!0);const c=Ee.current;if(1008===n||"Idle timeout"===r||"terminal"===i)return void D(d());const o=We.current<h&&null!=e;if((!s||"auth"===i||r===X)&&(o||"auth"===i||r===X))return o?(We.current+=1,void(async()=>{let t=!1;try{t=!!await(K()?.())}catch{}c===Ee.current&&(t?at.current():(O.warn({message:"Voice WS token refresh failed — no reconnect",api_name:ae,conversation_id:e??void 0}),F(p),D(d())))})()):(O.warn({message:"Voice WS auth refresh budget exhausted — giving up",api_name:ae,conversation_id:e??void 0}),F(p),void D(d()));if("retry"!==i&&s&&!Q.has(n)||null==e)D(d());else{if(Me.current>=m)return O.warn({message:"Voice WS auto-reconnect cap reached — giving up",api_name:ae,conversation_id:e??void 0,reconnect_attempts:Me.current}),F("Voice connection lost. Please try again."),void D(d());Me.current+=1,O.info({message:"Voice WS scheduling auto-reconnect",api_name:ae,conversation_id:e??void 0,reconnect_attempts:Me.current,delay_ms:g}),ze(Ne),Ne.current=setTimeout(()=>{Ne.current=null,at.current()},g)}},[D,ot,e]),dt=C((e,t)=>{if(e!==we.current||Be.current)return;const n=Ke.current;n.push(Date.now()),n.length>30&&n.shift(),Te.current?.play(t)},[]),ht=C((t,n)=>{if(t!==we.current)return;const r=n.data??{};switch(n.type){case"tts_start":Re.current=Date.now(),ze(Le),Ge.current+=1,He.current=!1,Be.current=!1,Ke.current=[],Pe.current?.pause(),Te.current?.resetForNewSegment(),Se(),D(c(o.SPEAKING)),pe(re.current?H.KEEP:H.FINALIZE),$e.current&&(ze($e),$e.current=setTimeout(()=>{$e.current=null,t===we.current&&Xe()},6e4));break;case"text_delta":{const e="string"==typeof r.token?r.token:"";if(!e)break;Se(),ve(z(de.current,e));break}case"tts_end":{const n=!0===r.cancelled;if(O.debug({message:"Voice WS tts_end",api_name:ae,conversation_id:e??void 0,cancelled:n}),je.current=se(Ke.current),Qe(),n){nt(),Z.getState().voice.pipelineStatus===o.SPEAKING&&D(c(o.IDLE));break}st(t);break}case"stt_result":{const e="string"==typeof r.text?r.text.trim():"";if(!e)break;D(l(null));const t=_(e),n=Fe.current,s=n&&!n.userSpokeSince&&Date.now()-n.at<=5e3?n.id:null;ce.current.setMessages(e=>{const n=s?e.findIndex(e=>e.id===s):-1;return-1===n?[...e,t]:[...e.slice(0,n),t,...e.slice(n)]});break}case"speech_started":{if(Date.now()-Re.current<500)break;const e=ke.current;e&&e.readyState===WebSocket.OPEN&&e.send(JSON.stringify({type:Y})),ze(Le),Ge.current+=1,ze(Oe),Oe.current=setTimeout(()=>{Oe.current=null,t===we.current&&nt()},400),Qe(),Ze(),Pe.current?.stop(),D(l(null)),D(c(o.LISTENING)),re.current||pe(H.KEEP);break}case"speech_stopped":if(Ze(),D(c(o.PROCESSING)),re.current)break;D(y(!0)),D(k(null)),he();break;case"error":{const t="string"==typeof r.message?r.message:"Voice error";if(j({kind:"error",error:t},me),ie.current(),D(c(o.IDLE)),pe(H.CANCEL),S.some(e=>e.test(t)))De.current="auth";else{if(!v.some(e=>e.test(t)))break;De.current="terminal"}O.warn({message:"Voice WS fatal error — closing socket",api_name:ae,conversation_id:e??void 0,drop_outcome:De.current}),ke.current?.close(ue);break}case"pong":Ie.current=null,Me.current=0,We.current=0,Ve.current=0;break;case f.STATUS_UPDATE:re.current=!0,Ue.current="string"==typeof r.text?r.text:null,ye(n),Pe.current?.start(Te.current?.remainingSeconds??0);break;default:ye(n)}},[D,Z,e,Se,pe,ve,de,Qe,nt,st,ce,he,me,ye,Xe]),pt=C((t,n)=>{ct(),Ie.current=null,Ce.current=setInterval(()=>{if(t!==we.current||n.readyState!==WebSocket.OPEN)return void ct();const r=Ie.current;if(null!==r&&Date.now()-r>3e4)return O.warn({message:"Voice WS pong watchdog tripped — forcing reconnect",api_name:ae,conversation_id:e??void 0}),ct(),De.current="retry",n.close(ue),void lt(t,ue,"watchdog",!0);null===r&&(Ie.current=Date.now()),n.send(JSON.stringify({type:"ping"}))},2e4)},[ct,lt,e]),mt=C(()=>{const e=Pe.current??te.current.createThinkingSound?.()??null;Pe.current=e,e?.unlock()},[]),gt=C(async()=>{if(ke.current||xe.current)return;if(!e)return;const t=te.current;if(!t.createPcmCapture||!t.createPcmPlayer)return O.error({message:"Voice WS transport unsupported on this platform (no PCM seams)",api_name:ae,conversation_id:e,transport:W}),F(p),void D(d());if(xe.current=!0,O.info({message:"Voice WS connecting",api_name:ae,conversation_id:e,transport:W,assistant_starts:V}),J($())&&Ve.current<h){Ve.current+=1;const e=Ee.current;try{await Promise.resolve(K()?.())}catch{}if(e!==Ee.current)return}const r=++we.current;let s;De.current=null,qe.current=!1,D(a(u.CONNECTING));try{s=new WebSocket(function(e,t){const n=t===L.PIPECAT_WS?G.CONVERSATION_PIPECAT_WS(e):G.CONVERSATION_STREAM(e),r="undefined"!=typeof window?window.location?.origin:void 0,s=new URL(n,r);s.protocol="https:"===s.protocol?"wss:":"ws:";const i=B();i&&s.searchParams.append("access_token",i);const c=function(){for(const[e,t]of Object.entries(U()))if("x-client-id"===e.toLowerCase()&&t)return t;return null}();return c&&s.searchParams.append("client_id",c),s.toString()}(e,W))}catch(t){return O.error({message:"Voice WS could not open socket",api_name:ae,conversation_id:e,error_message:t instanceof Error?t.message:String(t)}),F(p),D(d()),void ot(!1)}s.binaryType="arraybuffer",ke.current=s,mt();const l=Te.current??t.createPcmPlayer();Te.current=l,l.setPrerollProvider(()=>je.current??.2),l.setSinkId(Z.getState().voice.selectedSpeakerId||null);let m=!1,g=!1;it(),Ae.current=setTimeout(()=>{r===we.current&&(O.warn({message:"Voice WS connect timed out",api_name:ae,conversation_id:e}),F("Voice connection timed out. Please try again."),D(d()),ut.current())},T),s.onopen=()=>{if(r!==we.current)return;m=!0,it(),O.info({message:"Voice WS connected",api_name:ae,conversation_id:e,transport:W}),D(a(u.CONNECTED)),D(i(!0)),D(c(o.IDLE)),Ke.current=[];const t=V&&!Je.current;t&&(be.current=!1),tt(r),Ye(r),pt(r,s),t&&(Je.current=!0,s.send(JSON.stringify({type:"start_conversation"})),D(n(!0)),_e.current?.setMuted(!0),D(y(!0)),he(),ze($e),$e.current=setTimeout(()=>{$e.current=null,r===we.current&&(O.warn({message:"Voice WS greeting hold released by safety timer",api_name:ae,conversation_id:e}),Xe())},1e4))},s.onmessage=e=>{if(r!==we.current)return;if(e.data instanceof ArrayBuffer)return void dt(r,e.data);if("string"!=typeof e.data)return;let t=null;try{t=JSON.parse(e.data)}catch{return}t&&"string"==typeof t.type&&ht(r,t)},s.onerror=()=>{r===we.current&&O.warn({message:"Voice WS socket error",api_name:ae,conversation_id:e,socket_opened:m})},s.onclose=e=>{g||(g=!0,lt(r,e.code,e.reason,m))}},[e,W,Z,D,V,ot,it,mt,tt,Ye,pt,he,Xe,dt,ht,lt]),ft=C((t=!0)=>{O.info({message:"Voice WS disconnect (intentional teardown)",api_name:ae,conversation_id:e??void 0,commit_turn:t}),Ee.current+=1,we.current+=1,Ve.current=0,ze(Ne),Me.current=0,We.current=0,De.current=null,Je.current=!1,be.current=!0,D(P(!1));const n=ke.current;!n||n.readyState!==WebSocket.CONNECTING&&n.readyState!==WebSocket.OPEN||n.close(ue),ot(t)},[D,ot,e]),St=C(e=>{be.current=e,_e.current?.setMuted(!e)},[]),vt=C(e=>{D(w(e)),ke.current&&_e.current&&tt(we.current)},[D,tt]),yt=C(e=>{D(E(e)),Te.current?.setSinkId(e||null)},[D]),kt=C((t,n)=>{const r=ke.current;if(!r||r.readyState!==WebSocket.OPEN)return;const s=n?.modelContext;O.info({message:"Voice WS text_message",api_name:ae,conversation_id:e??void 0,has_model_context:Boolean(s?.length)}),r.send(JSON.stringify({type:Y})),nt(),rt(),le.current&&Qe(),Ze(),r.send(JSON.stringify({type:"text_message",text:t,...s?.length?{model_context:s}:{}})),ce.current.setMessages(e=>[...e,_(t,void 0,n?.documents,n?.messageType)]),D(y(!0)),D(k(null)),he(),D(c(o.PROCESSING))},[D,he,Qe,nt,rt,le,ce,e]);A(()=>{ut.current=ft},[ft]),A(()=>{at.current=gt},[gt]);const _t=b(e),Tt=b(!1);A(()=>{const t=_t.current;if(_t.current=e,t===e)return;const n=xe.current;n&&(Tt.current=!0),n&&ft(!1),Tt.current&&null!=e&&(Tt.current=!1,gt())},[e,ft,gt]);const Pt=b(ne);return A(()=>{const e=Pt.current;Pt.current=ne,e&&!ne&&xe.current&&ft(!1)},[ne,ft]),A(()=>()=>{ut.current(),Te.current?.destroy(),Te.current=null,Pe.current?.destroy(),Pe.current=null},[]),{connect:gt,disconnect:ft,sendText:kt,setMicEnabled:St,setInputDevice:vt,setOutputDevice:yt,primeAudio:mt}}(e,W,V,D);return x(Z,{voiceBridge:W})};export{re as BrowserPcmCapture,ie as BrowserPcmPlayer,ce as BrowserThinkingSound,se as computeAdaptivePreroll,le as useVoiceWsProviderValue};
|
|
1
|
+
import{A as e,u as t,g as n,s as r,a as s,b as i,c,V as o,d as a,e as u,f as l,j as d,h,i as p,q as m,r as g,B as f,o as S,p as v,k as y,l as k,m as _,v as T,w as P,x as w,y as E,z as x}from"./sessionHelpers-CK66mJIw.js";import{useRef as b,useEffect as A,useCallback as C}from"react";import{useDispatch as I,useStore as N,useSelector as M}from"react-redux";import{c as W,u as V,t as D,b as R}from"./browserAudioPlatform-BanqRaQm.js";import{logger as O,VOICE_TRANSPORTS as L,API_ENDPOINTS as G,readAccessToken as B,getCustomHeaders as U,showErrorToast as F,INDICATOR_TIMER_ACTIONS as H,getTokenRefreshHandler as K,handleSharedEvent as j,isTokenAboutToExpire as J,readTokenExpiresAt as $}from"@1interface/shared-core";import{u as q,f as z}from"./useVoiceSessionCore-C9xo14Pi.js";const Z="audio-recorder",Q=new Set([1006,1011,1012,1013,1014]),X="Token expired",Y="stop_playback",ee=`\nconst TARGET_RATE = ${e};\nconst CHUNK_SAMPLES = 2048;\n\nclass RecorderProcessor extends AudioWorkletProcessor {\n constructor() {\n super();\n this.pending = new Int16Array(CHUNK_SAMPLES);\n this.filled = 0;\n // Fractional read position carried across blocks so resampling has no seams.\n this.phase = 0;\n this.port.onmessage = (e) => {\n if (e.data && e.data.type === 'reset') this.filled = 0;\n };\n }\n\n resample(samples) {\n const step = sampleRate / TARGET_RATE;\n const out = [];\n let pos = this.phase;\n while (pos < samples.length) {\n const lo = Math.floor(pos);\n const frac = pos - lo;\n const hi = lo + 1 < samples.length ? samples[lo + 1] : samples[lo];\n out.push(samples[lo] * (1 - frac) + hi * frac);\n pos += step;\n }\n this.phase = pos - samples.length;\n return out;\n }\n\n process(inputs) {\n const channel = inputs[0] && inputs[0][0];\n if (!channel || channel.length === 0) return true;\n const frame = sampleRate === TARGET_RATE ? channel : this.resample(channel);\n for (let i = 0; i < frame.length; i++) {\n const s = Math.max(-1, Math.min(1, frame[i]));\n this.pending[this.filled++] = s < 0 ? s * 32768 : s * 32767;\n if (this.filled === CHUNK_SAMPLES) {\n const buffer = this.pending.buffer.slice(0);\n this.port.postMessage({ type: 'chunk', buffer }, [buffer]);\n this.filled = 0;\n }\n }\n return true;\n }\n}\n\nregisterProcessor('${Z}', RecorderProcessor);\n`;let te=null;const ne="reset";class re{ctx=null;stream=null;source=null;gain=null;worklet=null;visibilityHandler=null;opening=!1;cancelled=!1;opts=null;muted=!1;open(t){this.opening||this.ctx||(this.cancelled=!1,this.opening=!0,this.opts=t,this.muted=Boolean(t.initialMuted),t.onLoadingChange?.(!0),(async()=>{let n=null,r=null,s=null,i=null,c=null;try{try{n=new AudioContext({sampleRate:e})}catch{n=new AudioContext}if(this.cancelled)return void await n.close().catch(()=>{});if(await n.audioWorklet.addModule((te||(te=URL.createObjectURL(new Blob([ee],{type:"text/javascript"}))),te)),this.cancelled)return void await n.close().catch(()=>{});if(r=await navigator.mediaDevices.getUserMedia({audio:{...t.micDeviceId?{deviceId:{exact:t.micDeviceId}}:{},echoCancellation:!0,noiseSuppression:!0,autoGainControl:!1,channelCount:1,sampleRate:e,echoCancellationType:{ideal:"system"}}}),this.cancelled)return r.getTracks().forEach(e=>e.stop()),void await n.close().catch(()=>{});if(s=n.createMediaStreamSource(r),i=n.createGain(),i.gain.setValueAtTime(this.muted?0:1,n.currentTime),c=new AudioWorkletNode(n,Z),c.port.onmessage=e=>{if(this.cancelled)return;const t=e.data;"chunk"===t?.type&&t.buffer instanceof ArrayBuffer&&this.opts?.onChunk(t.buffer)},s.connect(i).connect(c).connect(n.destination),"suspended"===n.state&&await n.resume().catch(()=>{}),this.cancelled)return c.disconnect(),i.disconnect(),s.disconnect(),r.getTracks().forEach(e=>e.stop()),void await n.close().catch(()=>{});this.ctx=n,this.stream=r,this.source=s,this.gain=i,this.worklet=c;const o=()=>{"visible"===document.visibilityState&&"suspended"===n.state&&n.resume().catch(()=>{})};document.addEventListener("visibilitychange",o),this.visibilityHandler=o,this.opts?.onLoadingChange?.(!1)}catch(e){O.error({message:"Mic capture failed",api_name:"voice-mic",error_message:e instanceof Error?e.message:String(e)}),n?.close().catch(()=>{}),r?.getTracks().forEach(e=>e.stop()),c?.disconnect(),i?.disconnect(),s?.disconnect(),this.cancelled||(this.opts?.onError?.(e),this.opts?.onLoadingChange?.(!1))}finally{this.opening=!1}})())}setMuted(e){this.muted=e;const t=this.ctx,n=this.gain;t&&n&&(n.gain.setValueAtTime(e?0:1,t.currentTime),e&&this.worklet?.port.postMessage({type:ne}))}close(){this.cancelled=!0,this.worklet?.port.postMessage({type:ne}),this.worklet?.disconnect(),this.gain?.disconnect(),this.source?.disconnect(),this.stream?.getTracks().forEach(e=>e.stop()),this.visibilityHandler&&(document.removeEventListener("visibilitychange",this.visibilityHandler),this.visibilityHandler=null),this.ctx?.close().catch(()=>{}),this.ctx=null,this.stream=null,this.source=null,this.gain=null,this.worklet=null,this.opts=null}}function se(e){if(e.length<5)return.2;let t=0,n=0;for(let r=1;r<e.length;r++){const s=e[r]-e[r-1];t+=s,s>n&&(n=s)}return Math.min(.5,Math.max(.1,(n-t/(e.length-1))/1e3))}class ie{ctx=null;masterGain=null;sinkId=null;nextPlayTime=0;doneTimer=null;isFirstChunk=!0;prerollProvider=null;activeSources=new Set;setPrerollProvider(e){this.prerollProvider=e}ensureContext(){if(!this.ctx){try{this.ctx=new AudioContext({sampleRate:e})}catch{this.ctx=new AudioContext}this.masterGain=this.ctx.createGain(),this.masterGain.connect(this.ctx.destination),this.sinkId&&this.applySink(this.ctx,this.sinkId)}return this.ctx}applySink(e,t){"function"==typeof e.setSinkId&&e.setSinkId(t).catch(e=>{O.warn({message:"Voice speaker switch failed",api_name:"voice-audio",error_message:e instanceof Error?e.message:String(e)})})}play(t){const n=this.ensureContext();"suspended"===n.state&&n.resume().catch(()=>{});const r=new Int16Array(t),s=new Float32Array(r.length);for(let e=0;e<r.length;e++)s[e]=r[e]/32768;const i=n.createBuffer(1,s.length,e);i.getChannelData(0).set(s);const c=n.createBufferSource();c.buffer=i,c.connect(this.masterGain),this.activeSources.add(c),c.onended=()=>this.activeSources.delete(c);const o=n.currentTime;let a=Math.max(o,this.nextPlayTime);if(this.isFirstChunk){this.isFirstChunk=!1;const e=this.prerollProvider?this.prerollProvider():.2;a=Math.max(a,o+e)}c.start(a),this.nextPlayTime=a+i.duration}get remainingSeconds(){return this.ctx?Math.max(0,this.nextPlayTime-this.ctx.currentTime):0}onDrained(e){this.doneTimer&&clearTimeout(this.doneTimer);const t=this.remainingSeconds;t<=0?e():this.doneTimer=setTimeout(()=>{this.doneTimer=null,e()},1e3*t)}resetForNewSegment(){this.isFirstChunk=!0}setSinkId(e){this.sinkId=e||null,this.ctx&&this.applySink(this.ctx,this.sinkId??"")}stop(){this.doneTimer&&(clearTimeout(this.doneTimer),this.doneTimer=null);for(const e of this.activeSources)try{e.stop()}catch{}this.activeSources.clear(),this.nextPlayTime=0,this.isFirstChunk=!0}destroy(){this.stop(),this.ctx&&(this.ctx.close().catch(()=>{}),this.ctx=null,this.masterGain=null)}}class ce{audio=null;timer=null;pendingPlay=null;pendingAction=null;pendingStartSeconds=0;ensureAudio(){if(!this.audio){const e=D();this.audio=e?.audio??W(),e?.settled&&this.trackUnlock(e.settled)}return this.audio}trackUnlock(e){this.pendingPlay=e,e.then(()=>{this.pendingPlay=null;const e=this.pendingAction;this.pendingAction=null,"start"===e&&this.schedule(this.pendingStartSeconds)})}unlock(){const e=this.ensureAudio();if(this.pendingPlay||!e.paused)return;const t=V(e);t&&this.trackUnlock(t)}start(e){if(this.ensureAudio(),this.pendingPlay)return this.pendingAction="start",void(this.pendingStartSeconds=e);this.timer||this.audio&&!this.audio.paused||(this.stop(),this.schedule(e))}pause(){this.clearTimer(),this.pendingPlay?this.pendingAction="pause":this.audio&&!this.audio.paused&&this.audio.pause()}resume(e=0){this.audio&&this.schedule(e)}stop(){this.clearTimer(),this.pendingPlay?this.pendingAction="stop":this.audio&&(this.audio.pause(),this.audio.currentTime=0)}destroy(){this.stop(),this.pendingAction=null,this.audio&&(this.audio.removeAttribute("src"),this.audio=null)}clearTimer(){this.timer&&(clearTimeout(this.timer),this.timer=null)}schedule(e){this.clearTimer();const t=Math.round(1e3*e)+300;this.timer=setTimeout(()=>{this.timer=null,this.doPlay()},t)}doPlay(){this.pendingAction=null;const e=this.audio?.play();e instanceof Promise?(this.pendingPlay=e,e.then(()=>{this.pendingPlay=null;const e=this.pendingAction;this.pendingAction=null,"pause"===e?this.audio?.pause():"stop"===e&&this.audio&&(this.audio.pause(),this.audio.currentTime=0)}).catch(()=>{this.pendingPlay=null,this.pendingAction=null})):this.pendingPlay=null}}const oe={...R,createPcmCapture:()=>new re,createPcmPlayer:()=>new ie,createThinkingSound:()=>new ce},ae="voice-ws",ue=1e3;const le=({conversationId:e,voiceBridge:W,transport:V,assistantStartsConversation:D})=>{const Z=function(e,x,W,V){const D=I(),Z=N(),ee=(e=>e===R?oe:e)(t()),te=b(ee);A(()=>{te.current=ee});const ne=M(e=>e.auth.accessToken),re=b(!1),ie=b(()=>{}),{bridgeRef:ce,botTurnMsgIdRef:le,liveSegmentsRef:de,startResponseTimer:he,clearIndicator:pe,sharedDeps:me,commitBotTurn:ge,discardBotTurn:fe,openBotTurnIfNeeded:Se,ingestBotSegment:ve,routeServerMessage:ye}=q(e,x,{apiName:ae,onClearStatus:()=>ie.current()}),ke=b(null),_e=b(null),Te=b(null),Pe=b(null),we=b(0),Ee=b(0),xe=b(!1),be=b(!0),Ae=b(null),Ce=b(null),Ie=b(null),Ne=b(null),Me=b(0),We=b(0),Ve=b(0),De=b(null),Re=b(0),Oe=b(null),Le=b(null),Ge=b(0),Be=b(!1),Ue=b(null),Fe=b(null),He=b(!1),Ke=b([]),je=b(null),Je=b(!1),$e=b(null),qe=b(!1),ze=e=>{e.current&&(clearTimeout(e.current),e.current=null)},Ze=()=>{He.current=!0,Fe.current&&(Fe.current.userSpokeSince=!0)},Qe=C(()=>{const e=le.current;e&&(Fe.current={id:e,at:Date.now(),userSpokeSince:He.current}),ge()},[le,ge]),Xe=C(()=>{ze($e),Z.getState().voice.serverMuted&&D(n(!1))},[D,Z]),Ye=C(async t=>{try{const e=await navigator.mediaDevices.enumerateDevices();if(t!==we.current)return;const n=e=>({deviceId:e.deviceId,label:e.label});D(r(e.filter(e=>"audioinput"===e.kind).map(n))),D(s(e.filter(e=>"audiooutput"===e.kind).map(n)))}catch(t){O.warn({message:"Voice device enumeration failed",api_name:ae,conversation_id:e??void 0,error_message:t instanceof Error?t.message:String(t)})}},[D,e]),et=C(()=>{_e.current?.close(),_e.current=null},[]),tt=C(t=>{et();const n=te.current.createPcmCapture;if(!n)return;const r=n.call(te.current);_e.current=r,r.open({micDeviceId:Z.getState().voice.selectedMicId||null,initialMuted:!be.current,onChunk:e=>{const n=ke.current;t===we.current&&n&&n.readyState===WebSocket.OPEN&&n.send(e)},onError:n=>{t===we.current&&(O.error({message:"Voice WS mic capture failed",api_name:ae,conversation_id:e??void 0,error_message:n instanceof Error?n.message:String(n)}),F("Microphone could not be started. Please check your input device."))}})},[et,Z,e]),nt=C(()=>{ze(Le),Ge.current+=1,Be.current=!0,Te.current?.stop()},[]),rt=C(()=>{Pe.current?.stop()},[]);A(()=>{ie.current=()=>{re.current=!1,Ue.current=null,rt(),pe(H.KEEP)}},[rt,pe]);const st=C(e=>{const t=Te.current,n=++Ge.current,r=()=>{e===we.current&&n===Ge.current&&(ze(Le),Z.getState().voice.pipelineStatus===o.SPEAKING&&D(c(o.IDLE)),re.current&&(D(y(!0)),D(k(Ue.current)),Pe.current?.resume()),qe.current||(qe.current=!0,V&&Xe()))};t?(ze(Le),Le.current=setTimeout(r,1e3*t.remainingSeconds+5e3),t.onDrained(r)):r()},[D,Z,V,Xe]),it=C(()=>ze(Ae),[]),ct=C(()=>{Ce.current&&(clearInterval(Ce.current),Ce.current=null)},[]),ot=C(e=>{it(),ct(),ze(Oe),ze($e),nt(),rt(),et(),ke.current=null,xe.current=!1,re.current=!1,Ue.current=null,Fe.current=null,He.current=!1,Re.current=0,Ke.current=[],Be.current=!1,D(r([])),D(s([])),D(i(!1)),D(c(o.IDLE)),D(a(u.DISCONNECTED)),D(l(null)),D(n(!1)),e?ge():fe(),pe(H.CANCEL)},[D,it,ct,nt,rt,et,ge,fe,pe]),at=b(async()=>{}),ut=b(()=>{}),lt=C((t,n,r,s)=>{if(t!==we.current)return;const i=De.current;De.current=null,O.info({message:"Voice WS closed",api_name:ae,conversation_id:e??void 0,code:n,reason:r||void 0,socket_opened:s,drop_outcome:i??"transient"}),we.current+=1,ot(!0);const c=Ee.current;if(1008===n||"Idle timeout"===r||"terminal"===i)return void D(d());const o=We.current<h&&null!=e;if((!s||"auth"===i||r===X)&&(o||"auth"===i||r===X))return o?(We.current+=1,void(async()=>{let t=!1;try{t=!!await(K()?.())}catch{}c===Ee.current&&(t?at.current():(O.warn({message:"Voice WS token refresh failed — no reconnect",api_name:ae,conversation_id:e??void 0}),F(p),D(d())))})()):(O.warn({message:"Voice WS auth refresh budget exhausted — giving up",api_name:ae,conversation_id:e??void 0}),F(p),void D(d()));if("retry"!==i&&s&&!Q.has(n)||null==e)D(d());else{if(Me.current>=m)return O.warn({message:"Voice WS auto-reconnect cap reached — giving up",api_name:ae,conversation_id:e??void 0,reconnect_attempts:Me.current}),F("Voice connection lost. Please try again."),void D(d());Me.current+=1,O.info({message:"Voice WS scheduling auto-reconnect",api_name:ae,conversation_id:e??void 0,reconnect_attempts:Me.current,delay_ms:g}),ze(Ne),Ne.current=setTimeout(()=>{Ne.current=null,at.current()},g)}},[D,ot,e]),dt=C((e,t)=>{if(e!==we.current||Be.current)return;const n=Ke.current;n.push(Date.now()),n.length>30&&n.shift(),Te.current?.play(t)},[]),ht=C((t,n)=>{if(t!==we.current)return;const r=n.data??{};switch(n.type){case"tts_start":Re.current=Date.now(),ze(Le),Ge.current+=1,He.current=!1,Be.current=!1,Ke.current=[],Pe.current?.pause(),Te.current?.resetForNewSegment(),Se(),D(c(o.SPEAKING)),pe(re.current?H.KEEP:H.FINALIZE),$e.current&&(ze($e),$e.current=setTimeout(()=>{$e.current=null,t===we.current&&Xe()},6e4));break;case"text_delta":{const e="string"==typeof r.token?r.token:"";if(!e)break;Se(),ve(z(de.current,e));break}case"tts_end":{const n=!0===r.cancelled;if(O.debug({message:"Voice WS tts_end",api_name:ae,conversation_id:e??void 0,cancelled:n}),je.current=se(Ke.current),Qe(),n){nt(),Z.getState().voice.pipelineStatus===o.SPEAKING&&D(c(o.IDLE));break}st(t);break}case"stt_result":{const e="string"==typeof r.text?r.text.trim():"";if(!e)break;D(l(null));const t=_(e),n=Fe.current,s=n&&!n.userSpokeSince&&Date.now()-n.at<=5e3?n.id:null;ce.current.setMessages(e=>{const n=s?e.findIndex(e=>e.id===s):-1;return-1===n?[...e,t]:[...e.slice(0,n),t,...e.slice(n)]});break}case"speech_started":{if(Date.now()-Re.current<500)break;const e=ke.current;e&&e.readyState===WebSocket.OPEN&&e.send(JSON.stringify({type:Y})),ze(Le),Ge.current+=1,ze(Oe),Oe.current=setTimeout(()=>{Oe.current=null,t===we.current&&nt()},400),Qe(),Ze(),Pe.current?.stop(),D(l(null)),D(c(o.LISTENING)),re.current||pe(H.KEEP);break}case"speech_stopped":if(Ze(),D(c(o.PROCESSING)),re.current)break;D(y(!0)),D(k(null)),he();break;case"error":{const t="string"==typeof r.message?r.message:"Voice error";if(j({kind:"error",error:t},me),ie.current(),D(c(o.IDLE)),pe(H.CANCEL),S.some(e=>e.test(t)))De.current="auth";else{if(!v.some(e=>e.test(t)))break;De.current="terminal"}O.warn({message:"Voice WS fatal error — closing socket",api_name:ae,conversation_id:e??void 0,drop_outcome:De.current}),ke.current?.close(ue);break}case"pong":Ie.current=null,Me.current=0,We.current=0,Ve.current=0;break;case f.STATUS_UPDATE:re.current=!0,Ue.current="string"==typeof r.text?r.text:null,ye(n),Pe.current?.start(Te.current?.remainingSeconds??0);break;default:ye(n)}},[D,Z,e,Se,pe,ve,de,Qe,nt,st,ce,he,me,ye,Xe]),pt=C((t,n)=>{ct(),Ie.current=null,Ce.current=setInterval(()=>{if(t!==we.current||n.readyState!==WebSocket.OPEN)return void ct();const r=Ie.current;if(null!==r&&Date.now()-r>3e4)return O.warn({message:"Voice WS pong watchdog tripped — forcing reconnect",api_name:ae,conversation_id:e??void 0}),ct(),De.current="retry",n.close(ue),void lt(t,ue,"watchdog",!0);null===r&&(Ie.current=Date.now()),n.send(JSON.stringify({type:"ping"}))},2e4)},[ct,lt,e]),mt=C(()=>{const e=Pe.current??te.current.createThinkingSound?.()??null;Pe.current=e,e?.unlock()},[]),gt=C(async()=>{if(ke.current||xe.current)return;if(!e)return;const t=te.current;if(!t.createPcmCapture||!t.createPcmPlayer)return O.error({message:"Voice WS transport unsupported on this platform (no PCM seams)",api_name:ae,conversation_id:e,transport:W}),F(p),void D(d());if(xe.current=!0,O.info({message:"Voice WS connecting",api_name:ae,conversation_id:e,transport:W,assistant_starts:V}),J($())&&Ve.current<h){Ve.current+=1;const e=Ee.current;try{await Promise.resolve(K()?.())}catch{}if(e!==Ee.current)return}const r=++we.current;let s;De.current=null,qe.current=!1,D(a(u.CONNECTING));try{s=new WebSocket(function(e,t){const n=t===L.PIPECAT_WS?G.CONVERSATION_PIPECAT_WS(e):G.CONVERSATION_STREAM(e),r="undefined"!=typeof window?window.location?.origin:void 0,s=new URL(n,r);s.protocol="https:"===s.protocol?"wss:":"ws:";const i=B();i&&s.searchParams.append("access_token",i);const c=function(){for(const[e,t]of Object.entries(U()))if("x-client-id"===e.toLowerCase()&&t)return t;return null}();return c&&s.searchParams.append("client_id",c),s.toString()}(e,W))}catch(t){return O.error({message:"Voice WS could not open socket",api_name:ae,conversation_id:e,error_message:t instanceof Error?t.message:String(t)}),F(p),D(d()),void ot(!1)}s.binaryType="arraybuffer",ke.current=s,mt();const l=Te.current??t.createPcmPlayer();Te.current=l,l.setPrerollProvider(()=>je.current??.2),l.setSinkId(Z.getState().voice.selectedSpeakerId||null);let m=!1,g=!1;it(),Ae.current=setTimeout(()=>{r===we.current&&(O.warn({message:"Voice WS connect timed out",api_name:ae,conversation_id:e}),F("Voice connection timed out. Please try again."),D(d()),ut.current())},T),s.onopen=()=>{if(r!==we.current)return;m=!0,it(),O.info({message:"Voice WS connected",api_name:ae,conversation_id:e,transport:W}),D(a(u.CONNECTED)),D(i(!0)),D(c(o.IDLE)),Ke.current=[];const t=V&&!Je.current;t&&(be.current=!1),tt(r),Ye(r),pt(r,s),t&&(Je.current=!0,s.send(JSON.stringify({type:"start_conversation"})),D(n(!0)),_e.current?.setMuted(!0),D(y(!0)),he(),ze($e),$e.current=setTimeout(()=>{$e.current=null,r===we.current&&(O.warn({message:"Voice WS greeting hold released by safety timer",api_name:ae,conversation_id:e}),Xe())},1e4))},s.onmessage=e=>{if(r!==we.current)return;if(e.data instanceof ArrayBuffer)return void dt(r,e.data);if("string"!=typeof e.data)return;let t=null;try{t=JSON.parse(e.data)}catch{return}t&&"string"==typeof t.type&&ht(r,t)},s.onerror=()=>{r===we.current&&O.warn({message:"Voice WS socket error",api_name:ae,conversation_id:e,socket_opened:m})},s.onclose=e=>{g||(g=!0,lt(r,e.code,e.reason,m))}},[e,W,Z,D,V,ot,it,mt,tt,Ye,pt,he,Xe,dt,ht,lt]),ft=C((t=!0)=>{O.info({message:"Voice WS disconnect (intentional teardown)",api_name:ae,conversation_id:e??void 0,commit_turn:t}),Ee.current+=1,we.current+=1,Ve.current=0,ze(Ne),Me.current=0,We.current=0,De.current=null,Je.current=!1,be.current=!0,D(P(!1));const n=ke.current;!n||n.readyState!==WebSocket.CONNECTING&&n.readyState!==WebSocket.OPEN||n.close(ue),ot(t)},[D,ot,e]),St=C(e=>{be.current=e,_e.current?.setMuted(!e)},[]),vt=C(e=>{D(w(e)),ke.current&&_e.current&&tt(we.current)},[D,tt]),yt=C(e=>{D(E(e)),Te.current?.setSinkId(e||null)},[D]),kt=C((t,n)=>{const r=ke.current;if(!r||r.readyState!==WebSocket.OPEN)return;const s=n?.modelContext;O.info({message:"Voice WS text_message",api_name:ae,conversation_id:e??void 0,has_model_context:Boolean(s?.length)}),r.send(JSON.stringify({type:Y})),nt(),rt(),le.current&&Qe(),Ze(),r.send(JSON.stringify({type:"text_message",text:t,...s?.length?{model_context:s}:{}})),ce.current.setMessages(e=>[...e,_(t,void 0,n?.documents,n?.messageType)]),D(y(!0)),D(k(null)),he(),D(c(o.PROCESSING))},[D,he,Qe,nt,rt,le,ce,e]);A(()=>{ut.current=ft},[ft]),A(()=>{at.current=gt},[gt]);const _t=b(e),Tt=b(!1);A(()=>{const t=_t.current;if(_t.current=e,t===e)return;const n=xe.current;n&&(Tt.current=!0),n&&ft(!1),Tt.current&&null!=e&&(Tt.current=!1,gt())},[e,ft,gt]);const Pt=b(ne);return A(()=>{const e=Pt.current;Pt.current=ne,e&&!ne&&xe.current&&ft(!1)},[ne,ft]),A(()=>()=>{ut.current(),Te.current?.destroy(),Te.current=null,Pe.current?.destroy(),Pe.current=null},[]),{connect:gt,disconnect:ft,sendText:kt,setMicEnabled:St,setInputDevice:vt,setOutputDevice:yt,primeAudio:mt}}(e,W,V,D);return x(Z,{voiceBridge:W})};export{re as BrowserPcmCapture,ie as BrowserPcmPlayer,ce as BrowserThinkingSound,se as computeAdaptivePreroll,le as useVoiceWsProviderValue};
|