@1interface/voice-core 0.5.7 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +11 -2
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { ActionCreatorWithOptionalPayload } from '@reduxjs/toolkit';
|
|
2
2
|
import { ActionCreatorWithoutPayload } from '@reduxjs/toolkit';
|
|
3
|
+
import { AttachedDocument } from '@1interface/shared-core';
|
|
3
4
|
import { Context } from 'react';
|
|
4
5
|
import { Dispatch } from '@reduxjs/toolkit';
|
|
5
6
|
import { ListenerMiddlewareInstance } from '@reduxjs/toolkit';
|
|
6
7
|
import { Message } from '@1interface/shared-core';
|
|
8
|
+
import { ModelContextPart } from '@1interface/shared-core';
|
|
7
9
|
import { MutableRefObject } from 'react';
|
|
8
10
|
import { PaymentInitPayload } from '@1interface/shared-core';
|
|
9
11
|
import { ReactNode } from 'react';
|
|
10
12
|
import { Reducer } from '@reduxjs/toolkit';
|
|
13
|
+
import { RequestDocumentsPayload } from '@1interface/shared-core';
|
|
11
14
|
import { Storage as Storage_2 } from '@1interface/shared-core';
|
|
12
15
|
import { ThunkAction } from '@reduxjs/toolkit';
|
|
13
16
|
import { ThunkDispatch } from '@reduxjs/toolkit';
|
|
@@ -155,8 +158,9 @@ export declare interface VoiceChatBridge {
|
|
|
155
158
|
updateStreamingMessage: (chatMsgId: string, content: string) => void;
|
|
156
159
|
commitStreamingMessages: () => void;
|
|
157
160
|
discardStreamingMessages: () => void;
|
|
158
|
-
setVoiceSendText: (fn: ((text: string) => void) | null) => void;
|
|
161
|
+
setVoiceSendText: (fn: ((text: string, options?: VoiceSendTextOptions) => void) | null) => void;
|
|
159
162
|
onPaymentInit?: (init: PaymentInitPayload) => void;
|
|
163
|
+
onRequestDocuments?: (payload: RequestDocumentsPayload) => void;
|
|
160
164
|
onConversationTitle?: (title: string) => void;
|
|
161
165
|
}
|
|
162
166
|
|
|
@@ -171,7 +175,7 @@ export declare interface VoiceContextValue {
|
|
|
171
175
|
enterVoiceMode: () => Promise<boolean>;
|
|
172
176
|
exitVoiceMode: () => void;
|
|
173
177
|
toggleMute: () => void;
|
|
174
|
-
sendText: (text: string) => void;
|
|
178
|
+
sendText: (text: string, options?: VoiceSendTextOptions) => void;
|
|
175
179
|
muted: boolean;
|
|
176
180
|
serverMuted: boolean;
|
|
177
181
|
sessionEndedSignal: number;
|
|
@@ -209,6 +213,11 @@ export declare interface VoiceRootState {
|
|
|
209
213
|
};
|
|
210
214
|
}
|
|
211
215
|
|
|
216
|
+
export declare interface VoiceSendTextOptions {
|
|
217
|
+
modelContext?: ModelContextPart[];
|
|
218
|
+
documents?: AttachedDocument[];
|
|
219
|
+
}
|
|
220
|
+
|
|
212
221
|
export declare interface VoiceState {
|
|
213
222
|
connectionStatus: VoiceConnectionStatus;
|
|
214
223
|
pipelineStatus: VoicePipelineStatus;
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react"),t=require("react-redux"),r=require("@reduxjs/toolkit"),n=require("@1interface/shared-core"),s={DISCONNECTED:"disconnected",CONNECTING:"connecting",CONNECTED:"connected"},o={IDLE:"idle",LISTENING:"listening",PROCESSING:"processing",SPEAKING:"speaking"},i={CASCADE:"cascade",REALTIME:"realtime"},a={OFFLINE:"offline",CONNECTING:"connecting",READY:"ready",IDLE:"idle",LISTENING:"listening",MUTED:"muted",PROCESSING:"processing",SPEAKING:"speaking"},c=[/rejected/i,/unauthori[sz]ed/i,/forbidden/i,/\b40[13]\b/],u=[/\b402\b/,/\b429\b/,/rate[\s-]?limit/i],l={SELECTED_MIC_ID:"voice_selectedMicId",SELECTED_SPEAKER_ID:"voice_selectedSpeakerId"};function d(){return{connectionStatus:s.DISCONNECTED,pipelineStatus:o.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 g=d(),p=r.createSlice({name:"voice",initialState:g,reducers:{setConnectionStatus(e,t){e.connectionStatus=t.payload},setPipelineStatus(e,t){e.pipelineStatus=t.payload},setListeningEnabled(e,t){e.listeningEnabled=t.payload},setStatusText(e,t){e.statusText=t.payload},setIsAgentWorking(e,t){e.isAgentWorking=t.payload},setResponseStartTime(e,t){e.responseStartTime=t.payload},setInterimTranscript(e,t){e.interimTranscript=t.payload},openBotTurn(e,t){e.botTurn={active:!0,chatMsgId:t.payload?.chatMsgId??null,segments:[]}},upsertBotSegment(e,t){const{id:r,text:n,spokenChars:s}=t.payload;e.botTurn.active=!0;const o=e.botTurn.segments.find(e=>e.id===r);o?(o.text=n,o.spokenChars=Math.max(o.spokenChars,s)):(e.botTurn.segments.push({id:r,text:n,spokenChars:s}),e.botTurn.segments.sort((e,t)=>e.id-t.id))},clearBotTurn(e){e.botTurn={active:!1,chatMsgId:null,segments:[]}},setMuted(e,t){e.muted=t.payload},setServerMuted(e,t){e.serverMuted=t.payload},setSelectedMicId(e,t){e.selectedMicId=t.payload},setSelectedSpeakerId(e,t){e.selectedSpeakerId=t.payload},setMicrophones(e,t){e.microphones=t.payload},setSpeakers(e,t){e.speakers=t.payload},signalSessionEnded(e){e.sessionEndedSignal+=1},resetVoiceState:()=>d(),resetIndicatorState(e){e.statusText=null,e.isAgentWorking=!1,e.responseStartTime=null,e.interimTranscript=null,e.serverMuted=!1,e.botTurn={active:!1,chatMsgId:null,segments:[]}}}}),{setConnectionStatus:m,setPipelineStatus:v,setListeningEnabled:I,setMuted:S,setServerMuted:E,setStatusText:_,setIsAgentWorking:T,setResponseStartTime:f,setInterimTranscript:C,openBotTurn:b,upsertBotSegment:h,clearBotTurn:k,setSelectedMicId:R,setSelectedSpeakerId:V,setMicrophones:y,setSpeakers:x,signalSessionEnded:M,resetVoiceState:N,resetIndicatorState:w}=p.actions,A=p.reducer,D=e.createContext(null),O=()=>{const t=e.useContext(D);if(!t)throw new Error("useAudioPlatform must be used inside <AudioPlatformProvider>. <VoiceProvider> mounts one by default; if you replaced VoiceProvider, you must wire your own.");return t};function P(){const e={};for(const[t,r]of Object.entries(n.getCustomHeaders()))r&&(e[t]=r);const t=n.readAccessToken();t&&(e[n.AUTH_HEADERS.ACCESS_TOKEN]=t);const r=n.readDeviceToken();return r&&(e[n.DEVICE_TOKEN_HEADER]=r),e}async function L(e){try{const t=await fetch(n.API_ENDPOINTS.WEBRTC_ICE(e),{headers:P()});return t.ok?(await t.json()).iceServers??[]:(n.logger.warn({message:"Voice ICE fetch failed — continuing without TURN",api_name:"voice-webrtc-ice",status:t.status}),[])}catch(e){return n.logger.warn({message:"Voice ICE fetch error — continuing without TURN",api_name:"voice-webrtc-ice",error_message:e instanceof Error?e.message:String(e)}),[]}}const G="voice-user-",U="Could not connect voice. Please try again later.",B=(e,t=`${G}${n.generateUniqueId()}`)=>({id:t,sender:"user",message:e,timestamp:(new Date).toISOString()}),q=(e,t)=>{const r=`${e}${n.generateUniqueId()}`;return{id:r,msg_id:r,sender:"agent",message:"",timestamp:(new Date).toISOString(),...t}};function j(){const e=t.useSelector(e=>e.voice.connectionStatus),r=t.useSelector(e=>e.voice.pipelineStatus),n=t.useSelector(e=>e.voice.listeningEnabled),i=t.useSelector(e=>e.voice.muted);return e===s.CONNECTING?a.CONNECTING:e!==s.CONNECTED?a.OFFLINE:n?i?a.MUTED:r===o.PROCESSING?a.PROCESSING:r===o.SPEAKING?a.SPEAKING:r===o.LISTENING?a.LISTENING:a.IDLE:a.READY}const F=e.createContext(null),K=e.createContext({strategyActive:!1,voiceSlot:null,onEnterVoice:null,onFloatingOrbClick:null,voiceInputHidden:!1,headerCenterSlot:null,floatOrbPositionRef:null}),W=r.createListenerMiddleware();W.startListening({actionCreator:R,effect:async e=>{const t=n.getStorageRef();t&&await t.setItem(l.SELECTED_MIC_ID,e.payload)}}),W.startListening({actionCreator:V,effect:async e=>{const t=n.getStorageRef();t&&await t.setItem(l.SELECTED_SPEAKER_ID,e.payload)}}),W.startListening({matcher:r.isAnyOf(n.clearSession,n.sessionExpired),effect:async(e,t)=>{const r=n.getStorageRef();r&&await Promise.all([r.removeItem(l.SELECTED_MIC_ID),r.removeItem(l.SELECTED_SPEAKER_ID)]),t.dispatch(N())}});const H=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{}}},$={attachBotAudioTrack:H,requestMicPermission:async function(){try{const e=await(navigator.permissions?.query({name:"microphone"}).catch(()=>null));return n.logger.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 n.logger.warn({message:"Voice mic permission denied",api_name:"voice-mic",error_message:e instanceof Error?e.message:String(e)}),{granted:!1,error:e}}}};exports.AudioPlatformContext=D,exports.ORB_STATE_CONFIG={offline:{opacity:.15,playbackRate:.3,scale:1},connecting:{opacity:.4,playbackRate:.5,scale:1},ready:{opacity:.4,playbackRate:.4,scale:1},idle:{opacity:.6,playbackRate:.6,scale:1},listening:{opacity:.7,playbackRate:.7,scale:1},muted:{opacity:.7,playbackRate:.5,scale:1},processing:{opacity:1,playbackRate:1,scale:1},speaking:{opacity:1,playbackRate:1,scale:1.3}},exports.VOICE_CONNECTION_STATUSES=s,exports.VOICE_CONNECT_TIMEOUT_MS=15e3,exports.VOICE_DISPLAY_STATES=a,exports.VOICE_PIPELINE_MODES=i,exports.VOICE_PIPELINE_STATUSES=o,exports.VOICE_STORAGE_KEYS=l,exports.VoiceContext=F,exports.VoiceUIContext=K,exports.attachBrowserBotAudioTrack=H,exports.browserAudioPlatform=$,exports.clearBotTurn=k,exports.hydrateVoice=e=>async t=>{const[r,n]=await Promise.all([e.getItem(l.SELECTED_MIC_ID),e.getItem(l.SELECTED_SPEAKER_ID)]);r&&t(p.actions.setSelectedMicId(r)),n&&t(p.actions.setSelectedSpeakerId(n))},exports.openBotTurn=b,exports.resetIndicatorState=w,exports.setConnectionStatus=m,exports.setInterimTranscript=C,exports.setIsAgentWorking=T,exports.setListeningEnabled=I,exports.setMuted=S,exports.setPipelineStatus=v,exports.setResponseStartTime=f,exports.setSelectedMicId=R,exports.setSelectedSpeakerId=V,exports.setServerMuted=E,exports.setStatusText=_,exports.upsertBotSegment=h,exports.useAudioPlatform=O,exports.useMediaDevices=function(){return{microphones:t.useSelector(e=>e.voice.microphones),speakers:t.useSelector(e=>e.voice.speakers),selectedMicId:t.useSelector(e=>e.voice.selectedMicId),selectedSpeakerId:t.useSelector(e=>e.voice.selectedSpeakerId)}},exports.useVoice=()=>{const t=e.useContext(F);if(!t)throw new Error("useVoice must be used within VoiceProvider");return t},exports.useVoiceDisplayState=j,exports.useVoiceProviderValue=({conversationId:r,voiceBridge:a,transportFactory:l,assistantStartsConversation:d})=>{const g=t.useDispatch(),p=O(),N=t.useSelector(e=>e.voice.muted),w=t.useSelector(e=>e.voice.serverMuted),A=t.useSelector(e=>e.voice.pipelineStatus),D=t.useSelector(e=>e.voice.isAgentWorking),F=t.useSelector(e=>e.voice.sessionEndedSignal),{connect:K,disconnect:W,sendText:H,setMicEnabled:$,setInputDevice:Y,setOutputDevice:Z}=function(r,a,l,d){const g=t.useDispatch(),p=t.useStore(),N=O(),w=t.useSelector(e=>e.auth.accessToken),A=e.useRef(a);e.useEffect(()=>{A.current=a});const D=e.useRef(null),j=e.useRef(new Map),F=e.useRef(""),K=e.useRef(!1),W=e.useRef(!1),H=e.useRef(null),$=e.useRef(""),Y=e.useRef(null),Z=e.useRef(null),z=e.useRef(null),J=e.useRef(!0),Q=e.useRef(null),X=e.useRef(!1),ee=e.useRef(""),te=e.useRef(0),re=e.useRef(0),ne=e.useRef(!1),se=e.useRef(0),oe=e.useRef(null),ie=e.useRef(null),ae=e.useRef(0),ce=e.useRef(0),ue=e.useRef(null),le=e.useRef(null),de=e.useCallback(()=>{if(null!=oe.current)return;const e=Date.now();oe.current=e,g(f(e)),g(n.clearLastResponseTime())},[g]),ge=e.useCallback(()=>{const e=oe.current;oe.current=null,g(f(null)),null!=e&&g(n.setLastResponseTime(Date.now()-e))},[g]),pe=e.useCallback(()=>{oe.current=null,g(f(null))},[g]),me=e.useCallback((e=n.INDICATOR_TIMER_ACTIONS.KEEP)=>{g(_(null)),g(T(!1)),e===n.INDICATOR_TIMER_ACTIONS.FINALIZE?ge():e===n.INDICATOR_TIMER_ACTIONS.CANCEL&&pe()},[g,ge,pe]),[ve]=e.useState(()=>({setStatusText:e=>g(_(e||null)),setIsAgentWorking:e=>g(T(e)),appendStreamText:()=>{},pushWidget:e=>{const t=q("voice-widget-",{widget:[e],message_type:["widget"]});A.current.setMessages(e=>[...e,t])},onPayment:e=>A.current.onPaymentInit?.(e),onConversationTitle:e=>A.current.onConversationTitle?.(e),onError:e=>{n.logger.error({message:"Voice RTVI server error",api_name:"voice-rtvi",error_message:e});const t=q("voice-error-",{message:"Something went wrong. Please try again.",message_type:["error"]});A.current.setMessages(e=>[...e,t])},onStatusActive:()=>{de()}})),Ie=e.useCallback(()=>{z.current&&(clearTimeout(z.current),z.current=null)},[]),Se=e.useCallback(()=>[...j.current.entries()].sort((e,t)=>e[0]-t[0]).map(([,e])=>e.text).join(" ").trim(),[]),Ee=e.useCallback(()=>{D.current=null,j.current=new Map,F.current="",g(k())},[g]),_e=e.useCallback(()=>{const e=D.current;if(e){const t=Se();t&&A.current.updateStreamingMessage(e,t),A.current.commitStreamingMessages()}Ee()},[Se,Ee]),Te=e.useCallback(e=>{Ie(),Q.current&&(clearTimeout(Q.current),Q.current=null),X.current=!1,ee.current="",g(y([])),g(x([])),Z.current?.(),Z.current=null,Y.current=null,ne.current=!1,g(I(!1)),g(v(o.IDLE)),g(m(s.DISCONNECTED)),g(C(null)),g(E(!1)),H.current=null,$.current="",K.current=!1,W.current=!1,e?_e():(A.current.discardStreamingMessages(),Ee()),me(n.INDICATOR_TIMER_ACTIONS.CANCEL)},[g,me,Ie,_e,Ee]),fe=e.useCallback(()=>{if(D.current)return;const e=`voice-agent-${n.generateUniqueId()}`;D.current=e,j.current=new Map,F.current="",g(b({chatMsgId:e}))},[g]),Ce=e.useCallback(e=>{const t=e.spoken_progress?(e.spoken_progress?.accumulated_text??"")+(e.spoken_progress?.remaining_text??""):e.text??"";if(!t)return;fe();const r=K.current?function(e,t){const r=(e.get(0)?.text??"")+t;return e.set(0,{text:r,completed:!1}),{id:0,text:r,spokenChars:r.length}}(j.current,t):function(e,t,r,n){const s=t.segment_id??0,o="completed"===t.spoken_status;if(o&&!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 i=e.get(s);return e.set(s,{text:r,completed:o||!0===i?.completed}),{id:s,text:r,spokenChars:n&&t.spoken_progress?(t.spoken_progress.accumulated_text??"").length:r.length}}(j.current,e,t,W.current);g(h(r));const n=Se();n&&D.current&&n!==F.current&&(F.current=n,A.current.updateStreamingMessage(D.current,n))},[g,fe,Se]),be=e.useCallback(e=>{const t=e,s=t?.type,o=t?.data;switch(n.logger.debug({message:"Voice RTVI server-message received",api_name:"voice-rtvi",conversation_id:r??void 0,event_type:s??"unknown"}),s){case"status_update":n.handleSharedEvent({kind:"status_update",statusText:o?.text??""},ve);break;case"started-thinking":g(T(!0)),de();break;case"stopped-thinking":me(n.INDICATOR_TIMER_ACTIONS.FINALIZE);break;case"clear_status":break;case"widget":o&&n.handleSharedEvent({kind:"widget",widget:o},ve);break;case"payment":o&&n.handleSharedEvent({kind:"payment",payment:o},ve);break;case"conversation_title":{const e="string"==typeof o?.text?o.text.trim():"";e&&n.handleSharedEvent({kind:"conversation_title",title:e},ve);break}case"session-ended":le.current={reason:"string"==typeof o?.reason?o.reason:"ended"};break;default:n.logger.debug({message:"Voice RTVI unhandled server-message",api_name:"voice-rtvi",event_type:s??"unknown"})}},[ve,g,de,me,r]),he=e.useRef(()=>{}),ke=e.useRef(async()=>{}),Re=e.useCallback(e=>{const t=()=>e===te.current;return{onConnected:()=>{if(!t())return;n.logger.info({message:"Voice RTVI connected",api_name:"voice-rtvi",conversation_id:r??void 0}),g(m(s.CONNECTED)),g(I(!0)),g(v(o.IDLE)),Y.current&&Y.current.isMicEnabled!==J.current&&(n.logger.debug({message:"Voice re-asserting mic state on reconnect",api_name:"voice-rtvi",conversation_id:r??void 0,enabled:J.current}),Y.current.enableMic(J.current));const{selectedMicId:e,selectedSpeakerId:i}=p.getState().voice;e&&Y.current?.updateMic(e),i&&Y.current?.updateSpeaker(i);const a=e=>t=>n.logger.warn({message:"Voice device enumeration failed",api_name:"voice-mic",conversation_id:r??void 0,device_kind:e,error_message:t instanceof Error?t.message:String(t)});Y.current?.getAllMics().then(e=>{t()&&g(y(e.map(e=>({deviceId:e.deviceId,label:e.label}))))}).catch(a("mics")),Y.current?.getAllSpeakers().then(e=>{t()&&g(x(e.map(e=>({deviceId:e.deviceId,label:e.label}))))}).catch(a("speakers")),X.current=!0},onBotReady:e=>{if(!t())return;const s=e?.about;K.current=s?.pipeline_mode===i.REALTIME,W.current=!0===s?.tts_word_timestamps,ae.current=0,ce.current=0,se.current=0,n.logger.info({message:"Voice RTVI bot-ready",api_name:"voice-rtvi",conversation_id:r??void 0,pipeline_mode:s?.pipeline_mode??"cascade",tts_word_timestamps:W.current,assistant_starts:d}),Ie(),d&&(g(T(!0)),de())},onDisconnected:()=>{if(!t())return;n.logger.info({message:"Voice RTVI disconnected",api_name:"voice-rtvi",conversation_id:r??void 0,drop_outcome:ue.current??"transient",reconnect_attempts:ae.current}),Te(!0);const e=re.current,s=ue.current;ue.current=null;const o=le.current;return le.current=null,o?(n.logger.info({message:"Voice session ended by server — no reconnect",api_name:"voice-rtvi",conversation_id:r??void 0,reason:o.reason}),"idle_timeout"===o.reason&&n.showToast({title:"Voice ended",description:"It got quiet for a while, so we wrapped up. Start again anytime."}),void g(M())):"terminal"===s?(n.logger.warn({message:"Voice RTVI terminal error — no retry",api_name:"voice-rtvi"}),void g(M())):"auth"===s?ce.current>=1||null==r?(n.logger.warn({message:"Voice RTVI auth refresh budget exhausted — giving up",api_name:"voice-rtvi",conversation_id:r??void 0}),void g(M())):(ce.current+=1,n.logger.info({message:"Voice RTVI auth drop — refreshing token then reconnecting",api_name:"voice-rtvi",conversation_id:r??void 0,auth_refresh_count:ce.current}),void(async()=>{let t=!1;try{t=!!await(n.getTokenRefreshHandler()?.())}catch{}e===re.current&&(t?ke.current():(n.logger.warn({message:"Voice RTVI token refresh failed — no reconnect",api_name:"voice-rtvi",conversation_id:r??void 0}),g(M())))})()):ae.current>=3||null==r?(ae.current>=3&&(n.logger.warn({message:"Voice RTVI auto-reconnect cap reached — giving up",api_name:"voice-rtvi",conversation_id:r??void 0,reconnect_attempts:ae.current}),n.showErrorToast("Voice connection lost. Please try again.")),void g(M())):(ae.current+=1,n.logger.info({message:"Voice RTVI scheduling auto-reconnect",api_name:"voice-rtvi",conversation_id:r??void 0,reconnect_attempts:ae.current,delay_ms:1e3}),ie.current&&clearTimeout(ie.current),void(ie.current=setTimeout(()=>{ie.current=null,ke.current()},1e3)))},onError:e=>{if(!t())return;const s=e?.data,i=s?.error??s?.message??"Voice error";n.handleSharedEvent({kind:"error",error:i},ve),g(v(o.IDLE)),me(n.INDICATOR_TIMER_ACTIONS.CANCEL),s?.fatal&&(c.some(e=>e.test(i))?ue.current="auth":u.some(e=>e.test(i))&&(ue.current="terminal"),n.logger.warn({message:"Voice RTVI fatal error — closing transport",api_name:"voice-rtvi",conversation_id:r??void 0,drop_outcome:ue.current??"generic"}),Y.current?.disconnect().catch(()=>{}))},onTrackStarted:(e,s)=>{t()&&(s?.local||"audio"!==e.kind||(n.logger.debug({message:"Voice RTVI bot audio track started",api_name:"voice-rtvi",conversation_id:r??void 0}),Z.current?.(),Z.current=N.attachBotAudioTrack(e)))},onAvailableMicsUpdated:e=>{t()&&(g(y(e.map(e=>({deviceId:e.deviceId,label:e.label})))),X.current&&(Q.current&&clearTimeout(Q.current),Q.current=setTimeout(()=>{if(Q.current=null,!Y.current)return;const{selectedMicId:e,microphones:t}=p.getState().voice;n.logger.info({message:"Voice audio devices changed",api_name:"voice-mic",microphones:t.map(e=>e.label).join(", ")}),n.logger.debug({message:"Voice re-acquiring mic (device change)",api_name:"voice-rtvi",enabled:J.current,device:e||"default"}),Y.current.updateMic(e)},300)))},onAvailableSpeakersUpdated:e=>{if(!t())return;if(g(x(e.map(e=>({deviceId:e.deviceId,label:e.label})))),!X.current)return;const r=e.map(e=>e.label).join(", ");r!==ee.current&&(ee.current=r,n.logger.info({message:"Voice audio output devices changed",api_name:"voice-mic",speakers:r}))},onMicUpdated:e=>{if(!t())return;const s=Y.current;s&&s.isMicEnabled!==J.current&&(n.logger.debug({message:"Voice re-asserting mic state after track swap",api_name:"voice-rtvi",conversation_id:r??void 0,enabled:J.current,device:e?.label}),s.enableMic(J.current))},onUserMuteStarted:()=>{t()&&n.logger.debug({message:"Voice RTVI user-mute-started (ignored)",api_name:"voice-rtvi",conversation_id:r??void 0})},onUserMuteStopped:()=>{t()&&(n.logger.debug({message:"Voice RTVI user-mute-stopped",api_name:"voice-rtvi",conversation_id:r??void 0,honored:d}),d&&g(E(!1)))},onUserStartedSpeaking:()=>{t()&&(n.logger.debug({message:"Voice RTVI user started speaking",api_name:"voice-rtvi",conversation_id:r??void 0}),H.current=null,$.current="",g(C(null)),g(v(o.LISTENING)))},onUserStoppedSpeaking:()=>{if(!t())return;n.logger.debug({message:"Voice RTVI user stopped speaking",api_name:"voice-rtvi",conversation_id:r??void 0,has_interim:!!$.current.trim()}),g(v(o.PROCESSING)),g(T(!0)),g(_(null)),de();const e=$.current.trim();if(e){const t=`${G}${n.generateUniqueId()}`;H.current=t,g(C(null)),A.current.setMessages(r=>[...r,B(e,t)])}},onUserTranscript:e=>{if(!t())return;if(!e.final)return void(e.text&&($.current=e.text,K.current||g(C(e.text))));g(C(null));const s=e.text&&e.text.trim()?e.text:"";n.logger.debug({message:"Voice RTVI user transcript (final)",api_name:"voice-rtvi",conversation_id:r??void 0,has_text:!!s});const o=H.current;H.current=null,o?s&&A.current.setMessages(e=>e.map(e=>e.id===o?{...e,message:s}:e)):s&&A.current.setMessages(e=>[...e,B(s)])},onBotStartedSpeaking:()=>{t()&&(n.logger.debug({message:"Voice RTVI bot started speaking",api_name:"voice-rtvi",conversation_id:r??void 0}),fe(),g(v(o.SPEAKING)),me(n.INDICATOR_TIMER_ACTIONS.FINALIZE))},onBotOutput:e=>{t()&&Ce(e)},onBotStoppedSpeaking:()=>{t()&&(n.logger.debug({message:"Voice RTVI bot stopped speaking",api_name:"voice-rtvi",conversation_id:r??void 0}),_e(),g(v(o.IDLE)),me(n.INDICATOR_TIMER_ACTIONS.FINALIZE),d&&p.getState().voice.serverMuted&&(n.logger.warn({message:"Voice greeting mute-hold released on bot-turn end (server unmute missing)",api_name:"voice-rtvi",conversation_id:r??void 0}),g(E(!1))))},onServerMessage:e=>{t()&&be(e)}}},[g,p,N,Ie,Te,me,de,Ce,fe,_e,be,ve,d,r]),Ve=e.useCallback(async()=>{if(Y.current||ne.current)return;if(!r)return;if(ne.current=!0,n.logger.info({message:"Voice RTVI connecting",api_name:"voice-rtvi",conversation_id:r,assistant_starts:d}),d&&(J.current=!1,g(E(!0)),n.logger.debug({message:"Voice greeting mute-hold armed",api_name:"voice-rtvi",conversation_id:r})),n.isTokenAboutToExpire(n.readTokenExpiresAt())&&se.current<1){se.current+=1,n.logger.debug({message:"Voice pre-connect token refresh (freshness gate)",api_name:"voice-rtvi",conversation_id:r,freshness_refresh_count:se.current});const e=re.current;try{await Promise.resolve(n.getTokenRefreshHandler()?.())}catch{}if(e!==re.current)return}const e=++te.current,t=re.current;le.current=null,ue.current=null,g(m(s.CONNECTING));const o=()=>t!==re.current||e!==te.current;try{const[{PipecatClient:t,LogLevel:s},i]=await Promise.all([Promise.resolve().then(()=>require("./index.module-DCaqEXL5.cjs")),L(r)]);if(o())return;const a=new t({transport:l(),enableMic:!0,enableCam:!1,callbacks:Re(e)});if(a.setLogLevel(s.WARN),o())return void a.disconnect().catch(()=>{});Y.current=a,n.logger.debug({message:"Voice RTVI client constructed",api_name:"voice-rtvi",conversation_id:r,enable_mic:J.current}),Ie(),z.current=setTimeout(()=>{n.logger.warn({message:"Voice RTVI connect timed out (no bot-ready)",api_name:"voice-rtvi"}),n.showErrorToast("Voice connection timed out. Please try again."),g(M()),he.current()},15e3),await a.connect({webrtcRequestParams:{endpoint:n.API_ENDPOINTS.WEBRTC_OFFER(r),headers:new Headers(P())},iceConfig:{iceServers:i}})}catch(t){const r=(e=>{const t="object"==typeof e&&null!==e&&"status"in e?e.status:void 0;return"number"==typeof t?t:void 0})(t);n.logger.error({message:"Voice RTVI connect failed",api_name:"voice-rtvi",status:r,error_message:t instanceof Error?t.message:String(t)});const s=(e=>void 0!==e&&e>=400&&e<500)(r),o=s?await(async(e,t)=>{const r=429===t?"Voice session limit reached. Please try again later.":U,n="object"==typeof e&&null!==e&&"cause"in e?e.cause:void 0;if(!(n instanceof Response))return r;try{const e=await n.json(),t=e?.data?.error_message??e?.data?.message;return"string"==typeof t?t:r}catch{return r}})(t,r):U;e===te.current&&(n.showErrorToast(o),s&&(ie.current&&(clearTimeout(ie.current),ie.current=null),ae.current=0,g(M())),Te(!1))}},[r,g,l,Re,Ie,Te,d]),ye=e.useCallback((e=!0)=>{n.logger.info({message:"Voice RTVI disconnect (intentional teardown)",api_name:"voice-rtvi",conversation_id:r??void 0,commit_turn:e}),re.current+=1,te.current+=1,se.current=0,ie.current&&(clearTimeout(ie.current),ie.current=null),ae.current=0,ce.current=0,ue.current=null,J.current=!0,g(S(!1));const t=Y.current;t?.disconnect().catch(e=>{n.logger.warn({message:"Voice RTVI disconnect threw",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}),Te(e)},[Te,g,r]),xe=e.useCallback(e=>{J.current=e,Y.current&&(n.logger.debug({message:"Voice mic enable toggled",api_name:"voice-rtvi",enabled:e}),Y.current.enableMic(e))},[]),Me=e.useCallback(e=>{g(R(e)),Y.current?.updateMic(e)},[g]),Ne=e.useCallback(e=>{g(V(e)),Y.current?.updateSpeaker(e)},[g]),we=e.useCallback(e=>{const t=Y.current;if(t){n.logger.info({message:"Voice RTVI send-text",api_name:"voice-rtvi",conversation_id:r??void 0,run_immediately:!0});try{t.sendText(e,{run_immediately:!0,audio_response:!0})}catch(e){return void n.logger.error({message:"Voice RTVI send-text failed",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}D.current&&_e(),A.current.setMessages(t=>[...t,B(e)]),g(T(!0)),g(_(null)),de(),g(v(o.PROCESSING))}},[g,de,_e,r]);e.useEffect(()=>{he.current=ye},[ye]),e.useEffect(()=>{ke.current=Ve},[Ve]);const Ae=e.useRef(r),De=e.useRef(!1);e.useEffect(()=>{const e=Ae.current;if(Ae.current=r,e===r)return;const t=ne.current;t&&(De.current=!0),t&&ye(!1),De.current&&null!=r&&(De.current=!1,Ve())},[r,ye,Ve]);const Oe=e.useRef(w);return e.useEffect(()=>{const e=Oe.current;Oe.current=w,e&&!w&&ne.current&&ye(!1)},[w,ye]),e.useEffect(()=>()=>{he.current()},[]),{connect:Ve,disconnect:ye,sendText:we,setMicEnabled:xe,setInputDevice:Me,setOutputDevice:Ne}}(r,a,l,d);e.useEffect(()=>{$(!N&&!w)},[N,w,$]);const z=j();e.useEffect(()=>(a.setVoiceSendText(H),()=>a.setVoiceSendText(null)),[a,H]);const J=e.useRef(!1);return{displayState:z,pipelineStatus:A,isAgentWorking:D,enterVoiceMode:e.useCallback(async()=>{if(J.current)return n.showErrorToast("Microphone permission changed — please reload the page to apply the new setting."),!1;const e=await p.requestMicPermission();if(!e.granted){const t=e.error;return n.logger.error({message:"Mic permission denied",api_name:"voice-mic",error_message:t instanceof Error?t.message:t?String(t):void 0}),J.current=!0,n.showErrorToast("Microphone access is required for voice mode. Please allow it in your browser's site settings and reload the page."),!1}return K(),!0},[p,K]),exitVoiceMode:e.useCallback(()=>{W()},[W]),toggleMute:e.useCallback(()=>{g(S(!N))},[g,N]),sendText:H,muted:N,serverMuted:w,sessionEndedSignal:F,setInputDevice:Y,setOutputDevice:Z}},exports.useVoiceUI=()=>e.useContext(K),exports.voiceListenerMiddleware=W,exports.voiceReducer=A;
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react"),t=require("react-redux"),r=require("@reduxjs/toolkit"),n=require("@1interface/shared-core"),s={DISCONNECTED:"disconnected",CONNECTING:"connecting",CONNECTED:"connected"},o={IDLE:"idle",LISTENING:"listening",PROCESSING:"processing",SPEAKING:"speaking"},i={CASCADE:"cascade",REALTIME:"realtime"},a={OFFLINE:"offline",CONNECTING:"connecting",READY:"ready",IDLE:"idle",LISTENING:"listening",MUTED:"muted",PROCESSING:"processing",SPEAKING:"speaking"},c=[/rejected/i,/unauthori[sz]ed/i,/forbidden/i,/\b40[13]\b/],u=[/\b402\b/,/\b429\b/,/rate[\s-]?limit/i],l={SELECTED_MIC_ID:"voice_selectedMicId",SELECTED_SPEAKER_ID:"voice_selectedSpeakerId"};function d(){return{connectionStatus:s.DISCONNECTED,pipelineStatus:o.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 g=d(),p=r.createSlice({name:"voice",initialState:g,reducers:{setConnectionStatus(e,t){e.connectionStatus=t.payload},setPipelineStatus(e,t){e.pipelineStatus=t.payload},setListeningEnabled(e,t){e.listeningEnabled=t.payload},setStatusText(e,t){e.statusText=t.payload},setIsAgentWorking(e,t){e.isAgentWorking=t.payload},setResponseStartTime(e,t){e.responseStartTime=t.payload},setInterimTranscript(e,t){e.interimTranscript=t.payload},openBotTurn(e,t){e.botTurn={active:!0,chatMsgId:t.payload?.chatMsgId??null,segments:[]}},upsertBotSegment(e,t){const{id:r,text:n,spokenChars:s}=t.payload;e.botTurn.active=!0;const o=e.botTurn.segments.find(e=>e.id===r);o?(o.text=n,o.spokenChars=Math.max(o.spokenChars,s)):(e.botTurn.segments.push({id:r,text:n,spokenChars:s}),e.botTurn.segments.sort((e,t)=>e.id-t.id))},clearBotTurn(e){e.botTurn={active:!1,chatMsgId:null,segments:[]}},setMuted(e,t){e.muted=t.payload},setServerMuted(e,t){e.serverMuted=t.payload},setSelectedMicId(e,t){e.selectedMicId=t.payload},setSelectedSpeakerId(e,t){e.selectedSpeakerId=t.payload},setMicrophones(e,t){e.microphones=t.payload},setSpeakers(e,t){e.speakers=t.payload},signalSessionEnded(e){e.sessionEndedSignal+=1},resetVoiceState:()=>d(),resetIndicatorState(e){e.statusText=null,e.isAgentWorking=!1,e.responseStartTime=null,e.interimTranscript=null,e.serverMuted=!1,e.botTurn={active:!1,chatMsgId:null,segments:[]}}}}),{setConnectionStatus:m,setPipelineStatus:v,setListeningEnabled:I,setMuted:S,setServerMuted:_,setStatusText:E,setIsAgentWorking:T,setResponseStartTime:f,setInterimTranscript:C,openBotTurn:b,upsertBotSegment:h,clearBotTurn:k,setSelectedMicId:R,setSelectedSpeakerId:V,setMicrophones:y,setSpeakers:x,signalSessionEnded:M,resetVoiceState:N,resetIndicatorState:w}=p.actions,A=p.reducer,D=e.createContext(null),O=()=>{const t=e.useContext(D);if(!t)throw new Error("useAudioPlatform must be used inside <AudioPlatformProvider>. <VoiceProvider> mounts one by default; if you replaced VoiceProvider, you must wire your own.");return t};function P(){const e={};for(const[t,r]of Object.entries(n.getCustomHeaders()))r&&(e[t]=r);const t=n.readAccessToken();t&&(e[n.AUTH_HEADERS.ACCESS_TOKEN]=t);const r=n.readDeviceToken();return r&&(e[n.DEVICE_TOKEN_HEADER]=r),e}async function L(e){try{const t=await fetch(n.API_ENDPOINTS.WEBRTC_ICE(e),{headers:P()});return t.ok?(await t.json()).iceServers??[]:(n.logger.warn({message:"Voice ICE fetch failed — continuing without TURN",api_name:"voice-webrtc-ice",status:t.status}),[])}catch(e){return n.logger.warn({message:"Voice ICE fetch error — continuing without TURN",api_name:"voice-webrtc-ice",error_message:e instanceof Error?e.message:String(e)}),[]}}const G="voice-user-",U="Could not connect voice. Please try again later.",B=(e,t=`${G}${n.generateUniqueId()}`,r)=>({id:t,sender:"user",message:e,timestamp:(new Date).toISOString(),...r?.length?{documents:r}:{}}),q=(e,t)=>{const r=`${e}${n.generateUniqueId()}`;return{id:r,msg_id:r,sender:"agent",message:"",timestamp:(new Date).toISOString(),...t}};function j(){const e=t.useSelector(e=>e.voice.connectionStatus),r=t.useSelector(e=>e.voice.pipelineStatus),n=t.useSelector(e=>e.voice.listeningEnabled),i=t.useSelector(e=>e.voice.muted);return e===s.CONNECTING?a.CONNECTING:e!==s.CONNECTED?a.OFFLINE:n?i?a.MUTED:r===o.PROCESSING?a.PROCESSING:r===o.SPEAKING?a.SPEAKING:r===o.LISTENING?a.LISTENING:a.IDLE:a.READY}const F=e.createContext(null),K=e.createContext({strategyActive:!1,voiceSlot:null,onEnterVoice:null,onFloatingOrbClick:null,voiceInputHidden:!1,headerCenterSlot:null,floatOrbPositionRef:null}),W=r.createListenerMiddleware();W.startListening({actionCreator:R,effect:async e=>{const t=n.getStorageRef();t&&await t.setItem(l.SELECTED_MIC_ID,e.payload)}}),W.startListening({actionCreator:V,effect:async e=>{const t=n.getStorageRef();t&&await t.setItem(l.SELECTED_SPEAKER_ID,e.payload)}}),W.startListening({matcher:r.isAnyOf(n.clearSession,n.sessionExpired),effect:async(e,t)=>{const r=n.getStorageRef();r&&await Promise.all([r.removeItem(l.SELECTED_MIC_ID),r.removeItem(l.SELECTED_SPEAKER_ID)]),t.dispatch(N())}});const H=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{}}},$={attachBotAudioTrack:H,requestMicPermission:async function(){try{const e=await(navigator.permissions?.query({name:"microphone"}).catch(()=>null));return n.logger.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 n.logger.warn({message:"Voice mic permission denied",api_name:"voice-mic",error_message:e instanceof Error?e.message:String(e)}),{granted:!1,error:e}}}};exports.AudioPlatformContext=D,exports.ORB_STATE_CONFIG={offline:{opacity:.15,playbackRate:.3,scale:1},connecting:{opacity:.4,playbackRate:.5,scale:1},ready:{opacity:.4,playbackRate:.4,scale:1},idle:{opacity:.6,playbackRate:.6,scale:1},listening:{opacity:.7,playbackRate:.7,scale:1},muted:{opacity:.7,playbackRate:.5,scale:1},processing:{opacity:1,playbackRate:1,scale:1},speaking:{opacity:1,playbackRate:1,scale:1.3}},exports.VOICE_CONNECTION_STATUSES=s,exports.VOICE_CONNECT_TIMEOUT_MS=15e3,exports.VOICE_DISPLAY_STATES=a,exports.VOICE_PIPELINE_MODES=i,exports.VOICE_PIPELINE_STATUSES=o,exports.VOICE_STORAGE_KEYS=l,exports.VoiceContext=F,exports.VoiceUIContext=K,exports.attachBrowserBotAudioTrack=H,exports.browserAudioPlatform=$,exports.clearBotTurn=k,exports.hydrateVoice=e=>async t=>{const[r,n]=await Promise.all([e.getItem(l.SELECTED_MIC_ID),e.getItem(l.SELECTED_SPEAKER_ID)]);r&&t(p.actions.setSelectedMicId(r)),n&&t(p.actions.setSelectedSpeakerId(n))},exports.openBotTurn=b,exports.resetIndicatorState=w,exports.setConnectionStatus=m,exports.setInterimTranscript=C,exports.setIsAgentWorking=T,exports.setListeningEnabled=I,exports.setMuted=S,exports.setPipelineStatus=v,exports.setResponseStartTime=f,exports.setSelectedMicId=R,exports.setSelectedSpeakerId=V,exports.setServerMuted=_,exports.setStatusText=E,exports.upsertBotSegment=h,exports.useAudioPlatform=O,exports.useMediaDevices=function(){return{microphones:t.useSelector(e=>e.voice.microphones),speakers:t.useSelector(e=>e.voice.speakers),selectedMicId:t.useSelector(e=>e.voice.selectedMicId),selectedSpeakerId:t.useSelector(e=>e.voice.selectedSpeakerId)}},exports.useVoice=()=>{const t=e.useContext(F);if(!t)throw new Error("useVoice must be used within VoiceProvider");return t},exports.useVoiceDisplayState=j,exports.useVoiceProviderValue=({conversationId:r,voiceBridge:a,transportFactory:l,assistantStartsConversation:d})=>{const g=t.useDispatch(),p=O(),N=t.useSelector(e=>e.voice.muted),w=t.useSelector(e=>e.voice.serverMuted),A=t.useSelector(e=>e.voice.pipelineStatus),D=t.useSelector(e=>e.voice.isAgentWorking),F=t.useSelector(e=>e.voice.sessionEndedSignal),{connect:K,disconnect:W,sendText:H,setMicEnabled:$,setInputDevice:Y,setOutputDevice:Z}=function(r,a,l,d){const g=t.useDispatch(),p=t.useStore(),N=O(),w=t.useSelector(e=>e.auth.accessToken),A=e.useRef(a);e.useEffect(()=>{A.current=a});const D=e.useRef(null),j=e.useRef(new Map),F=e.useRef(""),K=e.useRef(!1),W=e.useRef(!1),H=e.useRef(null),$=e.useRef(""),Y=e.useRef(null),Z=e.useRef(null),z=e.useRef(null),J=e.useRef(null),Q=e.useRef(!0),X=e.useRef(null),ee=e.useRef(!1),te=e.useRef(""),re=e.useRef(0),ne=e.useRef(0),se=e.useRef(!1),oe=e.useRef(0),ie=e.useRef(null),ae=e.useRef(null),ce=e.useRef(0),ue=e.useRef(0),le=e.useRef(null),de=e.useRef(null),ge=e.useCallback(()=>{if(null!=ie.current)return;const e=Date.now();ie.current=e,g(f(e)),g(n.clearLastResponseTime())},[g]),pe=e.useCallback(()=>{const e=ie.current;ie.current=null,g(f(null)),null!=e&&g(n.setLastResponseTime(Date.now()-e))},[g]),me=e.useCallback(()=>{ie.current=null,g(f(null))},[g]),ve=e.useCallback((e=n.INDICATOR_TIMER_ACTIONS.KEEP)=>{g(E(null)),g(T(!1)),e===n.INDICATOR_TIMER_ACTIONS.FINALIZE?pe():e===n.INDICATOR_TIMER_ACTIONS.CANCEL&&me()},[g,pe,me]),[Ie]=e.useState(()=>({setStatusText:e=>g(E(e||null)),setIsAgentWorking:e=>g(T(e)),appendStreamText:()=>{},pushWidget:e=>{const t=q("voice-widget-",{widget:[e],message_type:["widget"]});A.current.setMessages(e=>[...e,t])},onPayment:e=>A.current.onPaymentInit?.(e),onRequestDocuments:e=>A.current.onRequestDocuments?.(e),onConversationTitle:e=>A.current.onConversationTitle?.(e),onError:e=>{n.logger.error({message:"Voice RTVI server error",api_name:"voice-rtvi",error_message:e});const t=q("voice-error-",{message:"Something went wrong. Please try again.",message_type:["error"]});A.current.setMessages(e=>[...e,t])},onStatusActive:()=>{ge()}})),Se=e.useCallback(()=>{J.current&&(clearTimeout(J.current),J.current=null)},[]),_e=e.useCallback(()=>[...j.current.entries()].sort((e,t)=>e[0]-t[0]).map(([,e])=>e.text).join(" ").trim(),[]),Ee=e.useCallback(()=>{D.current=null,j.current=new Map,F.current="",g(k())},[g]),Te=e.useCallback(()=>{const e=D.current;if(e){const t=_e();t&&A.current.updateStreamingMessage(e,t),A.current.commitStreamingMessages()}Ee()},[_e,Ee]),fe=e.useCallback(e=>{Se(),X.current&&(clearTimeout(X.current),X.current=null),ee.current=!1,te.current="",g(y([])),g(x([])),z.current?.(),z.current=null,Y.current=null,se.current=!1,g(I(!1)),g(v(o.IDLE)),g(m(s.DISCONNECTED)),g(C(null)),g(_(!1)),H.current=null,$.current="",K.current=!1,W.current=!1,e?Te():(A.current.discardStreamingMessages(),Ee()),ve(n.INDICATOR_TIMER_ACTIONS.CANCEL)},[g,ve,Se,Te,Ee]),Ce=e.useCallback(()=>{if(D.current)return;const e=`voice-agent-${n.generateUniqueId()}`;D.current=e,j.current=new Map,F.current="",g(b({chatMsgId:e}))},[g]),be=e.useCallback(e=>{const t=e.spoken_progress?(e.spoken_progress?.accumulated_text??"")+(e.spoken_progress?.remaining_text??""):e.text??"";if(!t)return;Ce();const r=K.current?function(e,t){const r=(e.get(0)?.text??"")+t;return e.set(0,{text:r,completed:!1}),{id:0,text:r,spokenChars:r.length}}(j.current,t):function(e,t,r,n){const s=t.segment_id??0,o="completed"===t.spoken_status;if(o&&!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 i=e.get(s);return e.set(s,{text:r,completed:o||!0===i?.completed}),{id:s,text:r,spokenChars:n&&t.spoken_progress?(t.spoken_progress.accumulated_text??"").length:r.length}}(j.current,e,t,W.current);g(h(r));const n=_e();n&&D.current&&n!==F.current&&(F.current=n,A.current.updateStreamingMessage(D.current,n))},[g,Ce,_e]),he=e.useCallback(e=>{const t=e,s=t?.type,o=t?.data;switch(n.logger.debug({message:"Voice RTVI server-message received",api_name:"voice-rtvi",conversation_id:r??void 0,event_type:s??"unknown"}),s){case"status_update":n.handleSharedEvent({kind:"status_update",statusText:o?.text??""},Ie);break;case"started-thinking":g(T(!0)),ge();break;case"stopped-thinking":ve(n.INDICATOR_TIMER_ACTIONS.FINALIZE);break;case"clear_status":break;case"widget":o&&n.handleSharedEvent({kind:"widget",widget:o},Ie);break;case"payment":o&&n.handleSharedEvent({kind:"payment",payment:o},Ie);break;case"request_documents":o&&n.handleSharedEvent({kind:"request_documents",requestDocuments:o},Ie);break;case"conversation_title":{const e="string"==typeof o?.text?o.text.trim():"";e&&n.handleSharedEvent({kind:"conversation_title",title:e},Ie);break}case"session-ended":de.current={reason:"string"==typeof o?.reason?o.reason:"ended"};break;default:n.logger.debug({message:"Voice RTVI unhandled server-message",api_name:"voice-rtvi",event_type:s??"unknown"})}},[Ie,g,ge,ve,r]),ke=e.useRef(()=>{}),Re=e.useRef(async()=>{}),Ve=e.useCallback(e=>{const t=()=>e===re.current;return{onConnected:()=>{if(!t())return;n.logger.info({message:"Voice RTVI connected",api_name:"voice-rtvi",conversation_id:r??void 0}),g(m(s.CONNECTED)),g(I(!0)),g(v(o.IDLE)),Y.current&&Y.current.isMicEnabled!==Q.current&&(n.logger.debug({message:"Voice re-asserting mic state on reconnect",api_name:"voice-rtvi",conversation_id:r??void 0,enabled:Q.current}),Y.current.enableMic(Q.current));const{selectedMicId:e,selectedSpeakerId:i}=p.getState().voice;e&&Y.current?.updateMic(e),i&&Y.current?.updateSpeaker(i);const a=e=>t=>n.logger.warn({message:"Voice device enumeration failed",api_name:"voice-mic",conversation_id:r??void 0,device_kind:e,error_message:t instanceof Error?t.message:String(t)});Y.current?.getAllMics().then(e=>{t()&&g(y(e.map(e=>({deviceId:e.deviceId,label:e.label}))))}).catch(a("mics")),Y.current?.getAllSpeakers().then(e=>{t()&&g(x(e.map(e=>({deviceId:e.deviceId,label:e.label}))))}).catch(a("speakers")),ee.current=!0},onBotReady:e=>{if(!t())return;const s=e?.about;K.current=s?.pipeline_mode===i.REALTIME,W.current=!0===s?.tts_word_timestamps,ce.current=0,ue.current=0,oe.current=0,n.logger.info({message:"Voice RTVI bot-ready",api_name:"voice-rtvi",conversation_id:r??void 0,pipeline_mode:s?.pipeline_mode??"cascade",tts_word_timestamps:W.current,assistant_starts:d}),Se(),d&&(g(T(!0)),ge())},onDisconnected:()=>{if(!t())return;n.logger.info({message:"Voice RTVI disconnected",api_name:"voice-rtvi",conversation_id:r??void 0,drop_outcome:le.current??"transient",reconnect_attempts:ce.current}),fe(!0);const e=ne.current,s=le.current;le.current=null;const o=de.current;return de.current=null,o?(n.logger.info({message:"Voice session ended by server — no reconnect",api_name:"voice-rtvi",conversation_id:r??void 0,reason:o.reason}),"idle_timeout"===o.reason&&n.showToast({title:"Voice ended",description:"It got quiet for a while, so we wrapped up. Start again anytime."}),void g(M())):"terminal"===s?(n.logger.warn({message:"Voice RTVI terminal error — no retry",api_name:"voice-rtvi"}),void g(M())):"auth"===s?ue.current>=1||null==r?(n.logger.warn({message:"Voice RTVI auth refresh budget exhausted — giving up",api_name:"voice-rtvi",conversation_id:r??void 0}),void g(M())):(ue.current+=1,n.logger.info({message:"Voice RTVI auth drop — refreshing token then reconnecting",api_name:"voice-rtvi",conversation_id:r??void 0,auth_refresh_count:ue.current}),void(async()=>{let t=!1;try{t=!!await(n.getTokenRefreshHandler()?.())}catch{}e===ne.current&&(t?Re.current():(n.logger.warn({message:"Voice RTVI token refresh failed — no reconnect",api_name:"voice-rtvi",conversation_id:r??void 0}),g(M())))})()):ce.current>=3||null==r?(ce.current>=3&&(n.logger.warn({message:"Voice RTVI auto-reconnect cap reached — giving up",api_name:"voice-rtvi",conversation_id:r??void 0,reconnect_attempts:ce.current}),n.showErrorToast("Voice connection lost. Please try again.")),void g(M())):(ce.current+=1,n.logger.info({message:"Voice RTVI scheduling auto-reconnect",api_name:"voice-rtvi",conversation_id:r??void 0,reconnect_attempts:ce.current,delay_ms:1e3}),ae.current&&clearTimeout(ae.current),void(ae.current=setTimeout(()=>{ae.current=null,Re.current()},1e3)))},onError:e=>{if(!t())return;const s=e?.data,i=s?.error??s?.message??"Voice error";n.handleSharedEvent({kind:"error",error:i},Ie),g(v(o.IDLE)),ve(n.INDICATOR_TIMER_ACTIONS.CANCEL),s?.fatal&&(c.some(e=>e.test(i))?le.current="auth":u.some(e=>e.test(i))&&(le.current="terminal"),n.logger.warn({message:"Voice RTVI fatal error — closing transport",api_name:"voice-rtvi",conversation_id:r??void 0,drop_outcome:le.current??"generic"}),Y.current?.disconnect().catch(()=>{}))},onTrackStarted:(e,s)=>{t()&&(s?.local||"audio"!==e.kind||(n.logger.debug({message:"Voice RTVI bot audio track started",api_name:"voice-rtvi",conversation_id:r??void 0}),z.current?.(),z.current=N.attachBotAudioTrack(e)))},onAvailableMicsUpdated:e=>{t()&&(g(y(e.map(e=>({deviceId:e.deviceId,label:e.label})))),ee.current&&(X.current&&clearTimeout(X.current),X.current=setTimeout(()=>{if(X.current=null,!Y.current)return;const{selectedMicId:e,microphones:t}=p.getState().voice;n.logger.info({message:"Voice audio devices changed",api_name:"voice-mic",microphones:t.map(e=>e.label).join(", ")}),n.logger.debug({message:"Voice re-acquiring mic (device change)",api_name:"voice-rtvi",enabled:Q.current,device:e||"default"}),Y.current.updateMic(e)},300)))},onAvailableSpeakersUpdated:e=>{if(!t())return;if(g(x(e.map(e=>({deviceId:e.deviceId,label:e.label})))),!ee.current)return;const r=e.map(e=>e.label).join(", ");r!==te.current&&(te.current=r,n.logger.info({message:"Voice audio output devices changed",api_name:"voice-mic",speakers:r}))},onMicUpdated:e=>{if(!t())return;const s=Y.current;s&&s.isMicEnabled!==Q.current&&(n.logger.debug({message:"Voice re-asserting mic state after track swap",api_name:"voice-rtvi",conversation_id:r??void 0,enabled:Q.current,device:e?.label}),s.enableMic(Q.current))},onUserMuteStarted:()=>{t()&&n.logger.debug({message:"Voice RTVI user-mute-started (ignored)",api_name:"voice-rtvi",conversation_id:r??void 0})},onUserMuteStopped:()=>{t()&&(n.logger.debug({message:"Voice RTVI user-mute-stopped",api_name:"voice-rtvi",conversation_id:r??void 0,honored:d}),d&&g(_(!1)))},onUserStartedSpeaking:()=>{t()&&(n.logger.debug({message:"Voice RTVI user started speaking",api_name:"voice-rtvi",conversation_id:r??void 0}),H.current=null,$.current="",g(C(null)),g(v(o.LISTENING)))},onUserStoppedSpeaking:()=>{if(!t())return;n.logger.debug({message:"Voice RTVI user stopped speaking",api_name:"voice-rtvi",conversation_id:r??void 0,has_interim:!!$.current.trim()}),g(v(o.PROCESSING)),g(T(!0)),g(E(null)),ge();const e=$.current.trim();if(e){const t=`${G}${n.generateUniqueId()}`;H.current=t,g(C(null)),A.current.setMessages(r=>[...r,B(e,t)])}},onUserTranscript:e=>{if(!t())return;if(!e.final)return void(e.text&&($.current=e.text,K.current||g(C(e.text))));g(C(null));const s=e.text&&e.text.trim()?e.text:"";n.logger.debug({message:"Voice RTVI user transcript (final)",api_name:"voice-rtvi",conversation_id:r??void 0,has_text:!!s});const o=H.current;H.current=null,o?s&&A.current.setMessages(e=>e.map(e=>e.id===o?{...e,message:s}:e)):s&&A.current.setMessages(e=>[...e,B(s)])},onBotStartedSpeaking:()=>{t()&&(n.logger.debug({message:"Voice RTVI bot started speaking",api_name:"voice-rtvi",conversation_id:r??void 0}),Ce(),g(v(o.SPEAKING)),ve(n.INDICATOR_TIMER_ACTIONS.FINALIZE))},onBotOutput:e=>{t()&&be(e)},onBotStoppedSpeaking:()=>{t()&&(n.logger.debug({message:"Voice RTVI bot stopped speaking",api_name:"voice-rtvi",conversation_id:r??void 0}),Te(),g(v(o.IDLE)),ve(n.INDICATOR_TIMER_ACTIONS.FINALIZE),d&&p.getState().voice.serverMuted&&(n.logger.warn({message:"Voice greeting mute-hold released on bot-turn end (server unmute missing)",api_name:"voice-rtvi",conversation_id:r??void 0}),g(_(!1))))},onServerMessage:e=>{t()&&he(e)}}},[g,p,N,Se,fe,ve,ge,be,Ce,Te,he,Ie,d,r]),ye=e.useCallback(async()=>{if(Y.current||se.current)return;if(!r)return;if(se.current=!0,n.logger.info({message:"Voice RTVI connecting",api_name:"voice-rtvi",conversation_id:r,assistant_starts:d}),d&&(Q.current=!1,g(_(!0)),n.logger.debug({message:"Voice greeting mute-hold armed",api_name:"voice-rtvi",conversation_id:r})),n.isTokenAboutToExpire(n.readTokenExpiresAt())&&oe.current<1){oe.current+=1,n.logger.debug({message:"Voice pre-connect token refresh (freshness gate)",api_name:"voice-rtvi",conversation_id:r,freshness_refresh_count:oe.current});const e=ne.current;try{await Promise.resolve(n.getTokenRefreshHandler()?.())}catch{}if(e!==ne.current)return}const e=++re.current,t=ne.current;de.current=null,le.current=null,g(m(s.CONNECTING));const o=()=>t!==ne.current||e!==re.current;try{const[{PipecatClient:t,LogLevel:s,RTVIMessage:i},a]=await Promise.all([Promise.resolve().then(()=>require("./index.module-DCaqEXL5.cjs")),L(r)]);if(o())return;Z.current=i;const c=new t({transport:l(),enableMic:!0,enableCam:!1,callbacks:Ve(e)});if(c.setLogLevel(s.WARN),o())return void c.disconnect().catch(()=>{});Y.current=c,n.logger.debug({message:"Voice RTVI client constructed",api_name:"voice-rtvi",conversation_id:r,enable_mic:Q.current}),Se(),J.current=setTimeout(()=>{n.logger.warn({message:"Voice RTVI connect timed out (no bot-ready)",api_name:"voice-rtvi"}),n.showErrorToast("Voice connection timed out. Please try again."),g(M()),ke.current()},15e3),await c.connect({webrtcRequestParams:{endpoint:n.API_ENDPOINTS.WEBRTC_OFFER(r),headers:new Headers(P())},iceConfig:{iceServers:a}})}catch(t){const r=(e=>{const t="object"==typeof e&&null!==e&&"status"in e?e.status:void 0;return"number"==typeof t?t:void 0})(t);n.logger.error({message:"Voice RTVI connect failed",api_name:"voice-rtvi",status:r,error_message:t instanceof Error?t.message:String(t)});const s=(e=>void 0!==e&&e>=400&&e<500)(r),o=s?await(async(e,t)=>{const r=429===t?"Voice session limit reached. Please try again later.":U,n="object"==typeof e&&null!==e&&"cause"in e?e.cause:void 0;if(!(n instanceof Response))return r;try{const e=await n.json(),t=e?.data?.error_message??e?.data?.message;return"string"==typeof t?t:r}catch{return r}})(t,r):U;e===re.current&&(n.showErrorToast(o),s&&(ae.current&&(clearTimeout(ae.current),ae.current=null),ce.current=0,g(M())),fe(!1))}},[r,g,l,Ve,Se,fe,d]),xe=e.useCallback((e=!0)=>{n.logger.info({message:"Voice RTVI disconnect (intentional teardown)",api_name:"voice-rtvi",conversation_id:r??void 0,commit_turn:e}),ne.current+=1,re.current+=1,oe.current=0,ae.current&&(clearTimeout(ae.current),ae.current=null),ce.current=0,ue.current=0,le.current=null,Q.current=!0,g(S(!1));const t=Y.current;t?.disconnect().catch(e=>{n.logger.warn({message:"Voice RTVI disconnect threw",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}),fe(e)},[fe,g,r]),Me=e.useCallback(e=>{Q.current=e,Y.current&&(n.logger.debug({message:"Voice mic enable toggled",api_name:"voice-rtvi",enabled:e}),Y.current.enableMic(e))},[]),Ne=e.useCallback(e=>{g(R(e)),Y.current?.updateMic(e)},[g]),we=e.useCallback(e=>{g(V(e)),Y.current?.updateSpeaker(e)},[g]),Ae=e.useCallback((e,t)=>{const s=Y.current;if(!s)return;const i=t?.modelContext;n.logger.info({message:"Voice RTVI send-text",api_name:"voice-rtvi",conversation_id:r??void 0,run_immediately:!0,has_model_context:Boolean(i?.length)});try{const t=Z.current;i?.length&&t?s.transport.sendMessage(new t("send-text",{content:e,options:{run_immediately:!0,audio_response:!0},model_context:i})):s.sendText(e,{run_immediately:!0,audio_response:!0})}catch(e){return void n.logger.error({message:"Voice RTVI send-text failed",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}D.current&&Te(),A.current.setMessages(r=>[...r,B(e,void 0,t?.documents)]),g(T(!0)),g(E(null)),ge(),g(v(o.PROCESSING))},[g,ge,Te,r]);e.useEffect(()=>{ke.current=xe},[xe]),e.useEffect(()=>{Re.current=ye},[ye]);const De=e.useRef(r),Oe=e.useRef(!1);e.useEffect(()=>{const e=De.current;if(De.current=r,e===r)return;const t=se.current;t&&(Oe.current=!0),t&&xe(!1),Oe.current&&null!=r&&(Oe.current=!1,ye())},[r,xe,ye]);const Pe=e.useRef(w);return e.useEffect(()=>{const e=Pe.current;Pe.current=w,e&&!w&&se.current&&xe(!1)},[w,xe]),e.useEffect(()=>()=>{ke.current()},[]),{connect:ye,disconnect:xe,sendText:Ae,setMicEnabled:Me,setInputDevice:Ne,setOutputDevice:we}}(r,a,l,d);e.useEffect(()=>{$(!N&&!w)},[N,w,$]);const z=j();e.useEffect(()=>(a.setVoiceSendText(H),()=>a.setVoiceSendText(null)),[a,H]);const J=e.useRef(!1);return{displayState:z,pipelineStatus:A,isAgentWorking:D,enterVoiceMode:e.useCallback(async()=>{if(J.current)return n.showErrorToast("Microphone permission changed — please reload the page to apply the new setting."),!1;const e=await p.requestMicPermission();if(!e.granted){const t=e.error;return n.logger.error({message:"Mic permission denied",api_name:"voice-mic",error_message:t instanceof Error?t.message:t?String(t):void 0}),J.current=!0,n.showErrorToast("Microphone access is required for voice mode. Please allow it in your browser's site settings and reload the page."),!1}return K(),!0},[p,K]),exitVoiceMode:e.useCallback(()=>{W()},[W]),toggleMute:e.useCallback(()=>{g(S(!N))},[g,N]),sendText:H,muted:N,serverMuted:w,sessionEndedSignal:F,setInputDevice:Y,setOutputDevice:Z}},exports.useVoiceUI=()=>e.useContext(K),exports.voiceListenerMiddleware=W,exports.voiceReducer=A;
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createContext as e,useContext as t,useRef as n,useEffect as r,useCallback as i,useState as s}from"react";import{useDispatch as c,useStore as a,useSelector as o}from"react-redux";import{createSlice as u,createListenerMiddleware as d,isAnyOf as l}from"@reduxjs/toolkit";import{API_ENDPOINTS as m,logger as p,getCustomHeaders as g,readAccessToken as v,AUTH_HEADERS as _,readDeviceToken as I,DEVICE_TOKEN_HEADER as S,clearLastResponseTime as E,setLastResponseTime as f,INDICATOR_TIMER_ACTIONS as T,generateUniqueId as b,handleSharedEvent as h,showToast as y,getTokenRefreshHandler as k,showErrorToast as V,isTokenAboutToExpire as C,readTokenExpiresAt as M,getStorageRef as w,clearSession as R,sessionExpired as N}from"@1interface/shared-core";const D={DISCONNECTED:"disconnected",CONNECTING:"connecting",CONNECTED:"connected"},x={IDLE:"idle",LISTENING:"listening",PROCESSING:"processing",SPEAKING:"speaking"},A={CASCADE:"cascade",REALTIME:"realtime"},L={OFFLINE:"offline",CONNECTING:"connecting",READY:"ready",IDLE:"idle",LISTENING:"listening",MUTED:"muted",PROCESSING:"processing",SPEAKING:"speaking"},P=15e3,O=[/rejected/i,/unauthori[sz]ed/i,/forbidden/i,/\b40[13]\b/],G=[/\b402\b/,/\b429\b/,/rate[\s-]?limit/i],B={SELECTED_MIC_ID:"voice_selectedMicId",SELECTED_SPEAKER_ID:"voice_selectedSpeakerId"},j={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 U(){return{connectionStatus:D.DISCONNECTED,pipelineStatus:x.IDLE,listeningEnabled:!1,muted:!1,serverMuted:!1,statusText:null,isAgentWorking:!1,responseStartTime:null,interimTranscript:null,botTurn:{active:!1,chatMsgId:null,segments:[]},sessionEndedSignal:0,selectedMicId:"",selectedSpeakerId:"",microphones:[],speakers:[]}}const F=e=>async t=>{const[n,r]=await Promise.all([e.getItem(B.SELECTED_MIC_ID),e.getItem(B.SELECTED_SPEAKER_ID)]);n&&t(W.actions.setSelectedMicId(n)),r&&t(W.actions.setSelectedSpeakerId(r))},W=u({name:"voice",initialState:U(),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:n,text:r,spokenChars:i}=t.payload;e.botTurn.active=!0;const s=e.botTurn.segments.find(e=>e.id===n);s?(s.text=r,s.spokenChars=Math.max(s.spokenChars,i)):(e.botTurn.segments.push({id:n,text:r,spokenChars:i}),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:()=>U(),resetIndicatorState(e){e.statusText=null,e.isAgentWorking=!1,e.responseStartTime=null,e.interimTranscript=null,e.serverMuted=!1,e.botTurn={active:!1,chatMsgId:null,segments:[]}}}}),{setConnectionStatus:K,setPipelineStatus:q,setListeningEnabled:$,setMuted:Z,setServerMuted:H,setStatusText:Y,setIsAgentWorking:z,setResponseStartTime:J,setInterimTranscript:Q,openBotTurn:X,upsertBotSegment:ee,clearBotTurn:te,setSelectedMicId:ne,setSelectedSpeakerId:re,setMicrophones:ie,setSpeakers:se,signalSessionEnded:ce,resetVoiceState:ae,resetIndicatorState:oe}=W.actions,ue=W.reducer,de=e(null),le=()=>{const e=t(de);if(!e)throw new Error("useAudioPlatform must be used inside <AudioPlatformProvider>. <VoiceProvider> mounts one by default; if you replaced VoiceProvider, you must wire your own.");return e};function me(){const e={};for(const[t,n]of Object.entries(g()))n&&(e[t]=n);const t=v();t&&(e[_.ACCESS_TOKEN]=t);const n=I();return n&&(e[S]=n),e}async function pe(e){try{const t=await fetch(m.WEBRTC_ICE(e),{headers:me()});return t.ok?(await t.json()).iceServers??[]:(p.warn({message:"Voice ICE fetch failed — continuing without TURN",api_name:"voice-webrtc-ice",status:t.status}),[])}catch(e){return p.warn({message:"Voice ICE fetch error — continuing without TURN",api_name:"voice-webrtc-ice",error_message:e instanceof Error?e.message:String(e)}),[]}}const ge="voice-user-",ve="Could not connect voice. Please try again later.",_e=(e,t=`${ge}${b()}`)=>({id:t,sender:"user",message:e,timestamp:(new Date).toISOString()}),Ie=(e,t)=>{const n=`${e}${b()}`;return{id:n,msg_id:n,sender:"agent",message:"",timestamp:(new Date).toISOString(),...t}};function Se(){const e=o(e=>e.voice.connectionStatus),t=o(e=>e.voice.pipelineStatus),n=o(e=>e.voice.listeningEnabled),r=o(e=>e.voice.muted);return e===D.CONNECTING?L.CONNECTING:e!==D.CONNECTED?L.OFFLINE:n?r?L.MUTED:t===x.PROCESSING?L.PROCESSING:t===x.SPEAKING?L.SPEAKING:t===x.LISTENING?L.LISTENING:L.IDLE:L.READY}const Ee=e(null),fe=({conversationId:e,voiceBridge:t,transportFactory:u,assistantStartsConversation:d})=>{const l=c(),g=le(),v=o(e=>e.voice.muted),_=o(e=>e.voice.serverMuted),I=o(e=>e.voice.pipelineStatus),S=o(e=>e.voice.isAgentWorking),w=o(e=>e.voice.sessionEndedSignal),{connect:R,disconnect:N,sendText:L,setMicEnabled:P,setInputDevice:B,setOutputDevice:j}=function(e,t,u,d){const l=c(),g=a(),v=le(),_=o(e=>e.auth.accessToken),I=n(t);r(()=>{I.current=t});const S=n(null),w=n(new Map),R=n(""),N=n(!1),L=n(!1),P=n(null),B=n(""),j=n(null),U=n(null),F=n(null),W=n(!0),ae=n(null),oe=n(!1),ue=n(""),de=n(0),Se=n(0),Ee=n(!1),fe=n(0),Te=n(null),be=n(null),he=n(0),ye=n(0),ke=n(null),Ve=n(null),Ce=i(()=>{if(null!=Te.current)return;const e=Date.now();Te.current=e,l(J(e)),l(E())},[l]),Me=i(()=>{const e=Te.current;Te.current=null,l(J(null)),null!=e&&l(f(Date.now()-e))},[l]),we=i(()=>{Te.current=null,l(J(null))},[l]),Re=i((e=T.KEEP)=>{l(Y(null)),l(z(!1)),e===T.FINALIZE?Me():e===T.CANCEL&&we()},[l,Me,we]),[Ne]=s(()=>({setStatusText:e=>l(Y(e||null)),setIsAgentWorking:e=>l(z(e)),appendStreamText:()=>{},pushWidget:e=>{const t=Ie("voice-widget-",{widget:[e],message_type:["widget"]});I.current.setMessages(e=>[...e,t])},onPayment:e=>I.current.onPaymentInit?.(e),onConversationTitle:e=>I.current.onConversationTitle?.(e),onError:e=>{p.error({message:"Voice RTVI server error",api_name:"voice-rtvi",error_message:e});const t=Ie("voice-error-",{message:"Something went wrong. Please try again.",message_type:["error"]});I.current.setMessages(e=>[...e,t])},onStatusActive:()=>{Ce()}})),De=i(()=>{F.current&&(clearTimeout(F.current),F.current=null)},[]),xe=i(()=>[...w.current.entries()].sort((e,t)=>e[0]-t[0]).map(([,e])=>e.text).join(" ").trim(),[]),Ae=i(()=>{S.current=null,w.current=new Map,R.current="",l(te())},[l]),Le=i(()=>{const e=S.current;if(e){const t=xe();t&&I.current.updateStreamingMessage(e,t),I.current.commitStreamingMessages()}Ae()},[xe,Ae]),Pe=i(e=>{De(),ae.current&&(clearTimeout(ae.current),ae.current=null),oe.current=!1,ue.current="",l(ie([])),l(se([])),U.current?.(),U.current=null,j.current=null,Ee.current=!1,l($(!1)),l(q(x.IDLE)),l(K(D.DISCONNECTED)),l(Q(null)),l(H(!1)),P.current=null,B.current="",N.current=!1,L.current=!1,e?Le():(I.current.discardStreamingMessages(),Ae()),Re(T.CANCEL)},[l,Re,De,Le,Ae]),Oe=i(()=>{if(S.current)return;const e=`voice-agent-${b()}`;S.current=e,w.current=new Map,R.current="",l(X({chatMsgId:e}))},[l]),Ge=i(e=>{const t=e.spoken_progress?(e.spoken_progress?.accumulated_text??"")+(e.spoken_progress?.remaining_text??""):e.text??"";if(!t)return;Oe();const n=N.current?function(e,t){const n=(e.get(0)?.text??"")+t;return e.set(0,{text:n,completed:!1}),{id:0,text:n,spokenChars:n.length}}(w.current,t):function(e,t,n,r){const i=t.segment_id??0,s="completed"===t.spoken_status;if(s&&!e.has(i)){const t=[...e.entries()].find(([,e])=>e.text===n&&!e.completed);if(t)return t[1].completed=!0,{id:t[0],text:n,spokenChars:n.length}}const c=e.get(i);return e.set(i,{text:n,completed:s||!0===c?.completed}),{id:i,text:n,spokenChars:r&&t.spoken_progress?(t.spoken_progress.accumulated_text??"").length:n.length}}(w.current,e,t,L.current);l(ee(n));const r=xe();r&&S.current&&r!==R.current&&(R.current=r,I.current.updateStreamingMessage(S.current,r))},[l,Oe,xe]),Be=i(t=>{const n=t,r=n?.type,i=n?.data;switch(p.debug({message:"Voice RTVI server-message received",api_name:"voice-rtvi",conversation_id:e??void 0,event_type:r??"unknown"}),r){case"status_update":h({kind:"status_update",statusText:i?.text??""},Ne);break;case"started-thinking":l(z(!0)),Ce();break;case"stopped-thinking":Re(T.FINALIZE);break;case"clear_status":break;case"widget":i&&h({kind:"widget",widget:i},Ne);break;case"payment":i&&h({kind:"payment",payment:i},Ne);break;case"conversation_title":{const e="string"==typeof i?.text?i.text.trim():"";e&&h({kind:"conversation_title",title:e},Ne);break}case"session-ended":Ve.current={reason:"string"==typeof i?.reason?i.reason:"ended"};break;default:p.debug({message:"Voice RTVI unhandled server-message",api_name:"voice-rtvi",event_type:r??"unknown"})}},[Ne,l,Ce,Re,e]),je=n(()=>{}),Ue=n(async()=>{}),Fe=i(t=>{const n=()=>t===de.current;return{onConnected:()=>{if(!n())return;p.info({message:"Voice RTVI connected",api_name:"voice-rtvi",conversation_id:e??void 0}),l(K(D.CONNECTED)),l($(!0)),l(q(x.IDLE)),j.current&&j.current.isMicEnabled!==W.current&&(p.debug({message:"Voice re-asserting mic state on reconnect",api_name:"voice-rtvi",conversation_id:e??void 0,enabled:W.current}),j.current.enableMic(W.current));const{selectedMicId:t,selectedSpeakerId:r}=g.getState().voice;t&&j.current?.updateMic(t),r&&j.current?.updateSpeaker(r);const i=t=>n=>p.warn({message:"Voice device enumeration failed",api_name:"voice-mic",conversation_id:e??void 0,device_kind:t,error_message:n instanceof Error?n.message:String(n)});j.current?.getAllMics().then(e=>{n()&&l(ie(e.map(e=>({deviceId:e.deviceId,label:e.label}))))}).catch(i("mics")),j.current?.getAllSpeakers().then(e=>{n()&&l(se(e.map(e=>({deviceId:e.deviceId,label:e.label}))))}).catch(i("speakers")),oe.current=!0},onBotReady:t=>{if(!n())return;const r=t?.about;N.current=r?.pipeline_mode===A.REALTIME,L.current=!0===r?.tts_word_timestamps,he.current=0,ye.current=0,fe.current=0,p.info({message:"Voice RTVI bot-ready",api_name:"voice-rtvi",conversation_id:e??void 0,pipeline_mode:r?.pipeline_mode??"cascade",tts_word_timestamps:L.current,assistant_starts:d}),De(),d&&(l(z(!0)),Ce())},onDisconnected:()=>{if(!n())return;p.info({message:"Voice RTVI disconnected",api_name:"voice-rtvi",conversation_id:e??void 0,drop_outcome:ke.current??"transient",reconnect_attempts:he.current}),Pe(!0);const t=Se.current,r=ke.current;ke.current=null;const i=Ve.current;return Ve.current=null,i?(p.info({message:"Voice session ended by server — no reconnect",api_name:"voice-rtvi",conversation_id:e??void 0,reason:i.reason}),"idle_timeout"===i.reason&&y({title:"Voice ended",description:"It got quiet for a while, so we wrapped up. Start again anytime."}),void l(ce())):"terminal"===r?(p.warn({message:"Voice RTVI terminal error — no retry",api_name:"voice-rtvi"}),void l(ce())):"auth"===r?ye.current>=1||null==e?(p.warn({message:"Voice RTVI auth refresh budget exhausted — giving up",api_name:"voice-rtvi",conversation_id:e??void 0}),void l(ce())):(ye.current+=1,p.info({message:"Voice RTVI auth drop — refreshing token then reconnecting",api_name:"voice-rtvi",conversation_id:e??void 0,auth_refresh_count:ye.current}),void(async()=>{let n=!1;try{n=!!await(k()?.())}catch{}t===Se.current&&(n?Ue.current():(p.warn({message:"Voice RTVI token refresh failed — no reconnect",api_name:"voice-rtvi",conversation_id:e??void 0}),l(ce())))})()):he.current>=3||null==e?(he.current>=3&&(p.warn({message:"Voice RTVI auto-reconnect cap reached — giving up",api_name:"voice-rtvi",conversation_id:e??void 0,reconnect_attempts:he.current}),V("Voice connection lost. Please try again.")),void l(ce())):(he.current+=1,p.info({message:"Voice RTVI scheduling auto-reconnect",api_name:"voice-rtvi",conversation_id:e??void 0,reconnect_attempts:he.current,delay_ms:1e3}),be.current&&clearTimeout(be.current),void(be.current=setTimeout(()=>{be.current=null,Ue.current()},1e3)))},onError:t=>{if(!n())return;const r=t?.data,i=r?.error??r?.message??"Voice error";h({kind:"error",error:i},Ne),l(q(x.IDLE)),Re(T.CANCEL),r?.fatal&&(O.some(e=>e.test(i))?ke.current="auth":G.some(e=>e.test(i))&&(ke.current="terminal"),p.warn({message:"Voice RTVI fatal error — closing transport",api_name:"voice-rtvi",conversation_id:e??void 0,drop_outcome:ke.current??"generic"}),j.current?.disconnect().catch(()=>{}))},onTrackStarted:(t,r)=>{n()&&(r?.local||"audio"!==t.kind||(p.debug({message:"Voice RTVI bot audio track started",api_name:"voice-rtvi",conversation_id:e??void 0}),U.current?.(),U.current=v.attachBotAudioTrack(t)))},onAvailableMicsUpdated:e=>{n()&&(l(ie(e.map(e=>({deviceId:e.deviceId,label:e.label})))),oe.current&&(ae.current&&clearTimeout(ae.current),ae.current=setTimeout(()=>{if(ae.current=null,!j.current)return;const{selectedMicId:e,microphones:t}=g.getState().voice;p.info({message:"Voice audio devices changed",api_name:"voice-mic",microphones:t.map(e=>e.label).join(", ")}),p.debug({message:"Voice re-acquiring mic (device change)",api_name:"voice-rtvi",enabled:W.current,device:e||"default"}),j.current.updateMic(e)},300)))},onAvailableSpeakersUpdated:e=>{if(!n())return;if(l(se(e.map(e=>({deviceId:e.deviceId,label:e.label})))),!oe.current)return;const t=e.map(e=>e.label).join(", ");t!==ue.current&&(ue.current=t,p.info({message:"Voice audio output devices changed",api_name:"voice-mic",speakers:t}))},onMicUpdated:t=>{if(!n())return;const r=j.current;r&&r.isMicEnabled!==W.current&&(p.debug({message:"Voice re-asserting mic state after track swap",api_name:"voice-rtvi",conversation_id:e??void 0,enabled:W.current,device:t?.label}),r.enableMic(W.current))},onUserMuteStarted:()=>{n()&&p.debug({message:"Voice RTVI user-mute-started (ignored)",api_name:"voice-rtvi",conversation_id:e??void 0})},onUserMuteStopped:()=>{n()&&(p.debug({message:"Voice RTVI user-mute-stopped",api_name:"voice-rtvi",conversation_id:e??void 0,honored:d}),d&&l(H(!1)))},onUserStartedSpeaking:()=>{n()&&(p.debug({message:"Voice RTVI user started speaking",api_name:"voice-rtvi",conversation_id:e??void 0}),P.current=null,B.current="",l(Q(null)),l(q(x.LISTENING)))},onUserStoppedSpeaking:()=>{if(!n())return;p.debug({message:"Voice RTVI user stopped speaking",api_name:"voice-rtvi",conversation_id:e??void 0,has_interim:!!B.current.trim()}),l(q(x.PROCESSING)),l(z(!0)),l(Y(null)),Ce();const t=B.current.trim();if(t){const e=`${ge}${b()}`;P.current=e,l(Q(null)),I.current.setMessages(n=>[...n,_e(t,e)])}},onUserTranscript:t=>{if(!n())return;if(!t.final)return void(t.text&&(B.current=t.text,N.current||l(Q(t.text))));l(Q(null));const r=t.text&&t.text.trim()?t.text:"";p.debug({message:"Voice RTVI user transcript (final)",api_name:"voice-rtvi",conversation_id:e??void 0,has_text:!!r});const i=P.current;P.current=null,i?r&&I.current.setMessages(e=>e.map(e=>e.id===i?{...e,message:r}:e)):r&&I.current.setMessages(e=>[...e,_e(r)])},onBotStartedSpeaking:()=>{n()&&(p.debug({message:"Voice RTVI bot started speaking",api_name:"voice-rtvi",conversation_id:e??void 0}),Oe(),l(q(x.SPEAKING)),Re(T.FINALIZE))},onBotOutput:e=>{n()&&Ge(e)},onBotStoppedSpeaking:()=>{n()&&(p.debug({message:"Voice RTVI bot stopped speaking",api_name:"voice-rtvi",conversation_id:e??void 0}),Le(),l(q(x.IDLE)),Re(T.FINALIZE),d&&g.getState().voice.serverMuted&&(p.warn({message:"Voice greeting mute-hold released on bot-turn end (server unmute missing)",api_name:"voice-rtvi",conversation_id:e??void 0}),l(H(!1))))},onServerMessage:e=>{n()&&Be(e)}}},[l,g,v,De,Pe,Re,Ce,Ge,Oe,Le,Be,Ne,d,e]),We=i(async()=>{if(j.current||Ee.current)return;if(!e)return;if(Ee.current=!0,p.info({message:"Voice RTVI connecting",api_name:"voice-rtvi",conversation_id:e,assistant_starts:d}),d&&(W.current=!1,l(H(!0)),p.debug({message:"Voice greeting mute-hold armed",api_name:"voice-rtvi",conversation_id:e})),C(M())&&fe.current<1){fe.current+=1,p.debug({message:"Voice pre-connect token refresh (freshness gate)",api_name:"voice-rtvi",conversation_id:e,freshness_refresh_count:fe.current});const t=Se.current;try{await Promise.resolve(k()?.())}catch{}if(t!==Se.current)return}const t=++de.current,n=Se.current;Ve.current=null,ke.current=null,l(K(D.CONNECTING));const r=()=>n!==Se.current||t!==de.current;try{const[{PipecatClient:n,LogLevel:i},s]=await Promise.all([import("./index.module-S2alA3-r.js"),pe(e)]);if(r())return;const c=new n({transport:u(),enableMic:!0,enableCam:!1,callbacks:Fe(t)});if(c.setLogLevel(i.WARN),r())return void c.disconnect().catch(()=>{});j.current=c,p.debug({message:"Voice RTVI client constructed",api_name:"voice-rtvi",conversation_id:e,enable_mic:W.current}),De(),F.current=setTimeout(()=>{p.warn({message:"Voice RTVI connect timed out (no bot-ready)",api_name:"voice-rtvi"}),V("Voice connection timed out. Please try again."),l(ce()),je.current()},15e3),await c.connect({webrtcRequestParams:{endpoint:m.WEBRTC_OFFER(e),headers:new Headers(me())},iceConfig:{iceServers:s}})}catch(e){const n=(e=>{const t="object"==typeof e&&null!==e&&"status"in e?e.status:void 0;return"number"==typeof t?t:void 0})(e);p.error({message:"Voice RTVI connect failed",api_name:"voice-rtvi",status:n,error_message:e instanceof Error?e.message:String(e)});const r=(e=>void 0!==e&&e>=400&&e<500)(n),i=r?await(async(e,t)=>{const n=429===t?"Voice session limit reached. Please try again later.":ve,r="object"==typeof e&&null!==e&&"cause"in e?e.cause:void 0;if(!(r instanceof Response))return n;try{const e=await r.json(),t=e?.data?.error_message??e?.data?.message;return"string"==typeof t?t:n}catch{return n}})(e,n):ve;t===de.current&&(V(i),r&&(be.current&&(clearTimeout(be.current),be.current=null),he.current=0,l(ce())),Pe(!1))}},[e,l,u,Fe,De,Pe,d]),Ke=i((t=!0)=>{p.info({message:"Voice RTVI disconnect (intentional teardown)",api_name:"voice-rtvi",conversation_id:e??void 0,commit_turn:t}),Se.current+=1,de.current+=1,fe.current=0,be.current&&(clearTimeout(be.current),be.current=null),he.current=0,ye.current=0,ke.current=null,W.current=!0,l(Z(!1));const n=j.current;n?.disconnect().catch(e=>{p.warn({message:"Voice RTVI disconnect threw",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}),Pe(t)},[Pe,l,e]),qe=i(e=>{W.current=e,j.current&&(p.debug({message:"Voice mic enable toggled",api_name:"voice-rtvi",enabled:e}),j.current.enableMic(e))},[]),$e=i(e=>{l(ne(e)),j.current?.updateMic(e)},[l]),Ze=i(e=>{l(re(e)),j.current?.updateSpeaker(e)},[l]),He=i(t=>{const n=j.current;if(n){p.info({message:"Voice RTVI send-text",api_name:"voice-rtvi",conversation_id:e??void 0,run_immediately:!0});try{n.sendText(t,{run_immediately:!0,audio_response:!0})}catch(e){return void p.error({message:"Voice RTVI send-text failed",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}S.current&&Le(),I.current.setMessages(e=>[...e,_e(t)]),l(z(!0)),l(Y(null)),Ce(),l(q(x.PROCESSING))}},[l,Ce,Le,e]);r(()=>{je.current=Ke},[Ke]),r(()=>{Ue.current=We},[We]);const Ye=n(e),ze=n(!1);r(()=>{const t=Ye.current;if(Ye.current=e,t===e)return;const n=Ee.current;n&&(ze.current=!0),n&&Ke(!1),ze.current&&null!=e&&(ze.current=!1,We())},[e,Ke,We]);const Je=n(_);return r(()=>{const e=Je.current;Je.current=_,e&&!_&&Ee.current&&Ke(!1)},[_,Ke]),r(()=>()=>{je.current()},[]),{connect:We,disconnect:Ke,sendText:He,setMicEnabled:qe,setInputDevice:$e,setOutputDevice:Ze}}(e,t,u,d);r(()=>{P(!v&&!_)},[v,_,P]);const U=Se();r(()=>(t.setVoiceSendText(L),()=>t.setVoiceSendText(null)),[t,L]);const F=n(!1);return{displayState:U,pipelineStatus:I,isAgentWorking:S,enterVoiceMode:i(async()=>{if(F.current)return V("Microphone permission changed — please reload the page to apply the new setting."),!1;const e=await g.requestMicPermission();if(!e.granted){const t=e.error;return p.error({message:"Mic permission denied",api_name:"voice-mic",error_message:t instanceof Error?t.message:t?String(t):void 0}),F.current=!0,V("Microphone access is required for voice mode. Please allow it in your browser's site settings and reload the page."),!1}return R(),!0},[g,R]),exitVoiceMode:i(()=>{N()},[N]),toggleMute:i(()=>{l(Z(!v))},[l,v]),sendText:L,muted:v,serverMuted:_,sessionEndedSignal:w,setInputDevice:B,setOutputDevice:j}},Te=()=>{const e=t(Ee);if(!e)throw new Error("useVoice must be used within VoiceProvider");return e},be=e({strategyActive:!1,voiceSlot:null,onEnterVoice:null,onFloatingOrbClick:null,voiceInputHidden:!1,headerCenterSlot:null,floatOrbPositionRef:null}),he=()=>t(be);function ye(){return{microphones:o(e=>e.voice.microphones),speakers:o(e=>e.voice.speakers),selectedMicId:o(e=>e.voice.selectedMicId),selectedSpeakerId:o(e=>e.voice.selectedSpeakerId)}}const ke=d();ke.startListening({actionCreator:ne,effect:async e=>{const t=w();t&&await t.setItem(B.SELECTED_MIC_ID,e.payload)}}),ke.startListening({actionCreator:re,effect:async e=>{const t=w();t&&await t.setItem(B.SELECTED_SPEAKER_ID,e.payload)}}),ke.startListening({matcher:l(R,N),effect:async(e,t)=>{const n=w();n&&await Promise.all([n.removeItem(B.SELECTED_MIC_ID),n.removeItem(B.SELECTED_SPEAKER_ID)]),t.dispatch(ae())}});const Ve=e=>{const t=document.createElement("audio");return t.autoplay=!0,t.style.display="none",t.srcObject=new MediaStream([e]),document.body.appendChild(t),()=>{try{t.pause(),t.srcObject=null,t.remove()}catch{}}},Ce={attachBotAudioTrack:Ve,requestMicPermission:async function(){try{const e=await(navigator.permissions?.query({name:"microphone"}).catch(()=>null));return p.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 p.warn({message:"Voice mic permission denied",api_name:"voice-mic",error_message:e instanceof Error?e.message:String(e)}),{granted:!1,error:e}}}};export{de as AudioPlatformContext,j as ORB_STATE_CONFIG,D as VOICE_CONNECTION_STATUSES,P as VOICE_CONNECT_TIMEOUT_MS,L as VOICE_DISPLAY_STATES,A as VOICE_PIPELINE_MODES,x as VOICE_PIPELINE_STATUSES,B as VOICE_STORAGE_KEYS,Ee as VoiceContext,be as VoiceUIContext,Ve as attachBrowserBotAudioTrack,Ce as browserAudioPlatform,te as clearBotTurn,F as hydrateVoice,X as openBotTurn,oe as resetIndicatorState,K as setConnectionStatus,Q as setInterimTranscript,z as setIsAgentWorking,$ as setListeningEnabled,Z as setMuted,q as setPipelineStatus,J as setResponseStartTime,ne as setSelectedMicId,re as setSelectedSpeakerId,H as setServerMuted,Y as setStatusText,ee as upsertBotSegment,le as useAudioPlatform,ye as useMediaDevices,Te as useVoice,Se as useVoiceDisplayState,fe as useVoiceProviderValue,he as useVoiceUI,ke as voiceListenerMiddleware,ue as voiceReducer};
|
|
1
|
+
import{createContext as e,useContext as t,useRef as n,useEffect as r,useCallback as i,useState as s}from"react";import{useDispatch as c,useStore as o,useSelector as a}from"react-redux";import{createSlice as u,createListenerMiddleware as d,isAnyOf as l}from"@reduxjs/toolkit";import{API_ENDPOINTS as m,logger as p,getCustomHeaders as g,readAccessToken as v,AUTH_HEADERS as _,readDeviceToken as I,DEVICE_TOKEN_HEADER as S,clearLastResponseTime as E,setLastResponseTime as f,INDICATOR_TIMER_ACTIONS as T,generateUniqueId as b,handleSharedEvent as h,showToast as y,getTokenRefreshHandler as k,showErrorToast as V,isTokenAboutToExpire as M,readTokenExpiresAt as C,getStorageRef as w,clearSession as R,sessionExpired as N}from"@1interface/shared-core";const D={DISCONNECTED:"disconnected",CONNECTING:"connecting",CONNECTED:"connected"},x={IDLE:"idle",LISTENING:"listening",PROCESSING:"processing",SPEAKING:"speaking"},A={CASCADE:"cascade",REALTIME:"realtime"},L={OFFLINE:"offline",CONNECTING:"connecting",READY:"ready",IDLE:"idle",LISTENING:"listening",MUTED:"muted",PROCESSING:"processing",SPEAKING:"speaking"},P=15e3,O=[/rejected/i,/unauthori[sz]ed/i,/forbidden/i,/\b40[13]\b/],G=[/\b402\b/,/\b429\b/,/rate[\s-]?limit/i],B={SELECTED_MIC_ID:"voice_selectedMicId",SELECTED_SPEAKER_ID:"voice_selectedSpeakerId"},j={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 U(){return{connectionStatus:D.DISCONNECTED,pipelineStatus:x.IDLE,listeningEnabled:!1,muted:!1,serverMuted:!1,statusText:null,isAgentWorking:!1,responseStartTime:null,interimTranscript:null,botTurn:{active:!1,chatMsgId:null,segments:[]},sessionEndedSignal:0,selectedMicId:"",selectedSpeakerId:"",microphones:[],speakers:[]}}const q=e=>async t=>{const[n,r]=await Promise.all([e.getItem(B.SELECTED_MIC_ID),e.getItem(B.SELECTED_SPEAKER_ID)]);n&&t(F.actions.setSelectedMicId(n)),r&&t(F.actions.setSelectedSpeakerId(r))},F=u({name:"voice",initialState:U(),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:n,text:r,spokenChars:i}=t.payload;e.botTurn.active=!0;const s=e.botTurn.segments.find(e=>e.id===n);s?(s.text=r,s.spokenChars=Math.max(s.spokenChars,i)):(e.botTurn.segments.push({id:n,text:r,spokenChars:i}),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:()=>U(),resetIndicatorState(e){e.statusText=null,e.isAgentWorking=!1,e.responseStartTime=null,e.interimTranscript=null,e.serverMuted=!1,e.botTurn={active:!1,chatMsgId:null,segments:[]}}}}),{setConnectionStatus:W,setPipelineStatus:K,setListeningEnabled:$,setMuted:Z,setServerMuted:H,setStatusText:Y,setIsAgentWorking:z,setResponseStartTime:J,setInterimTranscript:Q,openBotTurn:X,upsertBotSegment:ee,clearBotTurn:te,setSelectedMicId:ne,setSelectedSpeakerId:re,setMicrophones:ie,setSpeakers:se,signalSessionEnded:ce,resetVoiceState:oe,resetIndicatorState:ae}=F.actions,ue=F.reducer,de=e(null),le=()=>{const e=t(de);if(!e)throw new Error("useAudioPlatform must be used inside <AudioPlatformProvider>. <VoiceProvider> mounts one by default; if you replaced VoiceProvider, you must wire your own.");return e};function me(){const e={};for(const[t,n]of Object.entries(g()))n&&(e[t]=n);const t=v();t&&(e[_.ACCESS_TOKEN]=t);const n=I();return n&&(e[S]=n),e}async function pe(e){try{const t=await fetch(m.WEBRTC_ICE(e),{headers:me()});return t.ok?(await t.json()).iceServers??[]:(p.warn({message:"Voice ICE fetch failed — continuing without TURN",api_name:"voice-webrtc-ice",status:t.status}),[])}catch(e){return p.warn({message:"Voice ICE fetch error — continuing without TURN",api_name:"voice-webrtc-ice",error_message:e instanceof Error?e.message:String(e)}),[]}}const ge="voice-user-",ve="Could not connect voice. Please try again later.",_e=(e,t=`${ge}${b()}`,n)=>({id:t,sender:"user",message:e,timestamp:(new Date).toISOString(),...n?.length?{documents:n}:{}}),Ie=(e,t)=>{const n=`${e}${b()}`;return{id:n,msg_id:n,sender:"agent",message:"",timestamp:(new Date).toISOString(),...t}};function Se(){const e=a(e=>e.voice.connectionStatus),t=a(e=>e.voice.pipelineStatus),n=a(e=>e.voice.listeningEnabled),r=a(e=>e.voice.muted);return e===D.CONNECTING?L.CONNECTING:e!==D.CONNECTED?L.OFFLINE:n?r?L.MUTED:t===x.PROCESSING?L.PROCESSING:t===x.SPEAKING?L.SPEAKING:t===x.LISTENING?L.LISTENING:L.IDLE:L.READY}const Ee=e(null),fe=({conversationId:e,voiceBridge:t,transportFactory:u,assistantStartsConversation:d})=>{const l=c(),g=le(),v=a(e=>e.voice.muted),_=a(e=>e.voice.serverMuted),I=a(e=>e.voice.pipelineStatus),S=a(e=>e.voice.isAgentWorking),w=a(e=>e.voice.sessionEndedSignal),{connect:R,disconnect:N,sendText:L,setMicEnabled:P,setInputDevice:B,setOutputDevice:j}=function(e,t,u,d){const l=c(),g=o(),v=le(),_=a(e=>e.auth.accessToken),I=n(t);r(()=>{I.current=t});const S=n(null),w=n(new Map),R=n(""),N=n(!1),L=n(!1),P=n(null),B=n(""),j=n(null),U=n(null),q=n(null),F=n(null),oe=n(!0),ae=n(null),ue=n(!1),de=n(""),Se=n(0),Ee=n(0),fe=n(!1),Te=n(0),be=n(null),he=n(null),ye=n(0),ke=n(0),Ve=n(null),Me=n(null),Ce=i(()=>{if(null!=be.current)return;const e=Date.now();be.current=e,l(J(e)),l(E())},[l]),we=i(()=>{const e=be.current;be.current=null,l(J(null)),null!=e&&l(f(Date.now()-e))},[l]),Re=i(()=>{be.current=null,l(J(null))},[l]),Ne=i((e=T.KEEP)=>{l(Y(null)),l(z(!1)),e===T.FINALIZE?we():e===T.CANCEL&&Re()},[l,we,Re]),[De]=s(()=>({setStatusText:e=>l(Y(e||null)),setIsAgentWorking:e=>l(z(e)),appendStreamText:()=>{},pushWidget:e=>{const t=Ie("voice-widget-",{widget:[e],message_type:["widget"]});I.current.setMessages(e=>[...e,t])},onPayment:e=>I.current.onPaymentInit?.(e),onRequestDocuments:e=>I.current.onRequestDocuments?.(e),onConversationTitle:e=>I.current.onConversationTitle?.(e),onError:e=>{p.error({message:"Voice RTVI server error",api_name:"voice-rtvi",error_message:e});const t=Ie("voice-error-",{message:"Something went wrong. Please try again.",message_type:["error"]});I.current.setMessages(e=>[...e,t])},onStatusActive:()=>{Ce()}})),xe=i(()=>{F.current&&(clearTimeout(F.current),F.current=null)},[]),Ae=i(()=>[...w.current.entries()].sort((e,t)=>e[0]-t[0]).map(([,e])=>e.text).join(" ").trim(),[]),Le=i(()=>{S.current=null,w.current=new Map,R.current="",l(te())},[l]),Pe=i(()=>{const e=S.current;if(e){const t=Ae();t&&I.current.updateStreamingMessage(e,t),I.current.commitStreamingMessages()}Le()},[Ae,Le]),Oe=i(e=>{xe(),ae.current&&(clearTimeout(ae.current),ae.current=null),ue.current=!1,de.current="",l(ie([])),l(se([])),q.current?.(),q.current=null,j.current=null,fe.current=!1,l($(!1)),l(K(x.IDLE)),l(W(D.DISCONNECTED)),l(Q(null)),l(H(!1)),P.current=null,B.current="",N.current=!1,L.current=!1,e?Pe():(I.current.discardStreamingMessages(),Le()),Ne(T.CANCEL)},[l,Ne,xe,Pe,Le]),Ge=i(()=>{if(S.current)return;const e=`voice-agent-${b()}`;S.current=e,w.current=new Map,R.current="",l(X({chatMsgId:e}))},[l]),Be=i(e=>{const t=e.spoken_progress?(e.spoken_progress?.accumulated_text??"")+(e.spoken_progress?.remaining_text??""):e.text??"";if(!t)return;Ge();const n=N.current?function(e,t){const n=(e.get(0)?.text??"")+t;return e.set(0,{text:n,completed:!1}),{id:0,text:n,spokenChars:n.length}}(w.current,t):function(e,t,n,r){const i=t.segment_id??0,s="completed"===t.spoken_status;if(s&&!e.has(i)){const t=[...e.entries()].find(([,e])=>e.text===n&&!e.completed);if(t)return t[1].completed=!0,{id:t[0],text:n,spokenChars:n.length}}const c=e.get(i);return e.set(i,{text:n,completed:s||!0===c?.completed}),{id:i,text:n,spokenChars:r&&t.spoken_progress?(t.spoken_progress.accumulated_text??"").length:n.length}}(w.current,e,t,L.current);l(ee(n));const r=Ae();r&&S.current&&r!==R.current&&(R.current=r,I.current.updateStreamingMessage(S.current,r))},[l,Ge,Ae]),je=i(t=>{const n=t,r=n?.type,i=n?.data;switch(p.debug({message:"Voice RTVI server-message received",api_name:"voice-rtvi",conversation_id:e??void 0,event_type:r??"unknown"}),r){case"status_update":h({kind:"status_update",statusText:i?.text??""},De);break;case"started-thinking":l(z(!0)),Ce();break;case"stopped-thinking":Ne(T.FINALIZE);break;case"clear_status":break;case"widget":i&&h({kind:"widget",widget:i},De);break;case"payment":i&&h({kind:"payment",payment:i},De);break;case"request_documents":i&&h({kind:"request_documents",requestDocuments:i},De);break;case"conversation_title":{const e="string"==typeof i?.text?i.text.trim():"";e&&h({kind:"conversation_title",title:e},De);break}case"session-ended":Me.current={reason:"string"==typeof i?.reason?i.reason:"ended"};break;default:p.debug({message:"Voice RTVI unhandled server-message",api_name:"voice-rtvi",event_type:r??"unknown"})}},[De,l,Ce,Ne,e]),Ue=n(()=>{}),qe=n(async()=>{}),Fe=i(t=>{const n=()=>t===Se.current;return{onConnected:()=>{if(!n())return;p.info({message:"Voice RTVI connected",api_name:"voice-rtvi",conversation_id:e??void 0}),l(W(D.CONNECTED)),l($(!0)),l(K(x.IDLE)),j.current&&j.current.isMicEnabled!==oe.current&&(p.debug({message:"Voice re-asserting mic state on reconnect",api_name:"voice-rtvi",conversation_id:e??void 0,enabled:oe.current}),j.current.enableMic(oe.current));const{selectedMicId:t,selectedSpeakerId:r}=g.getState().voice;t&&j.current?.updateMic(t),r&&j.current?.updateSpeaker(r);const i=t=>n=>p.warn({message:"Voice device enumeration failed",api_name:"voice-mic",conversation_id:e??void 0,device_kind:t,error_message:n instanceof Error?n.message:String(n)});j.current?.getAllMics().then(e=>{n()&&l(ie(e.map(e=>({deviceId:e.deviceId,label:e.label}))))}).catch(i("mics")),j.current?.getAllSpeakers().then(e=>{n()&&l(se(e.map(e=>({deviceId:e.deviceId,label:e.label}))))}).catch(i("speakers")),ue.current=!0},onBotReady:t=>{if(!n())return;const r=t?.about;N.current=r?.pipeline_mode===A.REALTIME,L.current=!0===r?.tts_word_timestamps,ye.current=0,ke.current=0,Te.current=0,p.info({message:"Voice RTVI bot-ready",api_name:"voice-rtvi",conversation_id:e??void 0,pipeline_mode:r?.pipeline_mode??"cascade",tts_word_timestamps:L.current,assistant_starts:d}),xe(),d&&(l(z(!0)),Ce())},onDisconnected:()=>{if(!n())return;p.info({message:"Voice RTVI disconnected",api_name:"voice-rtvi",conversation_id:e??void 0,drop_outcome:Ve.current??"transient",reconnect_attempts:ye.current}),Oe(!0);const t=Ee.current,r=Ve.current;Ve.current=null;const i=Me.current;return Me.current=null,i?(p.info({message:"Voice session ended by server — no reconnect",api_name:"voice-rtvi",conversation_id:e??void 0,reason:i.reason}),"idle_timeout"===i.reason&&y({title:"Voice ended",description:"It got quiet for a while, so we wrapped up. Start again anytime."}),void l(ce())):"terminal"===r?(p.warn({message:"Voice RTVI terminal error — no retry",api_name:"voice-rtvi"}),void l(ce())):"auth"===r?ke.current>=1||null==e?(p.warn({message:"Voice RTVI auth refresh budget exhausted — giving up",api_name:"voice-rtvi",conversation_id:e??void 0}),void l(ce())):(ke.current+=1,p.info({message:"Voice RTVI auth drop — refreshing token then reconnecting",api_name:"voice-rtvi",conversation_id:e??void 0,auth_refresh_count:ke.current}),void(async()=>{let n=!1;try{n=!!await(k()?.())}catch{}t===Ee.current&&(n?qe.current():(p.warn({message:"Voice RTVI token refresh failed — no reconnect",api_name:"voice-rtvi",conversation_id:e??void 0}),l(ce())))})()):ye.current>=3||null==e?(ye.current>=3&&(p.warn({message:"Voice RTVI auto-reconnect cap reached — giving up",api_name:"voice-rtvi",conversation_id:e??void 0,reconnect_attempts:ye.current}),V("Voice connection lost. Please try again.")),void l(ce())):(ye.current+=1,p.info({message:"Voice RTVI scheduling auto-reconnect",api_name:"voice-rtvi",conversation_id:e??void 0,reconnect_attempts:ye.current,delay_ms:1e3}),he.current&&clearTimeout(he.current),void(he.current=setTimeout(()=>{he.current=null,qe.current()},1e3)))},onError:t=>{if(!n())return;const r=t?.data,i=r?.error??r?.message??"Voice error";h({kind:"error",error:i},De),l(K(x.IDLE)),Ne(T.CANCEL),r?.fatal&&(O.some(e=>e.test(i))?Ve.current="auth":G.some(e=>e.test(i))&&(Ve.current="terminal"),p.warn({message:"Voice RTVI fatal error — closing transport",api_name:"voice-rtvi",conversation_id:e??void 0,drop_outcome:Ve.current??"generic"}),j.current?.disconnect().catch(()=>{}))},onTrackStarted:(t,r)=>{n()&&(r?.local||"audio"!==t.kind||(p.debug({message:"Voice RTVI bot audio track started",api_name:"voice-rtvi",conversation_id:e??void 0}),q.current?.(),q.current=v.attachBotAudioTrack(t)))},onAvailableMicsUpdated:e=>{n()&&(l(ie(e.map(e=>({deviceId:e.deviceId,label:e.label})))),ue.current&&(ae.current&&clearTimeout(ae.current),ae.current=setTimeout(()=>{if(ae.current=null,!j.current)return;const{selectedMicId:e,microphones:t}=g.getState().voice;p.info({message:"Voice audio devices changed",api_name:"voice-mic",microphones:t.map(e=>e.label).join(", ")}),p.debug({message:"Voice re-acquiring mic (device change)",api_name:"voice-rtvi",enabled:oe.current,device:e||"default"}),j.current.updateMic(e)},300)))},onAvailableSpeakersUpdated:e=>{if(!n())return;if(l(se(e.map(e=>({deviceId:e.deviceId,label:e.label})))),!ue.current)return;const t=e.map(e=>e.label).join(", ");t!==de.current&&(de.current=t,p.info({message:"Voice audio output devices changed",api_name:"voice-mic",speakers:t}))},onMicUpdated:t=>{if(!n())return;const r=j.current;r&&r.isMicEnabled!==oe.current&&(p.debug({message:"Voice re-asserting mic state after track swap",api_name:"voice-rtvi",conversation_id:e??void 0,enabled:oe.current,device:t?.label}),r.enableMic(oe.current))},onUserMuteStarted:()=>{n()&&p.debug({message:"Voice RTVI user-mute-started (ignored)",api_name:"voice-rtvi",conversation_id:e??void 0})},onUserMuteStopped:()=>{n()&&(p.debug({message:"Voice RTVI user-mute-stopped",api_name:"voice-rtvi",conversation_id:e??void 0,honored:d}),d&&l(H(!1)))},onUserStartedSpeaking:()=>{n()&&(p.debug({message:"Voice RTVI user started speaking",api_name:"voice-rtvi",conversation_id:e??void 0}),P.current=null,B.current="",l(Q(null)),l(K(x.LISTENING)))},onUserStoppedSpeaking:()=>{if(!n())return;p.debug({message:"Voice RTVI user stopped speaking",api_name:"voice-rtvi",conversation_id:e??void 0,has_interim:!!B.current.trim()}),l(K(x.PROCESSING)),l(z(!0)),l(Y(null)),Ce();const t=B.current.trim();if(t){const e=`${ge}${b()}`;P.current=e,l(Q(null)),I.current.setMessages(n=>[...n,_e(t,e)])}},onUserTranscript:t=>{if(!n())return;if(!t.final)return void(t.text&&(B.current=t.text,N.current||l(Q(t.text))));l(Q(null));const r=t.text&&t.text.trim()?t.text:"";p.debug({message:"Voice RTVI user transcript (final)",api_name:"voice-rtvi",conversation_id:e??void 0,has_text:!!r});const i=P.current;P.current=null,i?r&&I.current.setMessages(e=>e.map(e=>e.id===i?{...e,message:r}:e)):r&&I.current.setMessages(e=>[...e,_e(r)])},onBotStartedSpeaking:()=>{n()&&(p.debug({message:"Voice RTVI bot started speaking",api_name:"voice-rtvi",conversation_id:e??void 0}),Ge(),l(K(x.SPEAKING)),Ne(T.FINALIZE))},onBotOutput:e=>{n()&&Be(e)},onBotStoppedSpeaking:()=>{n()&&(p.debug({message:"Voice RTVI bot stopped speaking",api_name:"voice-rtvi",conversation_id:e??void 0}),Pe(),l(K(x.IDLE)),Ne(T.FINALIZE),d&&g.getState().voice.serverMuted&&(p.warn({message:"Voice greeting mute-hold released on bot-turn end (server unmute missing)",api_name:"voice-rtvi",conversation_id:e??void 0}),l(H(!1))))},onServerMessage:e=>{n()&&je(e)}}},[l,g,v,xe,Oe,Ne,Ce,Be,Ge,Pe,je,De,d,e]),We=i(async()=>{if(j.current||fe.current)return;if(!e)return;if(fe.current=!0,p.info({message:"Voice RTVI connecting",api_name:"voice-rtvi",conversation_id:e,assistant_starts:d}),d&&(oe.current=!1,l(H(!0)),p.debug({message:"Voice greeting mute-hold armed",api_name:"voice-rtvi",conversation_id:e})),M(C())&&Te.current<1){Te.current+=1,p.debug({message:"Voice pre-connect token refresh (freshness gate)",api_name:"voice-rtvi",conversation_id:e,freshness_refresh_count:Te.current});const t=Ee.current;try{await Promise.resolve(k()?.())}catch{}if(t!==Ee.current)return}const t=++Se.current,n=Ee.current;Me.current=null,Ve.current=null,l(W(D.CONNECTING));const r=()=>n!==Ee.current||t!==Se.current;try{const[{PipecatClient:n,LogLevel:i,RTVIMessage:s},c]=await Promise.all([import("./index.module-S2alA3-r.js"),pe(e)]);if(r())return;U.current=s;const o=new n({transport:u(),enableMic:!0,enableCam:!1,callbacks:Fe(t)});if(o.setLogLevel(i.WARN),r())return void o.disconnect().catch(()=>{});j.current=o,p.debug({message:"Voice RTVI client constructed",api_name:"voice-rtvi",conversation_id:e,enable_mic:oe.current}),xe(),F.current=setTimeout(()=>{p.warn({message:"Voice RTVI connect timed out (no bot-ready)",api_name:"voice-rtvi"}),V("Voice connection timed out. Please try again."),l(ce()),Ue.current()},15e3),await o.connect({webrtcRequestParams:{endpoint:m.WEBRTC_OFFER(e),headers:new Headers(me())},iceConfig:{iceServers:c}})}catch(e){const n=(e=>{const t="object"==typeof e&&null!==e&&"status"in e?e.status:void 0;return"number"==typeof t?t:void 0})(e);p.error({message:"Voice RTVI connect failed",api_name:"voice-rtvi",status:n,error_message:e instanceof Error?e.message:String(e)});const r=(e=>void 0!==e&&e>=400&&e<500)(n),i=r?await(async(e,t)=>{const n=429===t?"Voice session limit reached. Please try again later.":ve,r="object"==typeof e&&null!==e&&"cause"in e?e.cause:void 0;if(!(r instanceof Response))return n;try{const e=await r.json(),t=e?.data?.error_message??e?.data?.message;return"string"==typeof t?t:n}catch{return n}})(e,n):ve;t===Se.current&&(V(i),r&&(he.current&&(clearTimeout(he.current),he.current=null),ye.current=0,l(ce())),Oe(!1))}},[e,l,u,Fe,xe,Oe,d]),Ke=i((t=!0)=>{p.info({message:"Voice RTVI disconnect (intentional teardown)",api_name:"voice-rtvi",conversation_id:e??void 0,commit_turn:t}),Ee.current+=1,Se.current+=1,Te.current=0,he.current&&(clearTimeout(he.current),he.current=null),ye.current=0,ke.current=0,Ve.current=null,oe.current=!0,l(Z(!1));const n=j.current;n?.disconnect().catch(e=>{p.warn({message:"Voice RTVI disconnect threw",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}),Oe(t)},[Oe,l,e]),$e=i(e=>{oe.current=e,j.current&&(p.debug({message:"Voice mic enable toggled",api_name:"voice-rtvi",enabled:e}),j.current.enableMic(e))},[]),Ze=i(e=>{l(ne(e)),j.current?.updateMic(e)},[l]),He=i(e=>{l(re(e)),j.current?.updateSpeaker(e)},[l]),Ye=i((t,n)=>{const r=j.current;if(!r)return;const i=n?.modelContext;p.info({message:"Voice RTVI send-text",api_name:"voice-rtvi",conversation_id:e??void 0,run_immediately:!0,has_model_context:Boolean(i?.length)});try{const e=U.current;i?.length&&e?r.transport.sendMessage(new e("send-text",{content:t,options:{run_immediately:!0,audio_response:!0},model_context:i})):r.sendText(t,{run_immediately:!0,audio_response:!0})}catch(e){return void p.error({message:"Voice RTVI send-text failed",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}S.current&&Pe(),I.current.setMessages(e=>[...e,_e(t,void 0,n?.documents)]),l(z(!0)),l(Y(null)),Ce(),l(K(x.PROCESSING))},[l,Ce,Pe,e]);r(()=>{Ue.current=Ke},[Ke]),r(()=>{qe.current=We},[We]);const ze=n(e),Je=n(!1);r(()=>{const t=ze.current;if(ze.current=e,t===e)return;const n=fe.current;n&&(Je.current=!0),n&&Ke(!1),Je.current&&null!=e&&(Je.current=!1,We())},[e,Ke,We]);const Qe=n(_);return r(()=>{const e=Qe.current;Qe.current=_,e&&!_&&fe.current&&Ke(!1)},[_,Ke]),r(()=>()=>{Ue.current()},[]),{connect:We,disconnect:Ke,sendText:Ye,setMicEnabled:$e,setInputDevice:Ze,setOutputDevice:He}}(e,t,u,d);r(()=>{P(!v&&!_)},[v,_,P]);const U=Se();r(()=>(t.setVoiceSendText(L),()=>t.setVoiceSendText(null)),[t,L]);const q=n(!1);return{displayState:U,pipelineStatus:I,isAgentWorking:S,enterVoiceMode:i(async()=>{if(q.current)return V("Microphone permission changed — please reload the page to apply the new setting."),!1;const e=await g.requestMicPermission();if(!e.granted){const t=e.error;return p.error({message:"Mic permission denied",api_name:"voice-mic",error_message:t instanceof Error?t.message:t?String(t):void 0}),q.current=!0,V("Microphone access is required for voice mode. Please allow it in your browser's site settings and reload the page."),!1}return R(),!0},[g,R]),exitVoiceMode:i(()=>{N()},[N]),toggleMute:i(()=>{l(Z(!v))},[l,v]),sendText:L,muted:v,serverMuted:_,sessionEndedSignal:w,setInputDevice:B,setOutputDevice:j}},Te=()=>{const e=t(Ee);if(!e)throw new Error("useVoice must be used within VoiceProvider");return e},be=e({strategyActive:!1,voiceSlot:null,onEnterVoice:null,onFloatingOrbClick:null,voiceInputHidden:!1,headerCenterSlot:null,floatOrbPositionRef:null}),he=()=>t(be);function ye(){return{microphones:a(e=>e.voice.microphones),speakers:a(e=>e.voice.speakers),selectedMicId:a(e=>e.voice.selectedMicId),selectedSpeakerId:a(e=>e.voice.selectedSpeakerId)}}const ke=d();ke.startListening({actionCreator:ne,effect:async e=>{const t=w();t&&await t.setItem(B.SELECTED_MIC_ID,e.payload)}}),ke.startListening({actionCreator:re,effect:async e=>{const t=w();t&&await t.setItem(B.SELECTED_SPEAKER_ID,e.payload)}}),ke.startListening({matcher:l(R,N),effect:async(e,t)=>{const n=w();n&&await Promise.all([n.removeItem(B.SELECTED_MIC_ID),n.removeItem(B.SELECTED_SPEAKER_ID)]),t.dispatch(oe())}});const Ve=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{}}},Me={attachBotAudioTrack:Ve,requestMicPermission:async function(){try{const e=await(navigator.permissions?.query({name:"microphone"}).catch(()=>null));return p.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 p.warn({message:"Voice mic permission denied",api_name:"voice-mic",error_message:e instanceof Error?e.message:String(e)}),{granted:!1,error:e}}}};export{de as AudioPlatformContext,j as ORB_STATE_CONFIG,D as VOICE_CONNECTION_STATUSES,P as VOICE_CONNECT_TIMEOUT_MS,L as VOICE_DISPLAY_STATES,A as VOICE_PIPELINE_MODES,x as VOICE_PIPELINE_STATUSES,B as VOICE_STORAGE_KEYS,Ee as VoiceContext,be as VoiceUIContext,Ve as attachBrowserBotAudioTrack,Me as browserAudioPlatform,te as clearBotTurn,q as hydrateVoice,X as openBotTurn,ae as resetIndicatorState,W as setConnectionStatus,Q as setInterimTranscript,z as setIsAgentWorking,$ as setListeningEnabled,Z as setMuted,K as setPipelineStatus,J as setResponseStartTime,ne as setSelectedMicId,re as setSelectedSpeakerId,H as setServerMuted,Y as setStatusText,ee as upsertBotSegment,le as useAudioPlatform,ye as useMediaDevices,Te as useVoice,Se as useVoiceDisplayState,fe as useVoiceProviderValue,he as useVoiceUI,ke as voiceListenerMiddleware,ue as voiceReducer};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1interface/voice-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@pipecat-ai/client-js": "^1.11.0",
|
|
30
|
-
"@1interface/shared-core": "^0.
|
|
30
|
+
"@1interface/shared-core": "^0.13.0"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"@reduxjs/toolkit": "^2.0.0",
|