@1interface/voice-core 0.7.0 → 0.8.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/browserAudioPlatform-BlQ_Ssdk.js +1 -0
- package/dist/browserAudioPlatform-DyVOp76o.cjs +1 -0
- package/dist/index.d.ts +56 -10
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/sessionHelpers-ALyIwLPk.cjs +1 -0
- package/dist/sessionHelpers-BIwU9Fpt.js +1 -0
- package/dist/useVoiceSessionCore-Bir8eqjh.cjs +1 -0
- package/dist/useVoiceSessionCore-Cwkj3eVq.js +1 -0
- package/dist/webrtc.d.ts +76 -0
- package/dist/webrtc.js +1 -0
- package/dist/webrtc.mjs +1 -0
- package/dist/ws.d.ts +168 -0
- package/dist/ws.js +1 -0
- package/dist/ws.mjs +1 -0
- package/package.json +12 -2
- package/dist/index.module-DCaqEXL5.cjs +0 -1
- package/dist/index.module-S2alA3-r.js +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{getBaseUrl as e,logger as t}from"@1interface/shared-core";import{X as n,Y as a}from"./sessionHelpers-BIwU9Fpt.js";const o=e=>{const t=document.createElement("audio");return t.autoplay=!0,t.style.display="none",t.srcObject=new MediaStream([e]),document.body.appendChild(t),()=>{try{t.pause(),t.srcObject=null,t.remove()}catch{}}};let r=null;function s(){const t=new Audio(`${e()}${n}`);return t.loop=!0,t.volume=a,t.preload="auto",t.load(),t}function i(e){const t=e.volume;e.volume=0,e.muted=!0;const n=()=>{e.pause(),e.currentTime=0,e.volume=t,e.muted=!1},a=e.play();return a&&"function"==typeof a.then?a.then(n,n):(n(),null)}function c(){const e=r;return r=null,e}const u={attachBotAudioTrack:o,requestMicPermission:async function(){try{const e=await(navigator.permissions?.query({name:"microphone"}).catch(()=>null));return t.debug({message:"Voice mic permission probe",api_name:"voice-mic",permission_state:e?.state??"unavailable"}),"granted"===e?.state||(await navigator.mediaDevices.getUserMedia({audio:!0})).getTracks().forEach(e=>e.stop()),{granted:!0}}catch(e){return t.warn({message:"Voice mic permission denied",api_name:"voice-mic",error_message:e instanceof Error?e.message:String(e)}),{granted:!1,error:e}}},primeGestureAudio:function(){if(r)return;const e=s(),t={audio:e,settled:null},n=i(e);t.settled=n?.then(()=>{t.settled=null})??null,r=t}};export{o as a,u as b,s as c,c as t,i as u};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";const e=require("@1interface/shared-core"),t=require("./sessionHelpers-ALyIwLPk.cjs"),r=e=>{const t=document.createElement("audio");return t.autoplay=!0,t.style.display="none",t.srcObject=new MediaStream([e]),document.body.appendChild(t),()=>{try{t.pause(),t.srcObject=null,t.remove()}catch{}}};let o=null;function n(){const r=new Audio(`${e.getBaseUrl()}${t.VOICE_THINKING_SOUND_PATH}`);return r.loop=!0,r.volume=t.VOICE_THINKING_SOUND_VOLUME,r.preload="auto",r.load(),r}function a(e){const t=e.volume;e.volume=0,e.muted=!0;const r=()=>{e.pause(),e.currentTime=0,e.volume=t,e.muted=!1},o=e.play();return o&&"function"==typeof o.then?o.then(r,r):(r(),null)}const s={attachBotAudioTrack:r,requestMicPermission:async function(){try{const t=await(navigator.permissions?.query({name:"microphone"}).catch(()=>null));return e.logger.debug({message:"Voice mic permission probe",api_name:"voice-mic",permission_state:t?.state??"unavailable"}),"granted"===t?.state||(await navigator.mediaDevices.getUserMedia({audio:!0})).getTracks().forEach(e=>e.stop()),{granted:!0}}catch(t){return e.logger.warn({message:"Voice mic permission denied",api_name:"voice-mic",error_message:t instanceof Error?t.message:String(t)}),{granted:!1,error:t}}},primeGestureAudio:function(){if(o)return;const e=n(),t={audio:e,settled:null},r=a(e);t.settled=r?.then(()=>{t.settled=null})??null,o=t}};exports.attachBrowserBotAudioTrack=r,exports.browserAudioPlatform=s,exports.createChimeElement=n,exports.takePrimedChime=function(){const e=o;return o=null,e},exports.unlockAudioElement=a;
|
package/dist/index.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { ThunkAction } from '@reduxjs/toolkit';
|
|
|
16
16
|
import { ThunkDispatch } from '@reduxjs/toolkit';
|
|
17
17
|
import { Transport } from '@pipecat-ai/client-js';
|
|
18
18
|
import { UnknownAction } from '@reduxjs/toolkit';
|
|
19
|
+
import { VoiceTransport } from '@1interface/shared-core';
|
|
19
20
|
|
|
20
21
|
export declare const attachBrowserBotAudioTrack: BotAudioTrackAttacher;
|
|
21
22
|
|
|
@@ -27,6 +28,10 @@ export declare interface AudioDeviceInfo {
|
|
|
27
28
|
export declare interface AudioPlatform {
|
|
28
29
|
attachBotAudioTrack: BotAudioTrackAttacher;
|
|
29
30
|
requestMicPermission(): Promise<MicPermissionResult>;
|
|
31
|
+
createPcmCapture?(): PcmCapture;
|
|
32
|
+
createPcmPlayer?(): PcmPlayer;
|
|
33
|
+
createThinkingSound?(): ThinkingSound;
|
|
34
|
+
primeGestureAudio?(): void;
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
export declare const AudioPlatformContext: Context<AudioPlatform>;
|
|
@@ -68,6 +73,31 @@ declare interface OrbStateConfig {
|
|
|
68
73
|
scale: number;
|
|
69
74
|
}
|
|
70
75
|
|
|
76
|
+
export declare interface PcmCapture {
|
|
77
|
+
open(opts: PcmCaptureOpenOptions): void;
|
|
78
|
+
setMuted(muted: boolean): void;
|
|
79
|
+
close(): void;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export declare interface PcmCaptureOpenOptions {
|
|
83
|
+
micDeviceId: string | null;
|
|
84
|
+
initialMuted: boolean;
|
|
85
|
+
onChunk: (buffer: ArrayBuffer) => void;
|
|
86
|
+
onLoadingChange?: (loading: boolean) => void;
|
|
87
|
+
onError?: (error: unknown) => void;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export declare interface PcmPlayer {
|
|
91
|
+
play(pcm: ArrayBuffer): void;
|
|
92
|
+
readonly remainingSeconds: number;
|
|
93
|
+
setPrerollProvider(provider: (() => number) | null): void;
|
|
94
|
+
onDrained(callback: () => void): void;
|
|
95
|
+
resetForNewSegment(): void;
|
|
96
|
+
setSinkId(deviceId: string | null): void;
|
|
97
|
+
stop(): void;
|
|
98
|
+
destroy(): void;
|
|
99
|
+
}
|
|
100
|
+
|
|
71
101
|
export declare const resetIndicatorState: ActionCreatorWithoutPayload<"voice/resetIndicatorState">;
|
|
72
102
|
|
|
73
103
|
export declare const setConnectionStatus: ActionCreatorWithOptionalPayload<VoiceConnectionStatus, "voice/setConnectionStatus">;
|
|
@@ -92,6 +122,15 @@ export declare const setServerMuted: ActionCreatorWithOptionalPayload<boolean, "
|
|
|
92
122
|
|
|
93
123
|
export declare const setStatusText: ActionCreatorWithOptionalPayload<string, "voice/setStatusText">;
|
|
94
124
|
|
|
125
|
+
export declare interface ThinkingSound {
|
|
126
|
+
unlock(): void;
|
|
127
|
+
start(remainingAudioSeconds: number): void;
|
|
128
|
+
pause(): void;
|
|
129
|
+
resume(remainingAudioSeconds?: number): void;
|
|
130
|
+
stop(): void;
|
|
131
|
+
destroy(): void;
|
|
132
|
+
}
|
|
133
|
+
|
|
95
134
|
export declare const upsertBotSegment: ActionCreatorWithOptionalPayload< {
|
|
96
135
|
id: number;
|
|
97
136
|
text: string;
|
|
@@ -111,10 +150,10 @@ export declare const useVoice: () => VoiceContextValue;
|
|
|
111
150
|
|
|
112
151
|
export declare function useVoiceDisplayState(): VoiceDisplayState;
|
|
113
152
|
|
|
114
|
-
export declare const useVoiceProviderValue: ({ conversationId, voiceBridge, transportFactory, assistantStartsConversation, }: VoiceProviderParams) => VoiceContextValue;
|
|
115
|
-
|
|
116
153
|
export declare const useVoiceUI: () => VoiceUIContextValue;
|
|
117
154
|
|
|
155
|
+
export declare const VOICE_CONNECT_ERROR_MESSAGE = "Could not connect voice. Please try again later.";
|
|
156
|
+
|
|
118
157
|
export declare const VOICE_CONNECT_TIMEOUT_MS = 15000;
|
|
119
158
|
|
|
120
159
|
export declare const VOICE_CONNECTION_STATUSES: {
|
|
@@ -146,6 +185,8 @@ export declare const VOICE_PIPELINE_STATUSES: {
|
|
|
146
185
|
readonly SPEAKING: "speaking";
|
|
147
186
|
};
|
|
148
187
|
|
|
188
|
+
export declare const VOICE_SAMPLE_RATE = 24000;
|
|
189
|
+
|
|
149
190
|
export declare const VOICE_STORAGE_KEYS: {
|
|
150
191
|
readonly SELECTED_MIC_ID: "voice_selectedMicId";
|
|
151
192
|
readonly SELECTED_SPEAKER_ID: "voice_selectedSpeakerId";
|
|
@@ -197,13 +238,6 @@ export declare type VoicePipelineMode = (typeof VOICE_PIPELINE_MODES)[keyof type
|
|
|
197
238
|
|
|
198
239
|
export declare type VoicePipelineStatus = (typeof VOICE_PIPELINE_STATUSES)[keyof typeof VOICE_PIPELINE_STATUSES];
|
|
199
240
|
|
|
200
|
-
export declare interface VoiceProviderParams {
|
|
201
|
-
conversationId: string | null;
|
|
202
|
-
voiceBridge: VoiceChatBridge;
|
|
203
|
-
transportFactory: VoiceTransportFactory;
|
|
204
|
-
assistantStartsConversation: boolean;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
241
|
export declare const voiceReducer: Reducer<VoiceState>;
|
|
208
242
|
|
|
209
243
|
export declare interface VoiceRootState {
|
|
@@ -219,6 +253,16 @@ export declare interface VoiceSendTextOptions {
|
|
|
219
253
|
messageType?: string[];
|
|
220
254
|
}
|
|
221
255
|
|
|
256
|
+
export declare interface VoiceSessionEngine {
|
|
257
|
+
connect: () => Promise<void>;
|
|
258
|
+
disconnect: (commitTurn?: boolean) => void;
|
|
259
|
+
sendText: (text: string, options?: VoiceSendTextOptions) => void;
|
|
260
|
+
setMicEnabled: (enabled: boolean) => void;
|
|
261
|
+
setInputDevice: (deviceId: string) => void;
|
|
262
|
+
setOutputDevice: (deviceId: string) => void;
|
|
263
|
+
primeAudio?: () => void;
|
|
264
|
+
}
|
|
265
|
+
|
|
222
266
|
export declare interface VoiceState {
|
|
223
267
|
connectionStatus: VoiceConnectionStatus;
|
|
224
268
|
pipelineStatus: VoicePipelineStatus;
|
|
@@ -237,7 +281,7 @@ export declare interface VoiceState {
|
|
|
237
281
|
speakers: AudioDeviceInfo[];
|
|
238
282
|
}
|
|
239
283
|
|
|
240
|
-
export declare type VoiceTransportFactory = () => Transport
|
|
284
|
+
export declare type VoiceTransportFactory = () => Transport | Promise<Transport>;
|
|
241
285
|
|
|
242
286
|
export declare const VoiceUIContext: Context<VoiceUIContextValue>;
|
|
243
287
|
|
|
@@ -254,4 +298,6 @@ export declare interface VoiceUIContextValue {
|
|
|
254
298
|
}> | null;
|
|
255
299
|
}
|
|
256
300
|
|
|
301
|
+
export declare type VoiceWsTransport = Exclude<VoiceTransport, 'pipecat_webrtc'>;
|
|
302
|
+
|
|
257
303
|
export { }
|
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:_,setStatusText:E,setIsAgentWorking:T,setResponseStartTime:f,setInterimTranscript:C,openBotTurn:b,upsertBotSegment:h,clearBotTurn:k,setSelectedMicId:R,setSelectedSpeakerId:y,setMicrophones:V,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,s)=>({id:t,sender:"user",message:e,timestamp:(new Date).toISOString(),...r?.length?{documents:r}:{},...s?.length?{message_type:s}:{}}),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:y,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=y,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(V([])),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()=>{}),ye=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(V(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(V(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]),Ve=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:ye(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,ye,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(y(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,t?.messageType)]),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=Ve},[Ve]);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,Ve())},[r,xe,Ve]);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:Ve,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;
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./sessionHelpers-ALyIwLPk.cjs"),t=require("react"),r=require("react-redux"),s=require("@reduxjs/toolkit"),o=require("@1interface/shared-core"),i=require("./browserAudioPlatform-DyVOp76o.cjs"),S=t.createContext({strategyActive:!1,voiceSlot:null,onEnterVoice:null,onFloatingOrbClick:null,voiceInputHidden:!1,headerCenterSlot:null,floatOrbPositionRef:null}),a=s.createListenerMiddleware();a.startListening({actionCreator:e.setSelectedMicId,effect:async t=>{const r=o.getStorageRef();r&&await r.setItem(e.VOICE_STORAGE_KEYS.SELECTED_MIC_ID,t.payload)}}),a.startListening({actionCreator:e.setSelectedSpeakerId,effect:async t=>{const r=o.getStorageRef();r&&await r.setItem(e.VOICE_STORAGE_KEYS.SELECTED_SPEAKER_ID,t.payload)}}),a.startListening({matcher:s.isAnyOf(o.clearSession,o.sessionExpired),effect:async(t,r)=>{const s=o.getStorageRef();s&&await Promise.all([s.removeItem(e.VOICE_STORAGE_KEYS.SELECTED_MIC_ID),s.removeItem(e.VOICE_STORAGE_KEYS.SELECTED_SPEAKER_ID)]),r.dispatch(e.resetVoiceState())}}),exports.AudioPlatformContext=e.AudioPlatformContext,exports.ORB_STATE_CONFIG=e.ORB_STATE_CONFIG,exports.VOICE_CONNECTION_STATUSES=e.VOICE_CONNECTION_STATUSES,exports.VOICE_CONNECT_ERROR_MESSAGE=e.VOICE_CONNECT_ERROR_MESSAGE,exports.VOICE_CONNECT_TIMEOUT_MS=e.VOICE_CONNECT_TIMEOUT_MS,exports.VOICE_DISPLAY_STATES=e.VOICE_DISPLAY_STATES,exports.VOICE_PIPELINE_MODES=e.VOICE_PIPELINE_MODES,exports.VOICE_PIPELINE_STATUSES=e.VOICE_PIPELINE_STATUSES,exports.VOICE_SAMPLE_RATE=e.VOICE_SAMPLE_RATE,exports.VOICE_STORAGE_KEYS=e.VOICE_STORAGE_KEYS,exports.VoiceContext=e.VoiceContext,exports.clearBotTurn=e.clearBotTurn,exports.hydrateVoice=e.hydrateVoice,exports.openBotTurn=e.openBotTurn,exports.resetIndicatorState=e.resetIndicatorState,exports.setConnectionStatus=e.setConnectionStatus,exports.setInterimTranscript=e.setInterimTranscript,exports.setIsAgentWorking=e.setIsAgentWorking,exports.setListeningEnabled=e.setListeningEnabled,exports.setMuted=e.setMuted,exports.setPipelineStatus=e.setPipelineStatus,exports.setResponseStartTime=e.setResponseStartTime,exports.setSelectedMicId=e.setSelectedMicId,exports.setSelectedSpeakerId=e.setSelectedSpeakerId,exports.setServerMuted=e.setServerMuted,exports.setStatusText=e.setStatusText,exports.upsertBotSegment=e.upsertBotSegment,exports.useAudioPlatform=e.useAudioPlatform,exports.useVoice=e.useVoice,exports.useVoiceDisplayState=e.useVoiceDisplayState,exports.voiceReducer=e.voiceSlice_default,exports.attachBrowserBotAudioTrack=i.attachBrowserBotAudioTrack,exports.browserAudioPlatform=i.browserAudioPlatform,exports.VoiceUIContext=S,exports.useMediaDevices=function(){return{microphones:r.useSelector(e=>e.voice.microphones),speakers:r.useSelector(e=>e.voice.speakers),selectedMicId:r.useSelector(e=>e.voice.selectedMicId),selectedSpeakerId:r.useSelector(e=>e.voice.selectedSpeakerId)}},exports.useVoiceUI=()=>t.useContext(S),exports.voiceListenerMiddleware=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 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,r)=>({id:t,sender:"user",message:e,timestamp:(new Date).toISOString(),...n?.length?{documents:n}:{},...r?.length?{message_type:r}:{}}),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,n?.messageType)]),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};
|
|
1
|
+
import{x as e,L as t,y as s,M as a}from"./sessionHelpers-BIwU9Fpt.js";import{N as o,O as r,e as i,v as n,t as c,P as E,r as l,V as I,A as S,Q as m,E as d,R as u,G as C,S as p,d as _,f as T,h as f,b as O,w as A,c as P,C as L,g as M,i as V,H as v,u as D,T as N,U as R,W as g}from"./sessionHelpers-BIwU9Fpt.js";import{createContext as y,useContext as h}from"react";import{useSelector as k}from"react-redux";import{createListenerMiddleware as x,isAnyOf as b}from"@reduxjs/toolkit";import{getStorageRef as w,clearSession as B,sessionExpired as j}from"@1interface/shared-core";import{a as H,b as U}from"./browserAudioPlatform-BlQ_Ssdk.js";const F=y({strategyActive:!1,voiceSlot:null,onEnterVoice:null,onFloatingOrbClick:null,voiceInputHidden:!1,headerCenterSlot:null,floatOrbPositionRef:null}),G=()=>h(F);function K(){return{microphones:k(e=>e.voice.microphones),speakers:k(e=>e.voice.speakers),selectedMicId:k(e=>e.voice.selectedMicId),selectedSpeakerId:k(e=>e.voice.selectedSpeakerId)}}const W=x();W.startListening({actionCreator:e,effect:async e=>{const s=w();s&&await s.setItem(t.SELECTED_MIC_ID,e.payload)}}),W.startListening({actionCreator:s,effect:async e=>{const s=w();s&&await s.setItem(t.SELECTED_SPEAKER_ID,e.payload)}}),W.startListening({matcher:b(B,j),effect:async(e,s)=>{const o=w();o&&await Promise.all([o.removeItem(t.SELECTED_MIC_ID),o.removeItem(t.SELECTED_SPEAKER_ID)]),s.dispatch(a())}});export{o as AudioPlatformContext,r as ORB_STATE_CONFIG,i as VOICE_CONNECTION_STATUSES,n as VOICE_CONNECT_ERROR_MESSAGE,c as VOICE_CONNECT_TIMEOUT_MS,E as VOICE_DISPLAY_STATES,l as VOICE_PIPELINE_MODES,I as VOICE_PIPELINE_STATUSES,S as VOICE_SAMPLE_RATE,t as VOICE_STORAGE_KEYS,m as VoiceContext,F as VoiceUIContext,H as attachBrowserBotAudioTrack,U as browserAudioPlatform,d as clearBotTurn,u as hydrateVoice,C as openBotTurn,p as resetIndicatorState,_ as setConnectionStatus,T as setInterimTranscript,f as setIsAgentWorking,O as setListeningEnabled,A as setMuted,P as setPipelineStatus,L as setResponseStartTime,e as setSelectedMicId,s as setSelectedSpeakerId,M as setServerMuted,V as setStatusText,v as upsertBotSegment,D as useAudioPlatform,K as useMediaDevices,N as useVoice,R as useVoiceDisplayState,G as useVoiceUI,W as voiceListenerMiddleware,g as voiceReducer};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";const e=require("react"),t=require("react-redux"),s=require("@reduxjs/toolkit"),o=require("@1interface/shared-core"),n={DISCONNECTED:"disconnected",CONNECTING:"connecting",CONNECTED:"connected"},r={IDLE:"idle",LISTENING:"listening",PROCESSING:"processing",SPEAKING:"speaking"},i={OFFLINE:"offline",CONNECTING:"connecting",READY:"ready",IDLE:"idle",LISTENING:"listening",MUTED:"muted",PROCESSING:"processing",SPEAKING:"speaking"},a={SELECTED_MIC_ID:"voice_selectedMicId",SELECTED_SPEAKER_ID:"voice_selectedSpeakerId"};function c(){return{connectionStatus:n.DISCONNECTED,pipelineStatus:r.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 d=c(),p=s.createSlice({name:"voice",initialState:d,reducers:{setConnectionStatus(e,t){e.connectionStatus=t.payload},setPipelineStatus(e,t){e.pipelineStatus=t.payload},setListeningEnabled(e,t){e.listeningEnabled=t.payload},setStatusText(e,t){e.statusText=t.payload},setIsAgentWorking(e,t){e.isAgentWorking=t.payload},setResponseStartTime(e,t){e.responseStartTime=t.payload},setInterimTranscript(e,t){e.interimTranscript=t.payload},openBotTurn(e,t){e.botTurn={active:!0,chatMsgId:t.payload?.chatMsgId??null,segments:[]}},upsertBotSegment(e,t){const{id:s,text:o,spokenChars:n}=t.payload;e.botTurn.active=!0;const r=e.botTurn.segments.find(e=>e.id===s);r?(r.text=o,r.spokenChars=Math.max(r.spokenChars,n)):(e.botTurn.segments.push({id:s,text:o,spokenChars:n}),e.botTurn.segments.sort((e,t)=>e.id-t.id))},clearBotTurn(e){e.botTurn={active:!1,chatMsgId:null,segments:[]}},setMuted(e,t){e.muted=t.payload},setServerMuted(e,t){e.serverMuted=t.payload},setSelectedMicId(e,t){e.selectedMicId=t.payload},setSelectedSpeakerId(e,t){e.selectedSpeakerId=t.payload},setMicrophones(e,t){e.microphones=t.payload},setSpeakers(e,t){e.speakers=t.payload},signalSessionEnded(e){e.sessionEndedSignal+=1},resetVoiceState:()=>c(),resetIndicatorState(e){e.statusText=null,e.isAgentWorking=!1,e.responseStartTime=null,e.interimTranscript=null,e.serverMuted=!1,e.botTurn={active:!1,chatMsgId:null,segments:[]}}}}),{setConnectionStatus:E,setPipelineStatus:u,setListeningEnabled:l,setMuted:S,setServerMuted:I,setStatusText:T,setIsAgentWorking:g,setResponseStartTime:_,setInterimTranscript:C,openBotTurn:x,upsertBotSegment:N,clearBotTurn:m,setSelectedMicId:O,setSelectedSpeakerId:R,setMicrophones:M,setSpeakers:A,signalSessionEnded:y,resetVoiceState:D,resetIndicatorState:k}=p.actions,v=p.reducer;function P(){const e=t.useSelector(e=>e.voice.connectionStatus),s=t.useSelector(e=>e.voice.pipelineStatus),o=t.useSelector(e=>e.voice.listeningEnabled),a=t.useSelector(e=>e.voice.muted);return e===n.CONNECTING?i.CONNECTING:e!==n.CONNECTED?i.OFFLINE:o?a?i.MUTED:s===r.PROCESSING?i.PROCESSING:s===r.SPEAKING?i.SPEAKING:s===r.LISTENING?i.LISTENING:i.IDLE:i.READY}const V=e.createContext(null),b=()=>{const t=e.useContext(V);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},h=e.createContext(null),G="voice-user-";exports.AudioPlatformContext=V,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_AGENT_ID_PREFIX="voice-agent-",exports.VOICE_AUTH_ERROR_PATTERNS=[/rejected/i,/unauthori[sz]ed/i,/forbidden/i,/\b40[13]\b/],exports.VOICE_CONNECTION_STATUSES=n,exports.VOICE_CONNECT_ERROR_MESSAGE="Could not connect voice. Please try again later.",exports.VOICE_CONNECT_TIMEOUT_MS=15e3,exports.VOICE_DISPLAY_STATES=i,exports.VOICE_ERROR_ID_PREFIX="voice-error-",exports.VOICE_GENERIC_ERROR_MESSAGE="Something went wrong. Please try again.",exports.VOICE_MAX_AUTH_REFRESHES=1,exports.VOICE_MAX_AUTO_RECONNECTS=3,exports.VOICE_PIPELINE_MODES={CASCADE:"cascade",REALTIME:"realtime"},exports.VOICE_PIPELINE_STATUSES=r,exports.VOICE_RECONNECT_DELAY_MS=1e3,exports.VOICE_SAMPLE_RATE=24e3,exports.VOICE_SERVER_MSG_TYPES={STATUS_UPDATE:"status_update",STARTED_THINKING:"started-thinking",STOPPED_THINKING:"stopped-thinking",CLEAR_STATUS:"clear_status",WIDGET:"widget",PAYMENT:"payment",REQUEST_DOCUMENTS:"request_documents",CONVERSATION_TITLE:"conversation_title",SESSION_ENDED:"session-ended"},exports.VOICE_STORAGE_KEYS=a,exports.VOICE_TERMINAL_ERROR_PATTERNS=[/\b402\b/,/\b429\b/,/rate[\s-]?limit/i],exports.VOICE_THINKING_SOUND_PATH="/sounds/thinking-sound.mp3",exports.VOICE_THINKING_SOUND_VOLUME=.5,exports.VOICE_USER_ID_PREFIX=G,exports.VOICE_WIDGET_ID_PREFIX="voice-widget-",exports.VoiceContext=h,exports.clearBotTurn=m,exports.hydrateVoice=e=>async t=>{const[s,o]=await Promise.all([e.getItem(a.SELECTED_MIC_ID),e.getItem(a.SELECTED_SPEAKER_ID)]);s&&t(p.actions.setSelectedMicId(s)),o&&t(p.actions.setSelectedSpeakerId(o))},exports.makeAgentMessage=(e,t)=>{const s=`${e}${o.generateUniqueId()}`;return{id:s,msg_id:s,sender:"agent",message:"",timestamp:(new Date).toISOString(),...t}},exports.makeUserMessage=(e,t=`${G}${o.generateUniqueId()}`,s,n)=>({id:t,sender:"user",message:e,timestamp:(new Date).toISOString(),...s?.length?{documents:s}:{},...n?.length?{message_type:n}:{}}),exports.openBotTurn=x,exports.resetIndicatorState=k,exports.resetVoiceState=D,exports.setConnectionStatus=E,exports.setInterimTranscript=C,exports.setIsAgentWorking=g,exports.setListeningEnabled=l,exports.setMicrophones=M,exports.setMuted=S,exports.setPipelineStatus=u,exports.setResponseStartTime=_,exports.setSelectedMicId=O,exports.setSelectedSpeakerId=R,exports.setServerMuted=I,exports.setSpeakers=A,exports.setStatusText=T,exports.signalSessionEnded=y,exports.upsertBotSegment=N,exports.useAudioPlatform=b,exports.useVoice=()=>{const t=e.useContext(h);if(!t)throw new Error("useVoice must be used within VoiceProvider");return t},exports.useVoiceContextValue=(s,{voiceBridge:n})=>{const r=t.useDispatch(),i=b(),a=t.useSelector(e=>e.voice.muted),c=t.useSelector(e=>e.voice.serverMuted),d=t.useSelector(e=>e.voice.pipelineStatus),p=t.useSelector(e=>e.voice.isAgentWorking),E=t.useSelector(e=>e.voice.sessionEndedSignal),{connect:u,disconnect:l,sendText:I,setMicEnabled:T,setInputDevice:g,setOutputDevice:_,primeAudio:C}=s;e.useEffect(()=>{T(!a&&!c)},[a,c,T]);const x=P();e.useEffect(()=>(n.setVoiceSendText(I),()=>n.setVoiceSendText(null)),[n,I]);const N=e.useRef(!1),m=e.useCallback(async()=>{if(N.current)return o.showErrorToast("Microphone permission changed — please reload the page to apply the new setting."),!1;C?.();const e=await i.requestMicPermission();if(!e.granted){const t=e.error;return o.logger.error({message:"Mic permission denied",api_name:"voice-mic",error_message:t instanceof Error?t.message:t?String(t):void 0}),N.current=!0,o.showErrorToast("Microphone access is required for voice mode. Please allow it in your browser's site settings and reload the page."),!1}return u(),!0},[i,u,C]),O=e.useCallback(()=>{l()},[l]),R=e.useCallback(()=>{r(S(!a))},[r,a]);return e.useMemo(()=>({displayState:x,pipelineStatus:d,isAgentWorking:p,enterVoiceMode:m,exitVoiceMode:O,toggleMute:R,sendText:I,muted:a,serverMuted:c,sessionEndedSignal:E,setInputDevice:g,setOutputDevice:_}),[x,d,p,m,O,R,I,a,c,E,g,_])},exports.useVoiceDisplayState=P,exports.voiceSlice_default=v;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createContext as e,useContext as t,useEffect as s,useRef as a,useCallback as n,useMemo as i}from"react";import{useSelector as o,useDispatch as r}from"react-redux";import{createSlice as c}from"@reduxjs/toolkit";import{showErrorToast as d,logger as l,generateUniqueId as u}from"@1interface/shared-core";const p={DISCONNECTED:"disconnected",CONNECTING:"connecting",CONNECTED:"connected"},S={IDLE:"idle",LISTENING:"listening",PROCESSING:"processing",SPEAKING:"speaking"},g={CASCADE:"cascade",REALTIME:"realtime"},E={STATUS_UPDATE:"status_update",STARTED_THINKING:"started-thinking",STOPPED_THINKING:"stopped-thinking",CLEAR_STATUS:"clear_status",WIDGET:"widget",PAYMENT:"payment",REQUEST_DOCUMENTS:"request_documents",CONVERSATION_TITLE:"conversation_title",SESSION_ENDED:"session-ended"},m={OFFLINE:"offline",CONNECTING:"connecting",READY:"ready",IDLE:"idle",LISTENING:"listening",MUTED:"muted",PROCESSING:"processing",SPEAKING:"speaking"},I=15e3,T=1,N=[/rejected/i,/unauthori[sz]ed/i,/forbidden/i,/\b40[13]\b/],y=[/\b402\b/,/\b429\b/,/rate[\s-]?limit/i],v=1e3,C=3,M=24e3,k="/sounds/thinking-sound.mp3",D=.5,b={SELECTED_MIC_ID:"voice_selectedMicId",SELECTED_SPEAKER_ID:"voice_selectedSpeakerId"},h={offline:{opacity:.15,playbackRate:.3,scale:1},connecting:{opacity:.4,playbackRate:.5,scale:1},ready:{opacity:.4,playbackRate:.4,scale:1},idle:{opacity:.6,playbackRate:.6,scale:1},listening:{opacity:.7,playbackRate:.7,scale:1},muted:{opacity:.7,playbackRate:.5,scale:1},processing:{opacity:1,playbackRate:1,scale:1},speaking:{opacity:1,playbackRate:1,scale:1.3}};function A(){return{connectionStatus:p.DISCONNECTED,pipelineStatus:S.IDLE,listeningEnabled:!1,muted:!1,serverMuted:!1,statusText:null,isAgentWorking:!1,responseStartTime:null,interimTranscript:null,botTurn:{active:!1,chatMsgId:null,segments:[]},sessionEndedSignal:0,selectedMicId:"",selectedSpeakerId:"",microphones:[],speakers:[]}}const P=e=>async t=>{const[s,a]=await Promise.all([e.getItem(b.SELECTED_MIC_ID),e.getItem(b.SELECTED_SPEAKER_ID)]);s&&t(_.actions.setSelectedMicId(s)),a&&t(_.actions.setSelectedSpeakerId(a))},_=c({name:"voice",initialState:A(),reducers:{setConnectionStatus(e,t){e.connectionStatus=t.payload},setPipelineStatus(e,t){e.pipelineStatus=t.payload},setListeningEnabled(e,t){e.listeningEnabled=t.payload},setStatusText(e,t){e.statusText=t.payload},setIsAgentWorking(e,t){e.isAgentWorking=t.payload},setResponseStartTime(e,t){e.responseStartTime=t.payload},setInterimTranscript(e,t){e.interimTranscript=t.payload},openBotTurn(e,t){e.botTurn={active:!0,chatMsgId:t.payload?.chatMsgId??null,segments:[]}},upsertBotSegment(e,t){const{id:s,text:a,spokenChars:n}=t.payload;e.botTurn.active=!0;const i=e.botTurn.segments.find(e=>e.id===s);i?(i.text=a,i.spokenChars=Math.max(i.spokenChars,n)):(e.botTurn.segments.push({id:s,text:a,spokenChars:n}),e.botTurn.segments.sort((e,t)=>e.id-t.id))},clearBotTurn(e){e.botTurn={active:!1,chatMsgId:null,segments:[]}},setMuted(e,t){e.muted=t.payload},setServerMuted(e,t){e.serverMuted=t.payload},setSelectedMicId(e,t){e.selectedMicId=t.payload},setSelectedSpeakerId(e,t){e.selectedSpeakerId=t.payload},setMicrophones(e,t){e.microphones=t.payload},setSpeakers(e,t){e.speakers=t.payload},signalSessionEnded(e){e.sessionEndedSignal+=1},resetVoiceState:()=>A(),resetIndicatorState(e){e.statusText=null,e.isAgentWorking=!1,e.responseStartTime=null,e.interimTranscript=null,e.serverMuted=!1,e.botTurn={active:!1,chatMsgId:null,segments:[]}}}}),{setConnectionStatus:f,setPipelineStatus:O,setListeningEnabled:R,setMuted:G,setServerMuted:L,setStatusText:w,setIsAgentWorking:x,setResponseStartTime:V,setInterimTranscript:K,openBotTurn:W,upsertBotSegment:B,clearBotTurn:U,setSelectedMicId:F,setSelectedSpeakerId:q,setMicrophones:Y,setSpeakers:$,signalSessionEnded:j,resetVoiceState:H,resetIndicatorState:z}=_.actions,Q=_.reducer;function J(){const e=o(e=>e.voice.connectionStatus),t=o(e=>e.voice.pipelineStatus),s=o(e=>e.voice.listeningEnabled),a=o(e=>e.voice.muted);return e===p.CONNECTING?m.CONNECTING:e!==p.CONNECTED?m.OFFLINE:s?a?m.MUTED:t===S.PROCESSING?m.PROCESSING:t===S.SPEAKING?m.SPEAKING:t===S.LISTENING?m.LISTENING:m.IDLE:m.READY}const X=e(null),Z=()=>{const e=t(X);if(!e)throw new Error("useAudioPlatform must be used inside <AudioPlatformProvider>. <VoiceProvider> mounts one by default; if you replaced VoiceProvider, you must wire your own.");return e},ee=e(null),te=(e,{voiceBridge:t})=>{const c=r(),u=Z(),p=o(e=>e.voice.muted),S=o(e=>e.voice.serverMuted),g=o(e=>e.voice.pipelineStatus),E=o(e=>e.voice.isAgentWorking),m=o(e=>e.voice.sessionEndedSignal),{connect:I,disconnect:T,sendText:N,setMicEnabled:y,setInputDevice:v,setOutputDevice:C,primeAudio:M}=e;s(()=>{y(!p&&!S)},[p,S,y]);const k=J();s(()=>(t.setVoiceSendText(N),()=>t.setVoiceSendText(null)),[t,N]);const D=a(!1),b=n(async()=>{if(D.current)return d("Microphone permission changed — please reload the page to apply the new setting."),!1;M?.();const e=await u.requestMicPermission();if(!e.granted){const t=e.error;return l.error({message:"Mic permission denied",api_name:"voice-mic",error_message:t instanceof Error?t.message:t?String(t):void 0}),D.current=!0,d("Microphone access is required for voice mode. Please allow it in your browser's site settings and reload the page."),!1}return I(),!0},[u,I,M]),h=n(()=>{T()},[T]),A=n(()=>{c(G(!p))},[c,p]);return i(()=>({displayState:k,pipelineStatus:g,isAgentWorking:E,enterVoiceMode:b,exitVoiceMode:h,toggleMute:A,sendText:N,muted:p,serverMuted:S,sessionEndedSignal:m,setInputDevice:v,setOutputDevice:C}),[k,g,E,b,h,A,N,p,S,m,v,C])},se=()=>{const e=t(ee);if(!e)throw new Error("useVoice must be used within VoiceProvider");return e},ae="voice-user-",ne="voice-widget-",ie="voice-agent-",oe="voice-error-",re="Something went wrong. Please try again.",ce="Could not connect voice. Please try again later.",de=(e,t=`${ae}${u()}`,s,a)=>({id:t,sender:"user",message:e,timestamp:(new Date).toISOString(),...s?.length?{documents:s}:{},...a?.length?{message_type:a}:{}}),le=(e,t)=>{const s=`${e}${u()}`;return{id:s,msg_id:s,sender:"agent",message:"",timestamp:(new Date).toISOString(),...t}};export{M as A,E as B,V as C,le as D,U as E,ie as F,W as G,B as H,re as I,oe as J,ne as K,b as L,H as M,X as N,h as O,m as P,ee as Q,P as R,z as S,se as T,J as U,S as V,Q as W,k as X,D as Y,$ as a,R as b,O as c,f as d,p as e,K as f,L as g,x as h,w as i,ae as j,N as k,y as l,de as m,j as n,T as o,C as p,v as q,g as r,Y as s,I as t,Z as u,ce as v,G as w,F as x,q as y,te as z};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";const e=require("react"),t=require("react-redux"),n=require("./sessionHelpers-ALyIwLPk.cjs"),s=require("@1interface/shared-core");exports.foldCascadeBotOutput=function(e,t,n,s){const r=t.segment_id??0,a="completed"===t.spoken_status;if(a&&!e.has(r)){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 u=e.get(r);return e.set(r,{text:n,completed:a||!0===u?.completed}),{id:r,text:n,spokenChars:s&&t.spoken_progress?(t.spoken_progress.accumulated_text??"").length:n.length}},exports.foldRealtimeBotOutput=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}},exports.useVoiceSessionCore=function(r,a,u){const o=t.useDispatch(),c=e.useRef(a),i=e.useRef(u);e.useEffect(()=>{c.current=a,i.current=u});const l=e.useRef(null),E=e.useRef(new Map),S=e.useRef(""),_=e.useRef(null),d=e.useCallback(()=>{if(null!=_.current)return;const e=Date.now();_.current=e,o(n.setResponseStartTime(e)),o(s.clearLastResponseTime())},[o]),g=e.useCallback(()=>{const e=_.current;_.current=null,o(n.setResponseStartTime(null)),null!=e&&o(s.setLastResponseTime(Date.now()-e))},[o]),T=e.useCallback(()=>{_.current=null,o(n.setResponseStartTime(null))},[o]),R=e.useCallback((e=s.INDICATOR_TIMER_ACTIONS.KEEP)=>{o(n.setStatusText(null)),o(n.setIsAgentWorking(!1)),e===s.INDICATOR_TIMER_ACTIONS.FINALIZE?g():e===s.INDICATOR_TIMER_ACTIONS.CANCEL&&T()},[o,g,T]),[I]=e.useState(()=>({setStatusText:e=>o(n.setStatusText(e||null)),setIsAgentWorking:e=>o(n.setIsAgentWorking(e)),appendStreamText:()=>{},pushWidget:e=>{const t=n.makeAgentMessage(n.VOICE_WIDGET_ID_PREFIX,{widget:[e],message_type:["widget"]});c.current.setMessages(e=>[...e,t])},onPayment:e=>c.current.onPaymentInit?.(e),onRequestDocuments:e=>c.current.onRequestDocuments?.(e),onConversationTitle:e=>c.current.onConversationTitle?.(e),onError:e=>{s.logger.error({message:"Voice server error",api_name:i.current.apiName,error_message:e});const t=n.makeAgentMessage(n.VOICE_ERROR_ID_PREFIX,{message:n.VOICE_GENERIC_ERROR_MESSAGE,message_type:["error"]});c.current.setMessages(e=>[...e,t])},onStatusActive:()=>{d()}})),p=e.useCallback(()=>[...E.current.entries()].sort((e,t)=>e[0]-t[0]).map(([,e])=>e.text).join(" ").trim(),[]),m=e.useCallback(()=>{l.current=null,E.current=new Map,S.current="",o(n.clearBotTurn())},[o]),C=e.useCallback(()=>{const e=l.current;if(e){const t=p();t&&c.current.updateStreamingMessage(e,t),c.current.commitStreamingMessages()}m()},[p,m]),k=e.useCallback(()=>{c.current.discardStreamingMessages(),m()},[m]),M=e.useCallback(()=>{if(l.current)return;const e=`${n.VOICE_AGENT_ID_PREFIX}${s.generateUniqueId()}`;l.current=e,E.current=new Map,S.current="",o(n.openBotTurn({chatMsgId:e}))},[o]),O=e.useCallback(e=>{o(n.upsertBotSegment(e));const t=p();t&&l.current&&t!==S.current&&(S.current=t,c.current.updateStreamingMessage(l.current,t))},[o,p]),f=e.useCallback(e=>{const t=e,a=t?.type,u=t?.data;switch(s.logger.debug({message:"Voice server-message received",api_name:i.current.apiName,conversation_id:r??void 0,event_type:a??"unknown"}),a){case n.VOICE_SERVER_MSG_TYPES.STATUS_UPDATE:s.handleSharedEvent({kind:"status_update",statusText:u?.text??""},I);break;case n.VOICE_SERVER_MSG_TYPES.STARTED_THINKING:o(n.setIsAgentWorking(!0)),d();break;case n.VOICE_SERVER_MSG_TYPES.STOPPED_THINKING:R(s.INDICATOR_TIMER_ACTIONS.FINALIZE);break;case n.VOICE_SERVER_MSG_TYPES.CLEAR_STATUS:i.current.onClearStatus?.();break;case n.VOICE_SERVER_MSG_TYPES.WIDGET:u&&s.handleSharedEvent({kind:"widget",widget:u},I);break;case n.VOICE_SERVER_MSG_TYPES.PAYMENT:u&&s.handleSharedEvent({kind:"payment",payment:u},I);break;case n.VOICE_SERVER_MSG_TYPES.REQUEST_DOCUMENTS:u&&s.handleSharedEvent({kind:"request_documents",requestDocuments:u},I);break;case n.VOICE_SERVER_MSG_TYPES.CONVERSATION_TITLE:{const e="string"==typeof u?.text?u.text.trim():"";e&&s.handleSharedEvent({kind:"conversation_title",title:e},I);break}case n.VOICE_SERVER_MSG_TYPES.SESSION_ENDED:{const e="string"==typeof u?.reason?u.reason:"ended";i.current.onSessionEnded?.(e);break}default:s.logger.debug({message:"Voice unhandled server-message",api_name:i.current.apiName,event_type:a??"unknown"})}},[I,o,d,R,r]);return{bridgeRef:c,botTurnMsgIdRef:l,liveSegmentsRef:E,startResponseTimer:d,finalizeResponseTimer:g,cancelResponseTimer:T,clearIndicator:R,sharedDeps:I,buildTurnText:p,clearTurnState:m,commitBotTurn:C,discardBotTurn:k,openBotTurnIfNeeded:M,ingestBotSegment:O,routeServerMessage:f}};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{useRef as e,useEffect as t,useCallback as r,useState as n}from"react";import{useDispatch as s}from"react-redux";import{C as a,i as o,h as c,D as u,E as i,F as m,G as d,H as p,B as l,I as g,J as T,K as S}from"./sessionHelpers-BIwU9Fpt.js";import{clearLastResponseTime as _,setLastResponseTime as f,INDICATOR_TIMER_ACTIONS as k,logger as E,generateUniqueId as I,handleSharedEvent as x}from"@1interface/shared-core";function N(e,t,r,n){const s=t.segment_id??0,a="completed"===t.spoken_status;if(a&&!e.has(s)){const t=[...e.entries()].find(([,e])=>e.text===r&&!e.completed);if(t)return t[1].completed=!0,{id:t[0],text:r,spokenChars:r.length}}const o=e.get(s);return e.set(s,{text:r,completed:a||!0===o?.completed}),{id:s,text:r,spokenChars:n&&t.spoken_progress?(t.spoken_progress.accumulated_text??"").length:r.length}}function h(e,t){const r=(e.get(0)?.text??"")+t;return e.set(0,{text:r,completed:!1}),{id:0,text:r,spokenChars:r.length}}function b(N,h,b){const v=s(),D=e(h),M=e(b);t(()=>{D.current=h,M.current=b});const w=e(null),A=e(new Map),C=e(""),R=e(null),y=r(()=>{if(null!=R.current)return;const e=Date.now();R.current=e,v(a(e)),v(_())},[v]),P=r(()=>{const e=R.current;R.current=null,v(a(null)),null!=e&&v(f(Date.now()-e))},[v]),B=r(()=>{R.current=null,v(a(null))},[v]),L=r((e=k.KEEP)=>{v(o(null)),v(c(!1)),e===k.FINALIZE?P():e===k.CANCEL&&B()},[v,P,B]),[O]=n(()=>({setStatusText:e=>v(o(e||null)),setIsAgentWorking:e=>v(c(e)),appendStreamText:()=>{},pushWidget:e=>{const t=u(S,{widget:[e],message_type:["widget"]});D.current.setMessages(e=>[...e,t])},onPayment:e=>D.current.onPaymentInit?.(e),onRequestDocuments:e=>D.current.onRequestDocuments?.(e),onConversationTitle:e=>D.current.onConversationTitle?.(e),onError:e=>{E.error({message:"Voice server error",api_name:M.current.apiName,error_message:e});const t=u(T,{message:g,message_type:["error"]});D.current.setMessages(e=>[...e,t])},onStatusActive:()=>{y()}})),U=r(()=>[...A.current.entries()].sort((e,t)=>e[0]-t[0]).map(([,e])=>e.text).join(" ").trim(),[]),q=r(()=>{w.current=null,A.current=new Map,C.current="",v(i())},[v]),G=r(()=>{const e=w.current;if(e){const t=U();t&&D.current.updateStreamingMessage(e,t),D.current.commitStreamingMessages()}q()},[U,q]),H=r(()=>{D.current.discardStreamingMessages(),q()},[q]),K=r(()=>{if(w.current)return;const e=`${m}${I()}`;w.current=e,A.current=new Map,C.current="",v(d({chatMsgId:e}))},[v]),V=r(e=>{v(p(e));const t=U();t&&w.current&&t!==C.current&&(C.current=t,D.current.updateStreamingMessage(w.current,t))},[v,U]),F=r(e=>{const t=e,r=t?.type,n=t?.data;switch(E.debug({message:"Voice server-message received",api_name:M.current.apiName,conversation_id:N??void 0,event_type:r??"unknown"}),r){case l.STATUS_UPDATE:x({kind:"status_update",statusText:n?.text??""},O);break;case l.STARTED_THINKING:v(c(!0)),y();break;case l.STOPPED_THINKING:L(k.FINALIZE);break;case l.CLEAR_STATUS:M.current.onClearStatus?.();break;case l.WIDGET:n&&x({kind:"widget",widget:n},O);break;case l.PAYMENT:n&&x({kind:"payment",payment:n},O);break;case l.REQUEST_DOCUMENTS:n&&x({kind:"request_documents",requestDocuments:n},O);break;case l.CONVERSATION_TITLE:{const e="string"==typeof n?.text?n.text.trim():"";e&&x({kind:"conversation_title",title:e},O);break}case l.SESSION_ENDED:{const e="string"==typeof n?.reason?n.reason:"ended";M.current.onSessionEnded?.(e);break}default:E.debug({message:"Voice unhandled server-message",api_name:M.current.apiName,event_type:r??"unknown"})}},[O,v,y,L,N]);return{bridgeRef:D,botTurnMsgIdRef:w,liveSegmentsRef:A,startResponseTimer:y,finalizeResponseTimer:P,cancelResponseTimer:B,clearIndicator:L,sharedDeps:O,buildTurnText:U,clearTurnState:q,commitBotTurn:G,discardBotTurn:H,openBotTurnIfNeeded:K,ingestBotSegment:V,routeServerMessage:F}}export{N as a,h as f,b as u};
|
package/dist/webrtc.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { AttachedDocument } from '@1interface/shared-core';
|
|
2
|
+
import { Message } from '@1interface/shared-core';
|
|
3
|
+
import { ModelContextPart } from '@1interface/shared-core';
|
|
4
|
+
import { PaymentInitPayload } from '@1interface/shared-core';
|
|
5
|
+
import { RequestDocumentsPayload } from '@1interface/shared-core';
|
|
6
|
+
import { Transport } from '@pipecat-ai/client-js';
|
|
7
|
+
|
|
8
|
+
export declare const useVoiceProviderValue: ({ conversationId, voiceBridge, transportFactory, assistantStartsConversation, }: VoiceProviderParams) => VoiceContextValue;
|
|
9
|
+
|
|
10
|
+
declare const VOICE_DISPLAY_STATES: {
|
|
11
|
+
readonly OFFLINE: "offline";
|
|
12
|
+
readonly CONNECTING: "connecting";
|
|
13
|
+
readonly READY: "ready";
|
|
14
|
+
readonly IDLE: "idle";
|
|
15
|
+
readonly LISTENING: "listening";
|
|
16
|
+
readonly MUTED: "muted";
|
|
17
|
+
readonly PROCESSING: "processing";
|
|
18
|
+
readonly SPEAKING: "speaking";
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
declare const VOICE_PIPELINE_STATUSES: {
|
|
22
|
+
readonly IDLE: "idle";
|
|
23
|
+
readonly LISTENING: "listening";
|
|
24
|
+
readonly PROCESSING: "processing";
|
|
25
|
+
readonly SPEAKING: "speaking";
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
declare interface VoiceChatBridge {
|
|
29
|
+
setMessages: React.Dispatch<React.SetStateAction<Message[]>>;
|
|
30
|
+
updateStreamingMessage: (chatMsgId: string, content: string) => void;
|
|
31
|
+
commitStreamingMessages: () => void;
|
|
32
|
+
discardStreamingMessages: () => void;
|
|
33
|
+
setVoiceSendText: (fn: ((text: string, options?: VoiceSendTextOptions) => void) | null) => void;
|
|
34
|
+
onPaymentInit?: (init: PaymentInitPayload) => void;
|
|
35
|
+
onRequestDocuments?: (payload: RequestDocumentsPayload) => void;
|
|
36
|
+
onConversationTitle?: (title: string) => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
declare interface VoiceContextValue {
|
|
40
|
+
displayState: VoiceDisplayState;
|
|
41
|
+
pipelineStatus: VoicePipelineStatus;
|
|
42
|
+
isAgentWorking: boolean;
|
|
43
|
+
enterVoiceMode: () => Promise<boolean>;
|
|
44
|
+
exitVoiceMode: () => void;
|
|
45
|
+
toggleMute: () => void;
|
|
46
|
+
sendText: (text: string, options?: VoiceSendTextOptions) => void;
|
|
47
|
+
muted: boolean;
|
|
48
|
+
serverMuted: boolean;
|
|
49
|
+
sessionEndedSignal: number;
|
|
50
|
+
setInputDevice: (deviceId: string) => void;
|
|
51
|
+
setOutputDevice: (deviceId: string) => void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
declare type VoiceDisplayState = (typeof VOICE_DISPLAY_STATES)[keyof typeof VOICE_DISPLAY_STATES];
|
|
55
|
+
|
|
56
|
+
declare type VoicePipelineStatus = (typeof VOICE_PIPELINE_STATUSES)[keyof typeof VOICE_PIPELINE_STATUSES];
|
|
57
|
+
|
|
58
|
+
declare interface VoiceProviderBaseParams {
|
|
59
|
+
conversationId: string | null;
|
|
60
|
+
voiceBridge: VoiceChatBridge;
|
|
61
|
+
assistantStartsConversation: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export declare interface VoiceProviderParams extends VoiceProviderBaseParams {
|
|
65
|
+
transportFactory: VoiceTransportFactory;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
declare interface VoiceSendTextOptions {
|
|
69
|
+
modelContext?: ModelContextPart[];
|
|
70
|
+
documents?: AttachedDocument[];
|
|
71
|
+
messageType?: string[];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
declare type VoiceTransportFactory = () => Transport | Promise<Transport>;
|
|
75
|
+
|
|
76
|
+
export { }
|
package/dist/webrtc.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./sessionHelpers-ALyIwLPk.cjs"),r=require("react"),t=require("react-redux"),n=require("./useVoiceSessionCore-Bir8eqjh.cjs"),i=require("@1interface/shared-core");function s(){const e={};for(const[r,t]of Object.entries(i.getCustomHeaders()))t&&(e[r]=t);const r=i.readAccessToken();r&&(e[i.AUTH_HEADERS.ACCESS_TOKEN]=r);const t=i.readDeviceToken();return t&&(e[i.DEVICE_TOKEN_HEADER]=t),e}async function o(e){try{const r=await fetch(i.API_ENDPOINTS.WEBRTC_ICE(e),{headers:s()});return r.ok?(await r.json()).iceServers??[]:(i.logger.warn({message:"Voice ICE fetch failed — continuing without TURN",api_name:"voice-webrtc-ice",status:r.status}),[])}catch(e){return i.logger.warn({message:"Voice ICE fetch error — continuing without TURN",api_name:"voice-webrtc-ice",error_message:e instanceof Error?e.message:String(e)}),[]}}exports.useVoiceProviderValue=({conversationId:c,voiceBridge:a,transportFactory:u,assistantStartsConversation:d})=>{const l=function(c,a,u,d){const l=t.useDispatch(),g=t.useStore(),m=e.useAudioPlatform(),v=t.useSelector(e=>e.auth.accessToken),_=r.useRef(null),{bridgeRef:p,botTurnMsgIdRef:E,liveSegmentsRef:I,startResponseTimer:T,clearIndicator:S,sharedDeps:f,commitBotTurn:R,discardBotTurn:V,openBotTurnIfNeeded:C,ingestBotSegment:b,routeServerMessage:N}=n.useVoiceSessionCore(c,a,{apiName:"voice-rtvi",onSessionEnded:e=>{_.current={reason:e}}}),h=r.useRef(!1),O=r.useRef(!1),A=r.useRef(null),k=r.useRef(""),M=r.useRef(null),P=r.useRef(null),w=r.useRef(null),U=r.useRef(null),y=r.useRef(!0),L=r.useRef(null),x=r.useRef(!1),D=r.useRef(""),B=r.useRef(0),H=r.useRef(0),j=r.useRef(!1),q=r.useRef(0),F=r.useRef(null),G=r.useRef(0),W=r.useRef(0),X=r.useRef(null),K=r.useCallback(()=>{U.current&&(clearTimeout(U.current),U.current=null)},[]),Y=r.useCallback(r=>{K(),L.current&&(clearTimeout(L.current),L.current=null),x.current=!1,D.current="",l(e.setMicrophones([])),l(e.setSpeakers([])),w.current?.(),w.current=null,M.current=null,j.current=!1,l(e.setListeningEnabled(!1)),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.IDLE)),l(e.setConnectionStatus(e.VOICE_CONNECTION_STATUSES.DISCONNECTED)),l(e.setInterimTranscript(null)),l(e.setServerMuted(!1)),A.current=null,k.current="",h.current=!1,O.current=!1,r?R():V(),S(i.INDICATOR_TIMER_ACTIONS.CANCEL)},[l,S,K,R,V]),Z=r.useCallback(e=>{const r=e.spoken_progress?(e.spoken_progress?.accumulated_text??"")+(e.spoken_progress?.remaining_text??""):e.text??"";if(!r)return;C();const t=h.current?n.foldRealtimeBotOutput(I.current,r):n.foldCascadeBotOutput(I.current,e,r,O.current);b(t)},[C,b,I]),$=r.useRef(()=>{}),z=r.useRef(async()=>{}),J=r.useCallback(r=>{const t=()=>r===B.current;return{onConnected:()=>{if(!t())return;i.logger.info({message:"Voice RTVI connected",api_name:"voice-rtvi",conversation_id:c??void 0}),l(e.setConnectionStatus(e.VOICE_CONNECTION_STATUSES.CONNECTED)),l(e.setListeningEnabled(!0)),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.IDLE)),M.current&&M.current.isMicEnabled!==y.current&&(i.logger.debug({message:"Voice re-asserting mic state on reconnect",api_name:"voice-rtvi",conversation_id:c??void 0,enabled:y.current}),M.current.enableMic(y.current));const{selectedMicId:r,selectedSpeakerId:n}=g.getState().voice;r&&M.current?.updateMic(r),n&&M.current?.updateSpeaker(n);const s=e=>r=>i.logger.warn({message:"Voice device enumeration failed",api_name:"voice-mic",conversation_id:c??void 0,device_kind:e,error_message:r instanceof Error?r.message:String(r)});M.current?.getAllMics().then(r=>{t()&&l(e.setMicrophones(r.map(e=>({deviceId:e.deviceId,label:e.label}))))}).catch(s("mics")),M.current?.getAllSpeakers().then(r=>{t()&&l(e.setSpeakers(r.map(e=>({deviceId:e.deviceId,label:e.label}))))}).catch(s("speakers")),x.current=!0},onBotReady:r=>{if(!t())return;const n=r?.about;h.current=n?.pipeline_mode===e.VOICE_PIPELINE_MODES.REALTIME,O.current=!0===n?.tts_word_timestamps,G.current=0,W.current=0,q.current=0,i.logger.info({message:"Voice RTVI bot-ready",api_name:"voice-rtvi",conversation_id:c??void 0,pipeline_mode:n?.pipeline_mode??"cascade",tts_word_timestamps:O.current,assistant_starts:d}),K(),d&&(l(e.setIsAgentWorking(!0)),T())},onDisconnected:()=>{if(!t())return;i.logger.info({message:"Voice RTVI disconnected",api_name:"voice-rtvi",conversation_id:c??void 0,drop_outcome:X.current??"transient",reconnect_attempts:G.current}),Y(!0);const r=H.current,n=X.current;X.current=null;const s=_.current;return _.current=null,s?(i.logger.info({message:"Voice session ended by server — no reconnect",api_name:"voice-rtvi",conversation_id:c??void 0,reason:s.reason}),"idle_timeout"===s.reason&&i.showToast({title:"Voice ended",description:"It got quiet for a while, so we wrapped up. Start again anytime."}),void l(e.signalSessionEnded())):"terminal"===n?(i.logger.warn({message:"Voice RTVI terminal error — no retry",api_name:"voice-rtvi"}),void l(e.signalSessionEnded())):"auth"===n?W.current>=e.VOICE_MAX_AUTH_REFRESHES||null==c?(i.logger.warn({message:"Voice RTVI auth refresh budget exhausted — giving up",api_name:"voice-rtvi",conversation_id:c??void 0}),void l(e.signalSessionEnded())):(W.current+=1,i.logger.info({message:"Voice RTVI auth drop — refreshing token then reconnecting",api_name:"voice-rtvi",conversation_id:c??void 0,auth_refresh_count:W.current}),void(async()=>{let t=!1;try{t=!!await(i.getTokenRefreshHandler()?.())}catch{}r===H.current&&(t?z.current():(i.logger.warn({message:"Voice RTVI token refresh failed — no reconnect",api_name:"voice-rtvi",conversation_id:c??void 0}),l(e.signalSessionEnded())))})()):G.current>=e.VOICE_MAX_AUTO_RECONNECTS||null==c?(G.current>=e.VOICE_MAX_AUTO_RECONNECTS&&(i.logger.warn({message:"Voice RTVI auto-reconnect cap reached — giving up",api_name:"voice-rtvi",conversation_id:c??void 0,reconnect_attempts:G.current}),i.showErrorToast("Voice connection lost. Please try again.")),void l(e.signalSessionEnded())):(G.current+=1,i.logger.info({message:"Voice RTVI scheduling auto-reconnect",api_name:"voice-rtvi",conversation_id:c??void 0,reconnect_attempts:G.current,delay_ms:e.VOICE_RECONNECT_DELAY_MS}),F.current&&clearTimeout(F.current),void(F.current=setTimeout(()=>{F.current=null,z.current()},e.VOICE_RECONNECT_DELAY_MS)))},onError:r=>{if(!t())return;const n=r?.data,s=n?.error??n?.message??"Voice error";i.handleSharedEvent({kind:"error",error:s},f),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.IDLE)),S(i.INDICATOR_TIMER_ACTIONS.CANCEL),n?.fatal&&(e.VOICE_AUTH_ERROR_PATTERNS.some(e=>e.test(s))?X.current="auth":e.VOICE_TERMINAL_ERROR_PATTERNS.some(e=>e.test(s))&&(X.current="terminal"),i.logger.warn({message:"Voice RTVI fatal error — closing transport",api_name:"voice-rtvi",conversation_id:c??void 0,drop_outcome:X.current??"generic"}),M.current?.disconnect().catch(()=>{}))},onTrackStarted:(e,r)=>{t()&&(r?.local||"audio"!==e.kind||(i.logger.debug({message:"Voice RTVI bot audio track started",api_name:"voice-rtvi",conversation_id:c??void 0}),w.current?.(),w.current=m.attachBotAudioTrack(e)))},onAvailableMicsUpdated:r=>{t()&&(l(e.setMicrophones(r.map(e=>({deviceId:e.deviceId,label:e.label})))),x.current&&(L.current&&clearTimeout(L.current),L.current=setTimeout(()=>{if(L.current=null,!M.current)return;const{selectedMicId:e,microphones:r}=g.getState().voice;i.logger.info({message:"Voice audio devices changed",api_name:"voice-mic",microphones:r.map(e=>e.label).join(", ")}),i.logger.debug({message:"Voice re-acquiring mic (device change)",api_name:"voice-rtvi",enabled:y.current,device:e||"default"}),M.current.updateMic(e)},300)))},onAvailableSpeakersUpdated:r=>{if(!t())return;if(l(e.setSpeakers(r.map(e=>({deviceId:e.deviceId,label:e.label})))),!x.current)return;const n=r.map(e=>e.label).join(", ");n!==D.current&&(D.current=n,i.logger.info({message:"Voice audio output devices changed",api_name:"voice-mic",speakers:n}))},onMicUpdated:e=>{if(!t())return;const r=M.current;r&&r.isMicEnabled!==y.current&&(i.logger.debug({message:"Voice re-asserting mic state after track swap",api_name:"voice-rtvi",conversation_id:c??void 0,enabled:y.current,device:e?.label}),r.enableMic(y.current))},onUserMuteStarted:()=>{t()&&i.logger.debug({message:"Voice RTVI user-mute-started (ignored)",api_name:"voice-rtvi",conversation_id:c??void 0})},onUserMuteStopped:()=>{t()&&(i.logger.debug({message:"Voice RTVI user-mute-stopped",api_name:"voice-rtvi",conversation_id:c??void 0,honored:d}),d&&l(e.setServerMuted(!1)))},onUserStartedSpeaking:()=>{t()&&(i.logger.debug({message:"Voice RTVI user started speaking",api_name:"voice-rtvi",conversation_id:c??void 0}),A.current=null,k.current="",l(e.setInterimTranscript(null)),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.LISTENING)))},onUserStoppedSpeaking:()=>{if(!t())return;i.logger.debug({message:"Voice RTVI user stopped speaking",api_name:"voice-rtvi",conversation_id:c??void 0,has_interim:!!k.current.trim()}),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.PROCESSING)),l(e.setIsAgentWorking(!0)),l(e.setStatusText(null)),T();const r=k.current.trim();if(r){const t=`${e.VOICE_USER_ID_PREFIX}${i.generateUniqueId()}`;A.current=t,l(e.setInterimTranscript(null)),p.current.setMessages(n=>[...n,e.makeUserMessage(r,t)])}},onUserTranscript:r=>{if(!t())return;if(!r.final)return void(r.text&&(k.current=r.text,h.current||l(e.setInterimTranscript(r.text))));l(e.setInterimTranscript(null));const n=r.text&&r.text.trim()?r.text:"";i.logger.debug({message:"Voice RTVI user transcript (final)",api_name:"voice-rtvi",conversation_id:c??void 0,has_text:!!n});const s=A.current;A.current=null,s?n&&p.current.setMessages(e=>e.map(e=>e.id===s?{...e,message:n}:e)):n&&p.current.setMessages(r=>[...r,e.makeUserMessage(n)])},onBotStartedSpeaking:()=>{t()&&(i.logger.debug({message:"Voice RTVI bot started speaking",api_name:"voice-rtvi",conversation_id:c??void 0}),C(),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.SPEAKING)),S(i.INDICATOR_TIMER_ACTIONS.FINALIZE))},onBotOutput:e=>{t()&&Z(e)},onBotStoppedSpeaking:()=>{t()&&(i.logger.debug({message:"Voice RTVI bot stopped speaking",api_name:"voice-rtvi",conversation_id:c??void 0}),R(),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.IDLE)),S(i.INDICATOR_TIMER_ACTIONS.FINALIZE),d&&g.getState().voice.serverMuted&&(i.logger.warn({message:"Voice greeting mute-hold released on bot-turn end (server unmute missing)",api_name:"voice-rtvi",conversation_id:c??void 0}),l(e.setServerMuted(!1))))},onServerMessage:e=>{t()&&N(e)}}},[l,g,m,K,Y,S,T,Z,C,R,N,f,p,d,c]),Q=r.useCallback(async()=>{if(M.current||j.current)return;if(!c)return;if(j.current=!0,i.logger.info({message:"Voice RTVI connecting",api_name:"voice-rtvi",conversation_id:c,assistant_starts:d}),d&&(y.current=!1,l(e.setServerMuted(!0)),i.logger.debug({message:"Voice greeting mute-hold armed",api_name:"voice-rtvi",conversation_id:c})),i.isTokenAboutToExpire(i.readTokenExpiresAt())&&q.current<e.VOICE_MAX_AUTH_REFRESHES){q.current+=1,i.logger.debug({message:"Voice pre-connect token refresh (freshness gate)",api_name:"voice-rtvi",conversation_id:c,freshness_refresh_count:q.current});const e=H.current;try{await Promise.resolve(i.getTokenRefreshHandler()?.())}catch{}if(e!==H.current)return}const r=++B.current,t=H.current;_.current=null,X.current=null,l(e.setConnectionStatus(e.VOICE_CONNECTION_STATUSES.CONNECTING));const n=()=>t!==H.current||r!==B.current;try{const[{PipecatClient:t,LogLevel:a,RTVIMessage:d},g,m]=await Promise.all([import("@pipecat-ai/client-js"),o(c),u()]);if(n())return;P.current=d;const v=new t({transport:m,enableMic:!0,enableCam:!1,callbacks:J(r)});if(v.setLogLevel(a.WARN),n())return void v.disconnect().catch(()=>{});M.current=v,i.logger.debug({message:"Voice RTVI client constructed",api_name:"voice-rtvi",conversation_id:c,enable_mic:y.current}),K(),U.current=setTimeout(()=>{i.logger.warn({message:"Voice RTVI connect timed out (no bot-ready)",api_name:"voice-rtvi"}),i.showErrorToast("Voice connection timed out. Please try again."),l(e.signalSessionEnded()),$.current()},e.VOICE_CONNECT_TIMEOUT_MS),await v.connect({webrtcRequestParams:{endpoint:i.API_ENDPOINTS.WEBRTC_OFFER(c),headers:new Headers(s())},iceConfig:{iceServers:g}})}catch(t){const n=(e=>{const r="object"==typeof e&&null!==e&&"status"in e?e.status:void 0;return"number"==typeof r?r:void 0})(t);i.logger.error({message:"Voice RTVI connect failed",api_name:"voice-rtvi",status:n,error_message:t instanceof Error?t.message:String(t)});const s=(e=>void 0!==e&&e>=400&&e<500)(n),o=s?await(async(r,t)=>{const n=429===t?"Voice session limit reached. Please try again later.":e.VOICE_CONNECT_ERROR_MESSAGE,i="object"==typeof r&&null!==r&&"cause"in r?r.cause:void 0;if(!(i instanceof Response))return n;try{const e=await i.json(),r=e?.data?.error_message??e?.data?.message;return"string"==typeof r?r:n}catch{return n}})(t,n):e.VOICE_CONNECT_ERROR_MESSAGE;r===B.current&&(i.showErrorToast(o),s&&(F.current&&(clearTimeout(F.current),F.current=null),G.current=0,l(e.signalSessionEnded())),Y(!1))}},[c,l,u,J,K,Y,d]),ee=r.useCallback((r=!0)=>{i.logger.info({message:"Voice RTVI disconnect (intentional teardown)",api_name:"voice-rtvi",conversation_id:c??void 0,commit_turn:r}),H.current+=1,B.current+=1,q.current=0,F.current&&(clearTimeout(F.current),F.current=null),G.current=0,W.current=0,X.current=null,y.current=!0,l(e.setMuted(!1));const t=M.current;t?.disconnect().catch(e=>{i.logger.warn({message:"Voice RTVI disconnect threw",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}),Y(r)},[Y,l,c]),re=r.useCallback(e=>{y.current=e,M.current&&(i.logger.debug({message:"Voice mic enable toggled",api_name:"voice-rtvi",enabled:e}),M.current.enableMic(e))},[]),te=r.useCallback(r=>{l(e.setSelectedMicId(r)),M.current?.updateMic(r)},[l]),ne=r.useCallback(r=>{l(e.setSelectedSpeakerId(r)),M.current?.updateSpeaker(r)},[l]),ie=r.useCallback((r,t)=>{const n=M.current;if(!n)return;const s=t?.modelContext;i.logger.info({message:"Voice RTVI send-text",api_name:"voice-rtvi",conversation_id:c??void 0,run_immediately:!0,has_model_context:Boolean(s?.length)});try{const e=P.current;s?.length&&e?n.transport.sendMessage(new e("send-text",{content:r,options:{run_immediately:!0,audio_response:!0},model_context:s})):n.sendText(r,{run_immediately:!0,audio_response:!0})}catch(e){return void i.logger.error({message:"Voice RTVI send-text failed",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}E.current&&R(),p.current.setMessages(n=>[...n,e.makeUserMessage(r,void 0,t?.documents,t?.messageType)]),l(e.setIsAgentWorking(!0)),l(e.setStatusText(null)),T(),l(e.setPipelineStatus(e.VOICE_PIPELINE_STATUSES.PROCESSING))},[l,T,R,E,p,c]);r.useEffect(()=>{$.current=ee},[ee]),r.useEffect(()=>{z.current=Q},[Q]);const se=r.useRef(c),oe=r.useRef(!1);r.useEffect(()=>{const e=se.current;if(se.current=c,e===c)return;const r=j.current;r&&(oe.current=!0),r&&ee(!1),oe.current&&null!=c&&(oe.current=!1,Q())},[c,ee,Q]);const ce=r.useRef(v);return r.useEffect(()=>{const e=ce.current;ce.current=v,e&&!v&&j.current&&ee(!1)},[v,ee]),r.useEffect(()=>()=>{$.current()},[]),{connect:Q,disconnect:ee,sendText:ie,setMicEnabled:re,setInputDevice:te,setOutputDevice:ne}}(c,a,u,d);return e.useVoiceContextValue(l,{voiceBridge:a})};
|
package/dist/webrtc.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{u as e,s as r,a as t,b as n,c as i,V as c,d as o,e as a,f as s,g as u,m as d,h as m,i as l,j as v,k as p,l as g,n as _,o as f,p as V,q as I,r as b,t as h,v as T,w as R,x as S,y as E,z as w}from"./sessionHelpers-BIwU9Fpt.js";import{useRef as k,useCallback as y,useEffect as M}from"react";import{useDispatch as C,useStore as x,useSelector as N}from"react-redux";import{u as B,f as L,a as A}from"./useVoiceSessionCore-Cwkj3eVq.js";import{API_ENDPOINTS as j,logger as D,getCustomHeaders as O,readAccessToken as P,AUTH_HEADERS as U,readDeviceToken as F,DEVICE_TOKEN_HEADER as G,INDICATOR_TIMER_ACTIONS as q,generateUniqueId as W,handleSharedEvent as H,showToast as K,getTokenRefreshHandler as Z,showErrorToast as $,isTokenAboutToExpire as z,readTokenExpiresAt as J}from"@1interface/shared-core";function Q(){const e={};for(const[r,t]of Object.entries(O()))t&&(e[r]=t);const r=P();r&&(e[U.ACCESS_TOKEN]=r);const t=F();return t&&(e[G]=t),e}async function X(e){try{const r=await fetch(j.WEBRTC_ICE(e),{headers:Q()});return r.ok?(await r.json()).iceServers??[]:(D.warn({message:"Voice ICE fetch failed — continuing without TURN",api_name:"voice-webrtc-ice",status:r.status}),[])}catch(e){return D.warn({message:"Voice ICE fetch error — continuing without TURN",api_name:"voice-webrtc-ice",error_message:e instanceof Error?e.message:String(e)}),[]}}const Y=({conversationId:O,voiceBridge:P,transportFactory:U,assistantStartsConversation:F})=>{const G=function(w,O,P,U){const F=C(),G=x(),Y=e(),ee=N(e=>e.auth.accessToken),re=k(null),{bridgeRef:te,botTurnMsgIdRef:ne,liveSegmentsRef:ie,startResponseTimer:ce,clearIndicator:oe,sharedDeps:ae,commitBotTurn:se,discardBotTurn:ue,openBotTurnIfNeeded:de,ingestBotSegment:me,routeServerMessage:le}=B(w,O,{apiName:"voice-rtvi",onSessionEnded:e=>{re.current={reason:e}}}),ve=k(!1),pe=k(!1),ge=k(null),_e=k(""),fe=k(null),Ve=k(null),Ie=k(null),be=k(null),he=k(!0),Te=k(null),Re=k(!1),Se=k(""),Ee=k(0),we=k(0),ke=k(!1),ye=k(0),Me=k(null),Ce=k(0),xe=k(0),Ne=k(null),Be=y(()=>{be.current&&(clearTimeout(be.current),be.current=null)},[]),Le=y(e=>{Be(),Te.current&&(clearTimeout(Te.current),Te.current=null),Re.current=!1,Se.current="",F(r([])),F(t([])),Ie.current?.(),Ie.current=null,fe.current=null,ke.current=!1,F(n(!1)),F(i(c.IDLE)),F(o(a.DISCONNECTED)),F(s(null)),F(u(!1)),ge.current=null,_e.current="",ve.current=!1,pe.current=!1,e?se():ue(),oe(q.CANCEL)},[F,oe,Be,se,ue]),Ae=y(e=>{const r=e.spoken_progress?(e.spoken_progress?.accumulated_text??"")+(e.spoken_progress?.remaining_text??""):e.text??"";if(!r)return;de();const t=ve.current?L(ie.current,r):A(ie.current,e,r,pe.current);me(t)},[de,me,ie]),je=k(()=>{}),De=k(async()=>{}),Oe=y(e=>{const h=()=>e===Ee.current;return{onConnected:()=>{if(!h())return;D.info({message:"Voice RTVI connected",api_name:"voice-rtvi",conversation_id:w??void 0}),F(o(a.CONNECTED)),F(n(!0)),F(i(c.IDLE)),fe.current&&fe.current.isMicEnabled!==he.current&&(D.debug({message:"Voice re-asserting mic state on reconnect",api_name:"voice-rtvi",conversation_id:w??void 0,enabled:he.current}),fe.current.enableMic(he.current));const{selectedMicId:e,selectedSpeakerId:s}=G.getState().voice;e&&fe.current?.updateMic(e),s&&fe.current?.updateSpeaker(s);const u=e=>r=>D.warn({message:"Voice device enumeration failed",api_name:"voice-mic",conversation_id:w??void 0,device_kind:e,error_message:r instanceof Error?r.message:String(r)});fe.current?.getAllMics().then(e=>{h()&&F(r(e.map(e=>({deviceId:e.deviceId,label:e.label}))))}).catch(u("mics")),fe.current?.getAllSpeakers().then(e=>{h()&&F(t(e.map(e=>({deviceId:e.deviceId,label:e.label}))))}).catch(u("speakers")),Re.current=!0},onBotReady:e=>{if(!h())return;const r=e?.about;ve.current=r?.pipeline_mode===b.REALTIME,pe.current=!0===r?.tts_word_timestamps,Ce.current=0,xe.current=0,ye.current=0,D.info({message:"Voice RTVI bot-ready",api_name:"voice-rtvi",conversation_id:w??void 0,pipeline_mode:r?.pipeline_mode??"cascade",tts_word_timestamps:pe.current,assistant_starts:U}),Be(),U&&(F(m(!0)),ce())},onDisconnected:()=>{if(!h())return;D.info({message:"Voice RTVI disconnected",api_name:"voice-rtvi",conversation_id:w??void 0,drop_outcome:Ne.current??"transient",reconnect_attempts:Ce.current}),Le(!0);const e=we.current,r=Ne.current;Ne.current=null;const t=re.current;return re.current=null,t?(D.info({message:"Voice session ended by server — no reconnect",api_name:"voice-rtvi",conversation_id:w??void 0,reason:t.reason}),"idle_timeout"===t.reason&&K({title:"Voice ended",description:"It got quiet for a while, so we wrapped up. Start again anytime."}),void F(_())):"terminal"===r?(D.warn({message:"Voice RTVI terminal error — no retry",api_name:"voice-rtvi"}),void F(_())):"auth"===r?xe.current>=f||null==w?(D.warn({message:"Voice RTVI auth refresh budget exhausted — giving up",api_name:"voice-rtvi",conversation_id:w??void 0}),void F(_())):(xe.current+=1,D.info({message:"Voice RTVI auth drop — refreshing token then reconnecting",api_name:"voice-rtvi",conversation_id:w??void 0,auth_refresh_count:xe.current}),void(async()=>{let r=!1;try{r=!!await(Z()?.())}catch{}e===we.current&&(r?De.current():(D.warn({message:"Voice RTVI token refresh failed — no reconnect",api_name:"voice-rtvi",conversation_id:w??void 0}),F(_())))})()):Ce.current>=V||null==w?(Ce.current>=V&&(D.warn({message:"Voice RTVI auto-reconnect cap reached — giving up",api_name:"voice-rtvi",conversation_id:w??void 0,reconnect_attempts:Ce.current}),$("Voice connection lost. Please try again.")),void F(_())):(Ce.current+=1,D.info({message:"Voice RTVI scheduling auto-reconnect",api_name:"voice-rtvi",conversation_id:w??void 0,reconnect_attempts:Ce.current,delay_ms:I}),Me.current&&clearTimeout(Me.current),void(Me.current=setTimeout(()=>{Me.current=null,De.current()},I)))},onError:e=>{if(!h())return;const r=e?.data,t=r?.error??r?.message??"Voice error";H({kind:"error",error:t},ae),F(i(c.IDLE)),oe(q.CANCEL),r?.fatal&&(p.some(e=>e.test(t))?Ne.current="auth":g.some(e=>e.test(t))&&(Ne.current="terminal"),D.warn({message:"Voice RTVI fatal error — closing transport",api_name:"voice-rtvi",conversation_id:w??void 0,drop_outcome:Ne.current??"generic"}),fe.current?.disconnect().catch(()=>{}))},onTrackStarted:(e,r)=>{h()&&(r?.local||"audio"!==e.kind||(D.debug({message:"Voice RTVI bot audio track started",api_name:"voice-rtvi",conversation_id:w??void 0}),Ie.current?.(),Ie.current=Y.attachBotAudioTrack(e)))},onAvailableMicsUpdated:e=>{h()&&(F(r(e.map(e=>({deviceId:e.deviceId,label:e.label})))),Re.current&&(Te.current&&clearTimeout(Te.current),Te.current=setTimeout(()=>{if(Te.current=null,!fe.current)return;const{selectedMicId:e,microphones:r}=G.getState().voice;D.info({message:"Voice audio devices changed",api_name:"voice-mic",microphones:r.map(e=>e.label).join(", ")}),D.debug({message:"Voice re-acquiring mic (device change)",api_name:"voice-rtvi",enabled:he.current,device:e||"default"}),fe.current.updateMic(e)},300)))},onAvailableSpeakersUpdated:e=>{if(!h())return;if(F(t(e.map(e=>({deviceId:e.deviceId,label:e.label})))),!Re.current)return;const r=e.map(e=>e.label).join(", ");r!==Se.current&&(Se.current=r,D.info({message:"Voice audio output devices changed",api_name:"voice-mic",speakers:r}))},onMicUpdated:e=>{if(!h())return;const r=fe.current;r&&r.isMicEnabled!==he.current&&(D.debug({message:"Voice re-asserting mic state after track swap",api_name:"voice-rtvi",conversation_id:w??void 0,enabled:he.current,device:e?.label}),r.enableMic(he.current))},onUserMuteStarted:()=>{h()&&D.debug({message:"Voice RTVI user-mute-started (ignored)",api_name:"voice-rtvi",conversation_id:w??void 0})},onUserMuteStopped:()=>{h()&&(D.debug({message:"Voice RTVI user-mute-stopped",api_name:"voice-rtvi",conversation_id:w??void 0,honored:U}),U&&F(u(!1)))},onUserStartedSpeaking:()=>{h()&&(D.debug({message:"Voice RTVI user started speaking",api_name:"voice-rtvi",conversation_id:w??void 0}),ge.current=null,_e.current="",F(s(null)),F(i(c.LISTENING)))},onUserStoppedSpeaking:()=>{if(!h())return;D.debug({message:"Voice RTVI user stopped speaking",api_name:"voice-rtvi",conversation_id:w??void 0,has_interim:!!_e.current.trim()}),F(i(c.PROCESSING)),F(m(!0)),F(l(null)),ce();const e=_e.current.trim();if(e){const r=`${v}${W()}`;ge.current=r,F(s(null)),te.current.setMessages(t=>[...t,d(e,r)])}},onUserTranscript:e=>{if(!h())return;if(!e.final)return void(e.text&&(_e.current=e.text,ve.current||F(s(e.text))));F(s(null));const r=e.text&&e.text.trim()?e.text:"";D.debug({message:"Voice RTVI user transcript (final)",api_name:"voice-rtvi",conversation_id:w??void 0,has_text:!!r});const t=ge.current;ge.current=null,t?r&&te.current.setMessages(e=>e.map(e=>e.id===t?{...e,message:r}:e)):r&&te.current.setMessages(e=>[...e,d(r)])},onBotStartedSpeaking:()=>{h()&&(D.debug({message:"Voice RTVI bot started speaking",api_name:"voice-rtvi",conversation_id:w??void 0}),de(),F(i(c.SPEAKING)),oe(q.FINALIZE))},onBotOutput:e=>{h()&&Ae(e)},onBotStoppedSpeaking:()=>{h()&&(D.debug({message:"Voice RTVI bot stopped speaking",api_name:"voice-rtvi",conversation_id:w??void 0}),se(),F(i(c.IDLE)),oe(q.FINALIZE),U&&G.getState().voice.serverMuted&&(D.warn({message:"Voice greeting mute-hold released on bot-turn end (server unmute missing)",api_name:"voice-rtvi",conversation_id:w??void 0}),F(u(!1))))},onServerMessage:e=>{h()&&le(e)}}},[F,G,Y,Be,Le,oe,ce,Ae,de,se,le,ae,te,U,w]),Pe=y(async()=>{if(fe.current||ke.current)return;if(!w)return;if(ke.current=!0,D.info({message:"Voice RTVI connecting",api_name:"voice-rtvi",conversation_id:w,assistant_starts:U}),U&&(he.current=!1,F(u(!0)),D.debug({message:"Voice greeting mute-hold armed",api_name:"voice-rtvi",conversation_id:w})),z(J())&&ye.current<f){ye.current+=1,D.debug({message:"Voice pre-connect token refresh (freshness gate)",api_name:"voice-rtvi",conversation_id:w,freshness_refresh_count:ye.current});const e=we.current;try{await Promise.resolve(Z()?.())}catch{}if(e!==we.current)return}const e=++Ee.current,r=we.current;re.current=null,Ne.current=null,F(o(a.CONNECTING));const t=()=>r!==we.current||e!==Ee.current;try{const[{PipecatClient:r,LogLevel:n,RTVIMessage:i},c,o]=await Promise.all([import("@pipecat-ai/client-js"),X(w),P()]);if(t())return;Ve.current=i;const a=new r({transport:o,enableMic:!0,enableCam:!1,callbacks:Oe(e)});if(a.setLogLevel(n.WARN),t())return void a.disconnect().catch(()=>{});fe.current=a,D.debug({message:"Voice RTVI client constructed",api_name:"voice-rtvi",conversation_id:w,enable_mic:he.current}),Be(),be.current=setTimeout(()=>{D.warn({message:"Voice RTVI connect timed out (no bot-ready)",api_name:"voice-rtvi"}),$("Voice connection timed out. Please try again."),F(_()),je.current()},h),await a.connect({webrtcRequestParams:{endpoint:j.WEBRTC_OFFER(w),headers:new Headers(Q())},iceConfig:{iceServers:c}})}catch(r){const t=(e=>{const r="object"==typeof e&&null!==e&&"status"in e?e.status:void 0;return"number"==typeof r?r:void 0})(r);D.error({message:"Voice RTVI connect failed",api_name:"voice-rtvi",status:t,error_message:r instanceof Error?r.message:String(r)});const n=(e=>void 0!==e&&e>=400&&e<500)(t),i=n?await(async(e,r)=>{const t=429===r?"Voice session limit reached. Please try again later.":T,n="object"==typeof e&&null!==e&&"cause"in e?e.cause:void 0;if(!(n instanceof Response))return t;try{const e=await n.json(),r=e?.data?.error_message??e?.data?.message;return"string"==typeof r?r:t}catch{return t}})(r,t):T;e===Ee.current&&($(i),n&&(Me.current&&(clearTimeout(Me.current),Me.current=null),Ce.current=0,F(_())),Le(!1))}},[w,F,P,Oe,Be,Le,U]),Ue=y((e=!0)=>{D.info({message:"Voice RTVI disconnect (intentional teardown)",api_name:"voice-rtvi",conversation_id:w??void 0,commit_turn:e}),we.current+=1,Ee.current+=1,ye.current=0,Me.current&&(clearTimeout(Me.current),Me.current=null),Ce.current=0,xe.current=0,Ne.current=null,he.current=!0,F(R(!1));const r=fe.current;r?.disconnect().catch(e=>{D.warn({message:"Voice RTVI disconnect threw",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}),Le(e)},[Le,F,w]),Fe=y(e=>{he.current=e,fe.current&&(D.debug({message:"Voice mic enable toggled",api_name:"voice-rtvi",enabled:e}),fe.current.enableMic(e))},[]),Ge=y(e=>{F(S(e)),fe.current?.updateMic(e)},[F]),qe=y(e=>{F(E(e)),fe.current?.updateSpeaker(e)},[F]),We=y((e,r)=>{const t=fe.current;if(!t)return;const n=r?.modelContext;D.info({message:"Voice RTVI send-text",api_name:"voice-rtvi",conversation_id:w??void 0,run_immediately:!0,has_model_context:Boolean(n?.length)});try{const r=Ve.current;n?.length&&r?t.transport.sendMessage(new r("send-text",{content:e,options:{run_immediately:!0,audio_response:!0},model_context:n})):t.sendText(e,{run_immediately:!0,audio_response:!0})}catch(e){return void D.error({message:"Voice RTVI send-text failed",api_name:"voice-rtvi",error_message:e instanceof Error?e.message:String(e)})}ne.current&&se(),te.current.setMessages(t=>[...t,d(e,void 0,r?.documents,r?.messageType)]),F(m(!0)),F(l(null)),ce(),F(i(c.PROCESSING))},[F,ce,se,ne,te,w]);M(()=>{je.current=Ue},[Ue]),M(()=>{De.current=Pe},[Pe]);const He=k(w),Ke=k(!1);M(()=>{const e=He.current;if(He.current=w,e===w)return;const r=ke.current;r&&(Ke.current=!0),r&&Ue(!1),Ke.current&&null!=w&&(Ke.current=!1,Pe())},[w,Ue,Pe]);const Ze=k(ee);return M(()=>{const e=Ze.current;Ze.current=ee,e&&!ee&&ke.current&&Ue(!1)},[ee,Ue]),M(()=>()=>{je.current()},[]),{connect:Pe,disconnect:Ue,sendText:We,setMicEnabled:Fe,setInputDevice:Ge,setOutputDevice:qe}}(O,P,U,F);return w(G,{voiceBridge:P})};export{Y as useVoiceProviderValue};
|