@vox-ai/react 0.1.7 → 0.1.9

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.
@@ -52,6 +52,7 @@ export interface VoxAIOptions {
52
52
  onDisconnect?: () => void;
53
53
  onError?: (error: Error) => void;
54
54
  onMessage?: (message: VoxMessage) => void;
55
+ enableNoiseCancellation?: boolean;
55
56
  }
56
57
  /**
57
58
  * ConnectParams
@@ -80,7 +81,7 @@ export interface ConnectParams {
80
81
  * });
81
82
  */
82
83
  export declare function useVoxAI(options?: VoxAIOptions): {
83
- connect: ({ agentId, apiKey, dynamicVariables, metadata }: ConnectParams) => Promise<void>;
84
+ connect: ({ agentId, apiKey, dynamicVariables, metadata }: ConnectParams) => Promise<undefined>;
84
85
  disconnect: () => void;
85
86
  state: VoxAgentState;
86
87
  messages: VoxMessage[];
@@ -93,4 +94,6 @@ export declare function useVoxAI(options?: VoxAIOptions): {
93
94
  barCount?: number;
94
95
  updateInterval?: number;
95
96
  }) => number[];
97
+ toggleMic: (value: boolean) => void;
98
+ setVolume: (volume: number) => void;
96
99
  };
package/dist/lib.cjs CHANGED
@@ -1,2 +1,2 @@
1
- var e=require("react/jsx-runtime"),t=require("@livekit/components-react"),n=require("livekit-client"),r=require("react"),a=require("react-dom/client");function s(e){let{port:a,initialConfig:s}=e;const{agent:o,state:i}=t.useVoiceAssistant(),{send:c}=t.useChat(),[u,l]=r.useState({speaker:s.speaker||"agent",barCount:s.barCount,updateInterval:s.updateInterval}),p=t.useParticipantTracks([n.Track.Source.Microphone],null==o?void 0:o.identity)[0],d=t.useTrackTranscription(p),f=t.useAudioWaveform(p,{barCount:"agent"===u.speaker?u.barCount:120,updateInterval:"agent"===u.speaker?u.updateInterval:20}),m=t.useLocalParticipant(),g=t.useTrackTranscription({publication:m.microphoneTrack,source:n.Track.Source.Microphone,participant:m.localParticipant}),v=t.useParticipantTracks([n.Track.Source.Microphone],m.localParticipant.identity)[0],y=t.useAudioWaveform(v,{barCount:"user"===u.speaker?u.barCount:120,updateInterval:"user"===u.speaker?u.updateInterval:20});return r.useEffect(()=>{a&&f&&f.bars&&a.postMessage({type:"waveform_update",waveformData:f.bars,speaker:"agent"})},[a,f]),r.useEffect(()=>{a&&y&&y.bars&&a.postMessage({type:"waveform_update",waveformData:y.bars,speaker:"user"})},[a,y]),r.useEffect(()=>{if(!a)return;const e=e=>{const t=e.data;"waveform_config"===t.type&&t.config?"number"==typeof t.config.barCount&&"number"==typeof t.config.updateInterval&&l(t.config):"send_text"===t.type?c?c(t.text):console.error("sendChat function is not available"):"send_dtmf"===t.type&&(m.localParticipant?m.localParticipant.publishDtmf(101,t.digit):console.error("Local participant is not available for DTMF"))};return a.start(),a.addEventListener("message",e),()=>{a.removeEventListener("message",e)}},[a,c,m]),r.useEffect(()=>{a&&a.postMessage({type:"state_update",state:i})},[i,a]),r.useEffect(()=>{if(a&&d.segments.length>0){const e=d.segments.map(e=>({id:e.id,text:e.text,isFinal:e.final,timestamp:Date.now(),speaker:"agent"}));a.postMessage({type:"transcription_update",transcriptions:e})}},[d.segments,a]),r.useEffect(()=>{if(a&&g.segments.length>0){const e=g.segments.map(e=>({id:e.id,text:e.text,isFinal:e.final,timestamp:Date.now(),speaker:"user"}));a.postMessage({type:"transcription_update",transcriptions:e})}},[g.segments,a]),t.useDataChannel("function_calls_collected",e=>{if(!a)return;const t=new TextDecoder,n=e.payload instanceof Uint8Array?t.decode(e.payload):String(e.payload);let r;try{r=JSON.parse(n),a.postMessage({type:"function_calls_collected",functionCalls:r})}catch(e){console.error("Failed to parse function call log:",e)}}),t.useDataChannel("function_calls_finished",e=>{if(!a)return;const t=new TextDecoder,n=e.payload instanceof Uint8Array?t.decode(e.payload):String(e.payload);let r;try{r=JSON.parse(n),a.postMessage({type:"function_calls_finished",functionResults:r})}catch(e){console.error("Failed to parse function call result:",e)}}),null}exports.useVoxAI=function(n){void 0===n&&(n={});const[o,i]=r.useState(null),[c,u]=r.useState("disconnected"),[l,p]=r.useState(new Map),[d,f]=r.useState([]),m=r.useRef(""),g=r.useRef(new Set),v=r.useRef(null),y=r.useRef(null),h=r.useRef(null),w=r.useRef(null),[k,C]=r.useState({agent:[],user:[]}),b=r.useRef(null);r.useEffect(()=>{const e=Array.from(l.values()).sort((e,t)=>e.timestamp-t.timestamp),t=JSON.stringify(e);t!==m.current&&(m.current=t,f(e),n.onMessage&&e.filter(e=>e.isFinal&&e.id&&!g.current.has(e.id)).forEach(e=>{e.id&&(g.current.add(e.id),null==n.onMessage||n.onMessage(e))}))},[l,n.onMessage]),r.useEffect(()=>{const e=new MessageChannel;return e.port1.onmessage=e=>{const t=e.data;if("state_update"===t.type)u(t.state);else if("transcription_update"===t.type)_(t.transcriptions);else if("waveform_update"===t.type&&t.speaker)C(e=>({...e,[t.speaker]:t.waveformData}));else if("function_calls_collected"===t.type&&t.functionCalls){const e="function-calls-"+Date.now();p(n=>{const r=new Map(n);return r.set(e,{id:e,name:"tool",toolCalls:t.functionCalls,timestamp:Date.now(),isFinal:!0}),r})}else if("function_calls_finished"===t.type&&t.functionResults){const e="function-results-"+Date.now();p(n=>{const r=new Map(n);return r.set(e,{id:e,name:"tool",toolCallResults:t.functionResults,timestamp:Date.now(),isFinal:!0}),r})}},e.port1.start(),h.current=e,()=>{var e,t;null==(e=h.current)||e.port1.close(),null==(t=h.current)||t.port2.close(),h.current=null}},[]);const _=r.useCallback(e=>{p(t=>{const n=new Map(t);return e.forEach(e=>{var r;const a="agent"===e.speaker?"agent":"user",s=(null==(r=t.get(e.id))?void 0:r.timestamp)||e.timestamp;n.set(e.id,{id:e.id,name:a,message:e.text,timestamp:s,isFinal:e.isFinal})}),n})},[]);r.useEffect(()=>{const e=document.createElement("div");return e.style.display="none",document.body.appendChild(e),v.current=e,y.current=a.createRoot(e),()=>{y.current&&y.current.unmount(),v.current&&document.body.removeChild(v.current)}},[]);const M=r.useCallback(function(e){let{agentId:t,apiKey:r,dynamicVariables:a,metadata:s}=e;try{return Promise.resolve(function(e,o){try{var c=(u("connecting"),Promise.resolve(fetch("https://www.tryvox.co/api/agent/sdk",{method:"POST",headers:{Authorization:"Bearer "+r,"Content-Type":"application/json"},body:JSON.stringify({agent_id:t,metadata:{call_web:{dynamic_variables:a||{},metadata:s||{}}}})})).then(function(e){function t(t){return Promise.resolve(e.json()).then(function(e){i(e),n.onConnect&&n.onConnect()})}const r=function(){if(!e.ok)return Promise.resolve(e.text()).then(function(t){throw new Error("Connection failed ("+e.status+"): "+t)})}();return r&&r.then?r.then(t):t()}))}catch(e){return o(e)}return c&&c.then?c.then(void 0,o):c}(0,function(e){i(null),p(new Map),f([]),u("disconnected");const t=e instanceof Error?e:new Error(String(e));n.onError&&n.onError(t)}))}catch(e){return Promise.reject(e)}},[n]),E=r.useCallback(()=>{i(null),p(new Map),f([]),u("disconnected"),n.onDisconnect&&n.onDisconnect()},[n]),x=r.useCallback(e=>{let{message:t,digit:n}=e;if("disconnected"!==c){if(t){const e="user-text-"+Date.now();p(n=>{const r=new Map(n);return r.set(e,{id:e,name:"user",message:t,timestamp:Date.now(),isFinal:!0}),r}),h.current?h.current.port1.postMessage({type:"send_text",text:t}):console.error("No message channel available to send message")}void 0!==n&&(h.current?h.current.port1.postMessage({type:"send_dtmf",digit:n}):console.error("No message channel available to send DTMF"))}else console.warn("Cannot send message: Not connected to a conversation")},[c]),D=r.useCallback(e=>{let{speaker:t="agent",barCount:n=10,updateInterval:r=20}=e;b.current={speaker:t,barCount:n,updateInterval:r},h.current&&h.current.port1.postMessage({type:"waveform_config",config:{speaker:t,barCount:n,updateInterval:r}});const a=k[t]||[];return a.length>0?a.slice(0,n):Array(n).fill(0)},[k]);return r.useEffect(()=>{y.current&&(o?(w.current||(h.current&&h.current.port2.start(),w.current=e.jsxs(t.LiveKitRoom,{serverUrl:o.serverUrl,token:o.participantToken,audio:!0,video:!1,connect:!0,onDisconnected:E,onError:e=>{console.error("LiveKit connection error:",e),E(),n.onError&&n.onError(new Error("LiveKit connection error: "+e.message))},children:[e.jsx(t.RoomAudioRenderer,{}),h.current&&e.jsx(s,{port:h.current.port2,initialConfig:b.current||{barCount:10,updateInterval:20}})]})),y.current.render(w.current)):(w.current=null,y.current.render(e.jsx(e.Fragment,{}))))},[o,E,n.onError]),{connect:M,disconnect:E,state:c,messages:d,send:x,audioWaveform:D}};
1
+ var e=require("react/jsx-runtime"),t=require("@livekit/components-react"),n=require("@livekit/krisp-noise-filter"),r=require("livekit-client"),o=require("react"),a=require("react-dom/client");function s(e,t){try{var n=e()}catch(e){return t(e)}return n&&n.then?n.then(void 0,t):n}function i(e){let{port:a,initialConfig:i,enableNoiseCancellation:c=!0}=e;const{agent:l,state:u}=t.useVoiceAssistant(),{send:p}=t.useChat(),[d,f]=o.useState({speaker:i.speaker||"agent",barCount:i.barCount,updateInterval:i.updateInterval}),m=t.useParticipantTracks([r.Track.Source.Microphone],null==l?void 0:l.identity)[0],g=t.useTrackTranscription(m),v=t.useAudioWaveform(m,{barCount:"agent"===d.speaker?d.barCount:120,updateInterval:"agent"===d.speaker?d.updateInterval:20}),h=t.useLocalParticipant(),y=t.useTrackTranscription({publication:h.microphoneTrack,source:r.Track.Source.Microphone,participant:h.localParticipant}),b=t.useParticipantTracks([r.Track.Source.Microphone],h.localParticipant.identity)[0],k=t.useAudioWaveform(b,{barCount:"user"===d.speaker?d.barCount:120,updateInterval:"user"===d.speaker?d.updateInterval:20});return o.useEffect(()=>{!function(){try{var e;const t=null==(e=h.microphoneTrack)?void 0:e.track;return Promise.resolve(function(){if(t&&t instanceof r.LocalAudioTrack)return s(function(){const e=c?Promise.resolve(t.setProcessor(n.KrispNoiseFilter())).then(function(){}):Promise.resolve(t.stopProcessor()).then(function(){});if(e&&e.then)return e.then(function(){})},function(e){console.error("Failed to set audio processor:",e)})}())}catch(e){return Promise.reject(e)}}()},[h.microphoneTrack,c]),o.useEffect(()=>{a&&v&&v.bars&&a.postMessage({type:"waveform_update",waveformData:v.bars,speaker:"agent"})},[a,v]),o.useEffect(()=>{a&&k&&k.bars&&a.postMessage({type:"waveform_update",waveformData:k.bars,speaker:"user"})},[a,k]),o.useEffect(()=>{if(!a)return;const e=e=>{const t=e.data;if("waveform_config"===t.type&&t.config)"number"==typeof t.config.barCount&&"number"==typeof t.config.updateInterval&&f(t.config);else if("send_text"===t.type)p?p(t.text):console.error("sendChat function is not available");else if("send_dtmf"===t.type)h.localParticipant?h.localParticipant.publishDtmf(101,t.digit):console.error("Local participant is not available for DTMF");else if("toggle_mic"===t.type&&"boolean"==typeof t.enabled)h.localParticipant?h.localParticipant.setMicrophoneEnabled(t.enabled).catch(e=>{console.error("Failed to toggle microphone:",e)}):console.error("Local participant is not available for mic toggle");else if("set_volume"===t.type&&"number"==typeof t.volume)if(l)try{l.setVolume(t.volume),console.log("Set agent volume to "+t.volume)}catch(e){console.error("Failed to set agent volume:",e)}else console.error("Agent is not available for volume control")};return a.start(),a.addEventListener("message",e),()=>{a.removeEventListener("message",e)}},[a,p,h,l]),o.useEffect(()=>{a&&a.postMessage({type:"state_update",state:u})},[u,a]),o.useEffect(()=>{if(a&&g.segments.length>0){const e=g.segments.map(e=>({id:e.id,text:e.text,isFinal:e.final,timestamp:Date.now(),speaker:"agent"}));a.postMessage({type:"transcription_update",transcriptions:e})}},[g.segments,a]),o.useEffect(()=>{if(a&&y.segments.length>0){const e=y.segments.map(e=>({id:e.id,text:e.text,isFinal:e.final,timestamp:Date.now(),speaker:"user"}));a.postMessage({type:"transcription_update",transcriptions:e})}},[y.segments,a]),t.useDataChannel("function_calls_collected",e=>{if(!a)return;const t=new TextDecoder,n=e.payload instanceof Uint8Array?t.decode(e.payload):String(e.payload);let r;try{r=JSON.parse(n),a.postMessage({type:"function_calls_collected",functionCalls:r})}catch(e){console.error("Failed to parse function call log:",e)}}),t.useDataChannel("function_calls_finished",e=>{if(!a)return;const t=new TextDecoder,n=e.payload instanceof Uint8Array?t.decode(e.payload):String(e.payload);let r;try{r=JSON.parse(n),a.postMessage({type:"function_calls_finished",functionResults:r})}catch(e){console.error("Failed to parse function call result:",e)}}),null}exports.useVoxAI=function(n){void 0===n&&(n={});const[r,c]=o.useState(null),[l,u]=o.useState("disconnected"),[p,d]=o.useState(new Map),[f,m]=o.useState([]),g=o.useRef(""),v=o.useRef(new Set),h=o.useRef(null),y=o.useRef(null),b=o.useRef(null),k=o.useRef(null),[C,w]=o.useState({agent:[],user:[]}),M=o.useRef(null),[_,E]=o.useState(!0);o.useEffect(()=>{const e=Array.from(p.values()).sort((e,t)=>e.timestamp-t.timestamp),t=JSON.stringify(e);t!==g.current&&(g.current=t,m(e),n.onMessage&&e.filter(e=>e.isFinal&&e.id&&!v.current.has(e.id)).forEach(e=>{e.id&&(v.current.add(e.id),null==n.onMessage||n.onMessage(e))}))},[p,n.onMessage]),o.useEffect(()=>{const e=new MessageChannel;return e.port1.onmessage=e=>{const t=e.data;if("state_update"===t.type)u(t.state);else if("transcription_update"===t.type)x(t.transcriptions);else if("waveform_update"===t.type&&t.speaker)w(e=>({...e,[t.speaker]:t.waveformData}));else if("function_calls_collected"===t.type&&t.functionCalls){const e="function-calls-"+Date.now();d(n=>{const r=new Map(n);return r.set(e,{id:e,name:"tool",toolCalls:t.functionCalls,timestamp:Date.now(),isFinal:!0}),r})}else if("function_calls_finished"===t.type&&t.functionResults){const e="function-results-"+Date.now();d(n=>{const r=new Map(n);return r.set(e,{id:e,name:"tool",toolCallResults:t.functionResults,timestamp:Date.now(),isFinal:!0}),r})}},e.port1.start(),b.current=e,()=>{var e,t;null==(e=b.current)||e.port1.close(),null==(t=b.current)||t.port2.close(),b.current=null}},[]);const x=o.useCallback(e=>{d(t=>{const n=new Map(t);return e.forEach(e=>{var r;const o="agent"===e.speaker?"agent":"user",a=(null==(r=t.get(e.id))?void 0:r.timestamp)||e.timestamp;n.set(e.id,{id:e.id,name:o,message:e.text,timestamp:a,isFinal:e.isFinal})}),n})},[]);o.useEffect(()=>{const e=document.createElement("div");return e.style.display="none",document.body.appendChild(e),h.current=e,y.current=a.createRoot(e),()=>{y.current&&y.current.unmount(),h.current&&document.body.removeChild(h.current)}},[]);const P=o.useCallback(function(e){let{agentId:t,apiKey:r,dynamicVariables:o,metadata:a}=e;try{return Promise.resolve(s(function(){if("disconnected"!==l){const e="Connection attempt rejected: Already in a connection state ("+l+")";return console.warn(e),n.onError&&n.onError(new Error(e)),Promise.reject(new Error(e))}return u("connecting"),Promise.resolve(fetch("https://www.tryvox.co/api/agent/sdk",{method:"POST",headers:{Authorization:"Bearer "+r,"Content-Type":"application/json"},body:JSON.stringify({agent_id:t,metadata:{call_web:{dynamic_variables:o||{},metadata:a||{}}}})})).then(function(e){function t(t){return Promise.resolve(e.json()).then(function(e){c(e),n.onConnect&&n.onConnect()})}const r=function(){if(!e.ok)return Promise.resolve(e.text()).then(function(t){throw new Error("Connection failed ("+e.status+"): "+t)})}();return r&&r.then?r.then(t):t()})},function(e){c(null),d(new Map),m([]),u("disconnected");const t=e instanceof Error?e:new Error(String(e));n.onError&&n.onError(t)}))}catch(e){return Promise.reject(e)}},[n]),D=o.useCallback(()=>{c(null),d(new Map),m([]),u("disconnected"),n.onDisconnect&&n.onDisconnect()},[n]),S=o.useCallback(e=>{let{message:t,digit:n}=e;if("disconnected"!==l){if(t){const e="user-text-"+Date.now();d(n=>{const r=new Map(n);return r.set(e,{id:e,name:"user",message:t,timestamp:Date.now(),isFinal:!0}),r}),b.current?b.current.port1.postMessage({type:"send_text",text:t}):console.error("No message channel available to send message")}void 0!==n&&(b.current?b.current.port1.postMessage({type:"send_dtmf",digit:n}):console.error("No message channel available to send DTMF"))}else console.warn("Cannot send message: Not connected to a conversation")},[l]),T=o.useCallback(e=>{let{speaker:t="agent",barCount:n=10,updateInterval:r=20}=e;M.current={speaker:t,barCount:n,updateInterval:r},b.current&&b.current.port1.postMessage({type:"waveform_config",config:{speaker:t,barCount:n,updateInterval:r}});const o=C[t]||[];return o.length>0?o.slice(0,n):Array(n).fill(0)},[C]),F=o.useCallback(e=>{E(e),b.current?b.current.port1.postMessage({type:"toggle_mic",enabled:e}):console.error("No message channel available to toggle microphone")},[n]),R=o.useCallback(e=>{const t=Math.min(Math.max(e,0),1);b.current?b.current.port1.postMessage({type:"set_volume",volume:t}):console.error("No message channel available to set volume")},[]);return o.useEffect(()=>{if(y.current)if(r){var o;k.current||(b.current&&b.current.port2.start(),k.current=e.jsxs(t.LiveKitRoom,{serverUrl:r.serverUrl,token:r.participantToken,audio:!0,video:!1,connect:!0,onDisconnected:D,onError:e=>{console.error("LiveKit connection error:",e),D(),n.onError&&n.onError(new Error("LiveKit connection error: "+e.message))},children:[e.jsx(t.RoomAudioRenderer,{}),b.current&&e.jsx(i,{port:b.current.port2,initialConfig:M.current||{barCount:10,updateInterval:20},enableNoiseCancellation:null==(o=n.enableNoiseCancellation)||o})]})),y.current.render(k.current)}else k.current=null,y.current.render(e.jsx(e.Fragment,{}))},[r,D,n.onError]),{connect:P,disconnect:D,state:l,messages:f,send:S,audioWaveform:T,toggleMic:F,setVolume:R}};
2
2
  //# sourceMappingURL=lib.cjs.map
package/dist/lib.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"lib.cjs","sources":["../src/hooks/useVoxAI.tsx"],"sourcesContent":["import {\n LiveKitRoom,\n RoomAudioRenderer,\n useLocalParticipant,\n useParticipantTracks,\n useTrackTranscription,\n useVoiceAssistant,\n useChat,\n useAudioWaveform,\n useDataChannel,\n} from \"@livekit/components-react\";\nimport { Track } from \"livekit-client\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { createRoot, Root } from \"react-dom/client\";\n\ntype VoxConnectionDetail = {\n serverUrl: string;\n roomName: string;\n participantName: string;\n participantToken: string;\n};\n\n/**\n * VoxAgentState\n * @description The state of the agent\n */\nexport type VoxAgentState =\n | \"disconnected\"\n | \"connecting\"\n | \"initializing\"\n | \"listening\"\n | \"thinking\"\n | \"speaking\";\n\n// API endpoint\nconst HTTPS_API_ORIGIN = \"https://www.tryvox.co/api/agent/sdk\";\n// const HTTPS_API_ORIGIN = \"http://localhost:3000/api/agent/sdk\";\n\n/**\n * Function call related types\n */\nexport interface FunctionCallInfo {\n tool_call_id: string;\n function_info: {\n name: string;\n description: string;\n arguments: Record<string, any>;\n };\n raw_arguments: string;\n arguments: Record<string, any>;\n}\n\nexport interface FunctionCallResult {\n call_info: {\n tool_call_id: string;\n function_info: {\n name: string;\n description: string;\n arguments: Record<string, any>;\n };\n raw_arguments: string;\n arguments: Record<string, any>;\n };\n result: Record<string, any> | null;\n exception: string | null;\n}\n\n/**\n * VoxMessage\n * @description The message type between the agent and the user\n */\nexport type VoxMessage = {\n id?: string;\n name: \"agent\" | \"user\" | \"tool\";\n message?: string;\n timestamp: number;\n isFinal?: boolean;\n toolCalls?: FunctionCallInfo[];\n toolCallResults?: FunctionCallResult[];\n};\n\n/**\n * VoxAIOptions\n * @description The callback functions for the useVoxAI hook\n */\nexport interface VoxAIOptions {\n onConnect?: () => void;\n onDisconnect?: () => void;\n onError?: (error: Error) => void;\n onMessage?: (message: VoxMessage) => void;\n}\n\n// Message channel event types\ntype MessageChannelEvent =\n | { type: \"state_update\"; state: VoxAgentState }\n | { type: \"transcription_update\"; transcriptions: TranscriptionSegment[] }\n | {\n type: \"waveform_update\";\n waveformData: number[];\n speaker: \"agent\" | \"user\";\n }\n | { type: \"function_calls_collected\"; functionCalls: FunctionCallInfo[] }\n | { type: \"function_calls_finished\"; functionResults: FunctionCallResult[] };\n\ntype TranscriptionSegment = {\n id: string;\n text: string;\n isFinal: boolean;\n timestamp: number;\n speaker: \"agent\" | \"user\";\n};\n\n/**\n * ConnectParams\n * @param agentId - The agent ID\n * @param apiKey - The API key\n * @param dynamicVariables - The dynamic variables\n * @param metadata - 이 메타데이터는 아웃바운드 웹훅, 통화 기록에 포함됩니다.\n */\nexport interface ConnectParams {\n agentId: string;\n apiKey: string;\n dynamicVariables?: Record<string, any>;\n metadata?: Record<string, any>;\n}\n\n/**\n * useVoxAI\n * @description The hook for integrating with vox.ai voice assistant\n * @param options - The options for the useVoxAI hook\n * @returns The useVoxAI hook\n * @example\n * const { connect, disconnect, state, messages, send } = useVoxAI({\n * onConnect: () => console.log(\"Connected\"),\n * onDisconnect: () => console.log(\"Disconnected\"),\n * onError: (error) => console.error(\"Error:\", error),\n * onMessage: (message) => console.log(\"Message:\", message),\n * });\n */\nexport function useVoxAI(options: VoxAIOptions = {}) {\n // Connection state\n const [connectionDetail, setConnectionDetail] =\n useState<VoxConnectionDetail | null>(null);\n const [state, setState] = useState<VoxAgentState>(\"disconnected\");\n\n // Message handling\n const [transcriptMap, setTranscriptMap] = useState<Map<string, VoxMessage>>(\n new Map()\n );\n const [messages, setMessages] = useState<VoxMessage[]>([]);\n const prevMessagesRef = useRef<string>(\"\");\n\n // Track which messages we've already sent to the onMessage callback\n const processedMessageIdsRef = useRef<Set<string>>(new Set());\n\n // DOM manipulation for LiveKit portal\n const portalRootRef = useRef<HTMLDivElement | null>(null);\n const rootRef = useRef<Root | null>(null);\n\n // Communication channel\n const channelRef = useRef<MessageChannel | null>(null);\n\n // Add this near the start of your useVoxAI hook\n const livekitComponentRef = useRef<React.ReactNode>(null);\n\n // Replace the single waveform state with a map for multiple speakers\n const [waveformDataMap, setWaveformDataMap] = useState<\n Record<string, number[]>\n >({\n agent: [],\n user: [],\n });\n\n // Add back the waveform config reference\n const waveformConfigRef = useRef<{\n speaker?: \"agent\" | \"user\";\n barCount: number;\n updateInterval: number;\n } | null>(null);\n\n // Update messages whenever transcriptMap changes\n useEffect(() => {\n const allMessages = Array.from(transcriptMap.values()).sort(\n (a, b) => a.timestamp - b.timestamp\n );\n\n // Only update if the messages have actually changed\n const messagesString = JSON.stringify(allMessages);\n if (messagesString !== prevMessagesRef.current) {\n prevMessagesRef.current = messagesString;\n setMessages(allMessages);\n\n // Only trigger onMessage for new final messages that haven't been processed yet\n if (options.onMessage) {\n allMessages\n .filter(\n (msg) =>\n msg.isFinal &&\n msg.id &&\n !processedMessageIdsRef.current.has(msg.id)\n )\n .forEach((msg) => {\n if (msg.id) {\n // Mark this message as processed\n processedMessageIdsRef.current.add(msg.id);\n // Call the callback\n options.onMessage?.(msg);\n }\n });\n }\n }\n }, [transcriptMap, options.onMessage]);\n\n // Initialize message channel - ensure ports are properly connected\n useEffect(() => {\n const channel = new MessageChannel();\n\n channel.port1.onmessage = (e) => {\n const data = e.data as MessageChannelEvent;\n\n if (data.type === \"state_update\") {\n setState(data.state);\n } else if (data.type === \"transcription_update\") {\n handleTranscriptionUpdate(data.transcriptions);\n } else if (data.type === \"waveform_update\" && data.speaker) {\n // Store the waveform data for the specific speaker\n setWaveformDataMap((prevMap) => ({\n ...prevMap,\n [data.speaker]: data.waveformData,\n }));\n } else if (\n data.type === \"function_calls_collected\" &&\n data.functionCalls\n ) {\n // Handle function calls\n const functionCallsId = `function-calls-${Date.now()}`;\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n newMap.set(functionCallsId, {\n id: functionCallsId,\n name: \"tool\",\n toolCalls: data.functionCalls,\n timestamp: Date.now(),\n isFinal: true,\n });\n return newMap;\n });\n } else if (\n data.type === \"function_calls_finished\" &&\n data.functionResults\n ) {\n // Handle function results\n const functionResultsId = `function-results-${Date.now()}`;\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n newMap.set(functionResultsId, {\n id: functionResultsId,\n name: \"tool\",\n toolCallResults: data.functionResults,\n timestamp: Date.now(),\n isFinal: true,\n });\n return newMap;\n });\n }\n };\n\n // Start the port\n channel.port1.start();\n\n // Store the channel reference\n channelRef.current = channel;\n\n return () => {\n channelRef.current?.port1.close();\n channelRef.current?.port2.close();\n channelRef.current = null;\n };\n }, []);\n\n // Process incoming transcriptions\n const handleTranscriptionUpdate = useCallback(\n (transcriptions: TranscriptionSegment[]) => {\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n\n transcriptions.forEach((t) => {\n const messageType = t.speaker === \"agent\" ? \"agent\" : \"user\";\n // Use existing timestamp if we already have this segment\n const existingTimestamp = prevMap.get(t.id)?.timestamp || t.timestamp;\n\n newMap.set(t.id, {\n id: t.id,\n name: messageType,\n message: t.text,\n timestamp: existingTimestamp,\n isFinal: t.isFinal,\n });\n });\n\n return newMap;\n });\n },\n []\n );\n\n // Set up DOM portal for LiveKit\n useEffect(() => {\n const div = document.createElement(\"div\");\n div.style.display = \"none\";\n document.body.appendChild(div);\n portalRootRef.current = div;\n rootRef.current = createRoot(div);\n\n return () => {\n if (rootRef.current) {\n rootRef.current.unmount();\n }\n if (portalRootRef.current) {\n document.body.removeChild(portalRootRef.current);\n }\n };\n }, []);\n\n // Connect to VoxAI service - updated to include dynamicVariables\n const connect = useCallback(\n async ({ agentId, apiKey, dynamicVariables, metadata }: ConnectParams) => {\n try {\n setState(\"connecting\");\n\n const response = await fetch(HTTPS_API_ORIGIN, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n agent_id: agentId,\n metadata: {\n call_web: {\n dynamic_variables: dynamicVariables || {},\n metadata: metadata || {},\n },\n },\n }),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `Connection failed (${response.status}): ${errorText}`\n );\n }\n\n const data = await response.json();\n setConnectionDetail(data);\n\n if (options.onConnect) {\n options.onConnect();\n }\n } catch (err) {\n // Reset state on error\n setConnectionDetail(null);\n setTranscriptMap(new Map());\n setMessages([]);\n setState(\"disconnected\");\n\n const error = err instanceof Error ? err : new Error(String(err));\n\n if (options.onError) {\n options.onError(error);\n }\n }\n },\n [options]\n );\n\n // Disconnect from VoxAI service\n const disconnect = useCallback(() => {\n setConnectionDetail(null);\n setTranscriptMap(new Map());\n setMessages([]);\n setState(\"disconnected\");\n\n if (options.onDisconnect) {\n options.onDisconnect();\n }\n }, [options]);\n\n // Update the send function with debugging and error checking\n const send = useCallback(\n ({ message, digit }: { message?: string; digit?: number }) => {\n if (state === \"disconnected\") {\n console.warn(\"Cannot send message: Not connected to a conversation\");\n return;\n }\n\n if (message) {\n // Add the message to our local transcript map for immediate feedback\n const messageId = `user-text-${Date.now()}`;\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n newMap.set(messageId, {\n id: messageId,\n name: \"user\",\n message: message,\n timestamp: Date.now(),\n isFinal: true,\n });\n return newMap;\n });\n\n // Send message through the message channel to StateMonitor\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"send_text\",\n text: message,\n });\n } else {\n console.error(\"No message channel available to send message\");\n }\n }\n\n if (digit !== undefined) {\n // Send DTMF through the message channel to StateMonitor\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"send_dtmf\",\n digit: digit,\n });\n } else {\n console.error(\"No message channel available to send DTMF\");\n }\n }\n },\n [state]\n );\n\n // Update the audioWaveform function to return data for the requested speaker\n const audioWaveform = useCallback(\n ({\n speaker = \"agent\",\n barCount = 10,\n updateInterval = 20,\n }: {\n speaker?: \"agent\" | \"user\";\n barCount?: number;\n updateInterval?: number;\n }): number[] => {\n // Store the waveform configuration for StateMonitor to use\n waveformConfigRef.current = { speaker, barCount, updateInterval };\n\n // Send the configuration to StateMonitor if channel is available\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"waveform_config\",\n config: { speaker, barCount, updateInterval },\n });\n }\n\n // Get the waveform data for the requested speaker\n const speakerData = waveformDataMap[speaker] || [];\n\n // Return the current waveform data, or a default array if no data yet\n return speakerData.length > 0\n ? speakerData.slice(0, barCount) // Ensure we return only barCount items\n : Array(barCount).fill(0);\n },\n [waveformDataMap]\n );\n\n // Modify the useEffect hook that renders the LiveKit component\n useEffect(() => {\n if (!rootRef.current) return;\n\n if (connectionDetail) {\n // Only create a new LiveKit component if we don't have one or connection details changed\n if (!livekitComponentRef.current) {\n if (channelRef.current) {\n // Start port2 before passing it to StateMonitor\n channelRef.current.port2.start();\n }\n\n livekitComponentRef.current = (\n <LiveKitRoom\n serverUrl={connectionDetail.serverUrl}\n token={connectionDetail.participantToken}\n audio={true}\n video={false}\n connect={true}\n onDisconnected={disconnect}\n onError={(error) => {\n console.error(\"LiveKit connection error:\", error);\n disconnect();\n if (options.onError) {\n options.onError(\n new Error(`LiveKit connection error: ${error.message}`)\n );\n }\n }}\n >\n <RoomAudioRenderer />\n {channelRef.current && (\n <StateMonitor\n port={channelRef.current.port2}\n initialConfig={\n waveformConfigRef.current || {\n barCount: 10,\n updateInterval: 20,\n }\n }\n />\n )}\n </LiveKitRoom>\n );\n }\n\n rootRef.current.render(livekitComponentRef.current);\n } else {\n livekitComponentRef.current = null;\n rootRef.current.render(<></>);\n }\n }, [connectionDetail, disconnect, options.onError]);\n\n return {\n connect,\n disconnect,\n state,\n messages,\n send,\n audioWaveform,\n };\n}\n\n/**\n * Component that monitors LiveKit state and communicates back to the main hook\n */\nfunction StateMonitor({\n port,\n initialConfig,\n}: {\n port: MessagePort | undefined;\n initialConfig: {\n speaker?: \"agent\" | \"user\";\n barCount: number;\n updateInterval: number;\n };\n}) {\n const { agent, state } = useVoiceAssistant();\n const { send: sendChat } = useChat();\n\n // Initialize waveform config with the passed initial values, defaulting to \"agent\" if not specified\n const [waveformConfig, setWaveformConfig] = useState({\n speaker: initialConfig.speaker || \"agent\",\n barCount: initialConfig.barCount,\n updateInterval: initialConfig.updateInterval,\n });\n\n // Agent transcriptions\n const agentAudioTrack = useParticipantTracks(\n [Track.Source.Microphone],\n agent?.identity\n )[0];\n const agentTranscription = useTrackTranscription(agentAudioTrack);\n\n // Use the current config for the waveform, applying different settings based on speaker\n const agentAudioWaveform = useAudioWaveform(agentAudioTrack, {\n barCount:\n waveformConfig.speaker === \"agent\" ? waveformConfig.barCount : 120, // default if not the selected speaker\n updateInterval:\n waveformConfig.speaker === \"agent\" ? waveformConfig.updateInterval : 20,\n });\n\n // User transcriptions\n const localParticipant = useLocalParticipant();\n const localMessages = useTrackTranscription({\n publication: localParticipant.microphoneTrack,\n source: Track.Source.Microphone,\n participant: localParticipant.localParticipant,\n });\n const localAudioTrack = useParticipantTracks(\n [Track.Source.Microphone],\n localParticipant.localParticipant.identity\n )[0];\n const userAudioWaveform = useAudioWaveform(localAudioTrack, {\n barCount: waveformConfig.speaker === \"user\" ? waveformConfig.barCount : 120, // default if not the selected speaker\n updateInterval:\n waveformConfig.speaker === \"user\" ? waveformConfig.updateInterval : 20,\n });\n\n // Add separate effects to send agent and user waveform data\n useEffect(() => {\n if (!port || !agentAudioWaveform || !agentAudioWaveform.bars) return;\n\n // Send the agent waveform data\n port.postMessage({\n type: \"waveform_update\",\n waveformData: agentAudioWaveform.bars,\n speaker: \"agent\",\n });\n }, [port, agentAudioWaveform]);\n\n useEffect(() => {\n if (!port || !userAudioWaveform || !userAudioWaveform.bars) return;\n\n // Send the user waveform data\n port.postMessage({\n type: \"waveform_update\",\n waveformData: userAudioWaveform.bars,\n speaker: \"user\",\n });\n }, [port, userAudioWaveform]);\n\n // Listen for messages including config updates\n useEffect(() => {\n if (!port) return;\n\n const handleMessage = (event: MessageEvent) => {\n const data = event.data;\n\n if (data.type === \"waveform_config\" && data.config) {\n // Verify we have both required properties before updating\n if (\n typeof data.config.barCount === \"number\" &&\n typeof data.config.updateInterval === \"number\"\n ) {\n setWaveformConfig(data.config);\n }\n } else if (data.type === \"send_text\") {\n if (sendChat) {\n sendChat(data.text);\n } else {\n console.error(\"sendChat function is not available\");\n }\n } else if (data.type === \"send_dtmf\") {\n if (localParticipant.localParticipant) {\n // Use standard DTMF code (RFC 4733)\n const standardDtmfCode = 101; // Standard DTMF payload type\n localParticipant.localParticipant.publishDtmf(\n standardDtmfCode,\n data.digit\n );\n } else {\n console.error(\"Local participant is not available for DTMF\");\n }\n }\n };\n\n // Make sure we start the port\n port.start();\n\n port.addEventListener(\"message\", handleMessage);\n\n return () => {\n port.removeEventListener(\"message\", handleMessage);\n };\n }, [port, sendChat, localParticipant]);\n\n // Send agent state updates\n useEffect(() => {\n if (port) {\n port.postMessage({ type: \"state_update\", state });\n }\n }, [state, port]);\n\n // Send agent transcriptions\n useEffect(() => {\n if (port && agentTranscription.segments.length > 0) {\n const transcriptions = agentTranscription.segments.map((segment) => ({\n id: segment.id,\n text: segment.text,\n isFinal: segment.final,\n timestamp: Date.now(),\n speaker: \"agent\" as const,\n }));\n\n port.postMessage({\n type: \"transcription_update\",\n transcriptions,\n });\n }\n }, [agentTranscription.segments, port]);\n\n // Send user transcriptions\n useEffect(() => {\n if (port && localMessages.segments.length > 0) {\n const transcriptions = localMessages.segments.map((segment) => ({\n id: segment.id,\n text: segment.text,\n isFinal: segment.final,\n timestamp: Date.now(),\n speaker: \"user\" as const,\n }));\n\n port.postMessage({\n type: \"transcription_update\",\n transcriptions,\n });\n }\n }, [localMessages.segments, port]);\n\n // Add data channel hook for function calls\n const { message: functionCallsCollected } = useDataChannel(\n \"function_calls_collected\",\n (msg) => {\n if (!port) return;\n\n const textDecoder = new TextDecoder();\n const messageString =\n msg.payload instanceof Uint8Array\n ? textDecoder.decode(msg.payload)\n : String(msg.payload);\n\n let functionCallInfo: FunctionCallInfo[];\n try {\n functionCallInfo = JSON.parse(messageString);\n\n // Send function calls to main hook via the port\n port.postMessage({\n type: \"function_calls_collected\",\n functionCalls: functionCallInfo,\n });\n } catch (e) {\n console.error(\"Failed to parse function call log:\", e);\n }\n }\n );\n\n // Add data channel hook for function call results\n const { message: functionCallsFinished } = useDataChannel(\n \"function_calls_finished\",\n (msg) => {\n if (!port) return;\n\n const textDecoder = new TextDecoder();\n const messageString =\n msg.payload instanceof Uint8Array\n ? textDecoder.decode(msg.payload)\n : String(msg.payload);\n\n let functionCallResult: FunctionCallResult[];\n try {\n functionCallResult = JSON.parse(messageString);\n\n // Send function results to main hook via the port\n port.postMessage({\n type: \"function_calls_finished\",\n functionResults: functionCallResult,\n });\n } catch (e) {\n console.error(\"Failed to parse function call result:\", e);\n }\n }\n );\n\n return null;\n}\n"],"names":["StateMonitor","_ref4","port","initialConfig","agent","state","useVoiceAssistant","send","sendChat","useChat","waveformConfig","setWaveformConfig","useState","speaker","barCount","updateInterval","agentAudioTrack","useParticipantTracks","Track","Source","Microphone","identity","agentTranscription","useTrackTranscription","agentAudioWaveform","useAudioWaveform","localParticipant","useLocalParticipant","localMessages","publication","microphoneTrack","source","participant","localAudioTrack","userAudioWaveform","useEffect","bars","postMessage","type","waveformData","handleMessage","event","data","config","text","console","error","publishDtmf","digit","start","addEventListener","removeEventListener","segments","length","transcriptions","map","segment","id","isFinal","final","timestamp","Date","now","useDataChannel","msg","textDecoder","TextDecoder","messageString","payload","Uint8Array","decode","String","functionCallInfo","JSON","parse","functionCalls","e","functionCallResult","functionResults","options","connectionDetail","setConnectionDetail","setState","transcriptMap","setTranscriptMap","Map","messages","setMessages","prevMessagesRef","useRef","processedMessageIdsRef","Set","portalRootRef","rootRef","channelRef","livekitComponentRef","waveformDataMap","setWaveformDataMap","user","waveformConfigRef","allMessages","Array","from","values","sort","a","b","messagesString","stringify","current","onMessage","filter","has","forEach","add","channel","MessageChannel","port1","onmessage","handleTranscriptionUpdate","prevMap","functionCallsId","newMap","set","name","toolCalls","functionResultsId","toolCallResults","_channelRef$current","_channelRef$current2","close","port2","useCallback","t","_prevMap$get","messageType","existingTimestamp","get","message","div","document","createElement","style","display","body","appendChild","createRoot","unmount","removeChild","connect","_ref","agentId","apiKey","dynamicVariables","metadata","Promise","resolve","fetch","method","headers","Authorization","agent_id","call_web","dynamic_variables","then","response","_temp2","_result2","json","onConnect","_temp","ok","errorText","Error","status","_catch","err","onError","reject","disconnect","onDisconnect","_ref2","messageId","undefined","warn","audioWaveform","_ref3","speakerData","slice","fill","_jsxs","jsxs","LiveKitRoom","serverUrl","token","participantToken","audio","video","onDisconnected","children","_jsx","jsx","RoomAudioRenderer","render","_Fragment"],"mappings":"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"}
1
+ {"version":3,"file":"lib.cjs","sources":["../src/hooks/useVoxAI.tsx"],"sourcesContent":["import {\n LiveKitRoom,\n RoomAudioRenderer,\n useLocalParticipant,\n useParticipantTracks,\n useTrackTranscription,\n useVoiceAssistant,\n useChat,\n useAudioWaveform,\n useDataChannel,\n useTrackVolume,\n} from \"@livekit/components-react\";\nimport { KrispNoiseFilter } from \"@livekit/krisp-noise-filter\";\nimport { LocalAudioTrack, Track } from \"livekit-client\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { createRoot, Root } from \"react-dom/client\";\n\ntype VoxConnectionDetail = {\n serverUrl: string;\n roomName: string;\n participantName: string;\n participantToken: string;\n};\n\n/**\n * VoxAgentState\n * @description The state of the agent\n */\nexport type VoxAgentState =\n | \"disconnected\"\n | \"connecting\"\n | \"initializing\"\n | \"listening\"\n | \"thinking\"\n | \"speaking\";\n\n// API endpoint\nconst HTTPS_API_ORIGIN = \"https://www.tryvox.co/api/agent/sdk\";\n// const HTTPS_API_ORIGIN = \"http://localhost:3000/api/agent/sdk\";\n\n/**\n * Function call related types\n */\nexport interface FunctionCallInfo {\n tool_call_id: string;\n function_info: {\n name: string;\n description: string;\n arguments: Record<string, any>;\n };\n raw_arguments: string;\n arguments: Record<string, any>;\n}\n\nexport interface FunctionCallResult {\n call_info: {\n tool_call_id: string;\n function_info: {\n name: string;\n description: string;\n arguments: Record<string, any>;\n };\n raw_arguments: string;\n arguments: Record<string, any>;\n };\n result: Record<string, any> | null;\n exception: string | null;\n}\n\n/**\n * VoxMessage\n * @description The message type between the agent and the user\n */\nexport type VoxMessage = {\n id?: string;\n name: \"agent\" | \"user\" | \"tool\";\n message?: string;\n timestamp: number;\n isFinal?: boolean;\n toolCalls?: FunctionCallInfo[];\n toolCallResults?: FunctionCallResult[];\n};\n\n/**\n * VoxAIOptions\n * @description The callback functions for the useVoxAI hook\n */\nexport interface VoxAIOptions {\n onConnect?: () => void;\n onDisconnect?: () => void;\n onError?: (error: Error) => void;\n onMessage?: (message: VoxMessage) => void;\n enableNoiseCancellation?: boolean;\n}\n\n// Message channel event types\ntype MessageChannelEvent =\n | { type: \"state_update\"; state: VoxAgentState }\n | { type: \"transcription_update\"; transcriptions: TranscriptionSegment[] }\n | {\n type: \"waveform_update\";\n waveformData: number[];\n speaker: \"agent\" | \"user\";\n }\n | { type: \"function_calls_collected\"; functionCalls: FunctionCallInfo[] }\n | { type: \"function_calls_finished\"; functionResults: FunctionCallResult[] };\n\ntype TranscriptionSegment = {\n id: string;\n text: string;\n isFinal: boolean;\n timestamp: number;\n speaker: \"agent\" | \"user\";\n};\n\n/**\n * ConnectParams\n * @param agentId - The agent ID\n * @param apiKey - The API key\n * @param dynamicVariables - The dynamic variables\n * @param metadata - 이 메타데이터는 아웃바운드 웹훅, 통화 기록에 포함됩니다.\n */\nexport interface ConnectParams {\n agentId: string;\n apiKey: string;\n dynamicVariables?: Record<string, any>;\n metadata?: Record<string, any>;\n}\n\n/**\n * useVoxAI\n * @description The hook for integrating with vox.ai voice assistant\n * @param options - The options for the useVoxAI hook\n * @returns The useVoxAI hook\n * @example\n * const { connect, disconnect, state, messages, send } = useVoxAI({\n * onConnect: () => console.log(\"Connected\"),\n * onDisconnect: () => console.log(\"Disconnected\"),\n * onError: (error) => console.error(\"Error:\", error),\n * onMessage: (message) => console.log(\"Message:\", message),\n * });\n */\nexport function useVoxAI(options: VoxAIOptions = {}) {\n // Connection state\n const [connectionDetail, setConnectionDetail] =\n useState<VoxConnectionDetail | null>(null);\n const [state, setState] = useState<VoxAgentState>(\"disconnected\");\n\n // Message handling\n const [transcriptMap, setTranscriptMap] = useState<Map<string, VoxMessage>>(\n new Map()\n );\n const [messages, setMessages] = useState<VoxMessage[]>([]);\n const prevMessagesRef = useRef<string>(\"\");\n\n // Track which messages we've already sent to the onMessage callback\n const processedMessageIdsRef = useRef<Set<string>>(new Set());\n\n // DOM manipulation for LiveKit portal\n const portalRootRef = useRef<HTMLDivElement | null>(null);\n const rootRef = useRef<Root | null>(null);\n\n // Communication channel\n const channelRef = useRef<MessageChannel | null>(null);\n\n // Add this near the start of your useVoxAI hook\n const livekitComponentRef = useRef<React.ReactNode>(null);\n\n // Replace the single waveform state with a map for multiple speakers\n const [waveformDataMap, setWaveformDataMap] = useState<\n Record<string, number[]>\n >({\n agent: [],\n user: [],\n });\n\n // Add back the waveform config reference\n const waveformConfigRef = useRef<{\n speaker?: \"agent\" | \"user\";\n barCount: number;\n updateInterval: number;\n } | null>(null);\n\n // Add a new state to track microphone status\n const [isMicEnabled, setIsMicEnabled] = useState<boolean>(true);\n\n // Update messages whenever transcriptMap changes\n useEffect(() => {\n const allMessages = Array.from(transcriptMap.values()).sort(\n (a, b) => a.timestamp - b.timestamp\n );\n\n // Only update if the messages have actually changed\n const messagesString = JSON.stringify(allMessages);\n if (messagesString !== prevMessagesRef.current) {\n prevMessagesRef.current = messagesString;\n setMessages(allMessages);\n\n // Only trigger onMessage for new final messages that haven't been processed yet\n if (options.onMessage) {\n allMessages\n .filter(\n (msg) =>\n msg.isFinal &&\n msg.id &&\n !processedMessageIdsRef.current.has(msg.id)\n )\n .forEach((msg) => {\n if (msg.id) {\n // Mark this message as processed\n processedMessageIdsRef.current.add(msg.id);\n // Call the callback\n options.onMessage?.(msg);\n }\n });\n }\n }\n }, [transcriptMap, options.onMessage]);\n\n // Initialize message channel - ensure ports are properly connected\n useEffect(() => {\n const channel = new MessageChannel();\n\n channel.port1.onmessage = (e) => {\n const data = e.data as MessageChannelEvent;\n\n if (data.type === \"state_update\") {\n setState(data.state);\n } else if (data.type === \"transcription_update\") {\n handleTranscriptionUpdate(data.transcriptions);\n } else if (data.type === \"waveform_update\" && data.speaker) {\n // Store the waveform data for the specific speaker\n setWaveformDataMap((prevMap) => ({\n ...prevMap,\n [data.speaker]: data.waveformData,\n }));\n } else if (\n data.type === \"function_calls_collected\" &&\n data.functionCalls\n ) {\n // Handle function calls\n const functionCallsId = `function-calls-${Date.now()}`;\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n newMap.set(functionCallsId, {\n id: functionCallsId,\n name: \"tool\",\n toolCalls: data.functionCalls,\n timestamp: Date.now(),\n isFinal: true,\n });\n return newMap;\n });\n } else if (\n data.type === \"function_calls_finished\" &&\n data.functionResults\n ) {\n // Handle function results\n const functionResultsId = `function-results-${Date.now()}`;\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n newMap.set(functionResultsId, {\n id: functionResultsId,\n name: \"tool\",\n toolCallResults: data.functionResults,\n timestamp: Date.now(),\n isFinal: true,\n });\n return newMap;\n });\n }\n };\n\n // Start the port\n channel.port1.start();\n\n // Store the channel reference\n channelRef.current = channel;\n\n return () => {\n channelRef.current?.port1.close();\n channelRef.current?.port2.close();\n channelRef.current = null;\n };\n }, []);\n\n // Process incoming transcriptions\n const handleTranscriptionUpdate = useCallback(\n (transcriptions: TranscriptionSegment[]) => {\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n\n transcriptions.forEach((t) => {\n const messageType = t.speaker === \"agent\" ? \"agent\" : \"user\";\n // Use existing timestamp if we already have this segment\n const existingTimestamp = prevMap.get(t.id)?.timestamp || t.timestamp;\n\n newMap.set(t.id, {\n id: t.id,\n name: messageType,\n message: t.text,\n timestamp: existingTimestamp,\n isFinal: t.isFinal,\n });\n });\n\n return newMap;\n });\n },\n []\n );\n\n // Set up DOM portal for LiveKit\n useEffect(() => {\n const div = document.createElement(\"div\");\n div.style.display = \"none\";\n document.body.appendChild(div);\n portalRootRef.current = div;\n rootRef.current = createRoot(div);\n\n return () => {\n if (rootRef.current) {\n rootRef.current.unmount();\n }\n if (portalRootRef.current) {\n document.body.removeChild(portalRootRef.current);\n }\n };\n }, []);\n\n // Connect to VoxAI service - updated to include dynamicVariables\n /**\n * Initiates a connection to the VoxAI service\n * @param {ConnectParams} options - Connection parameters\n * @returns {Promise} - Resolves when the connection is successful, rejects if:\n * 1. The connection is already in progress (state is not \"disconnected\")\n * 2. The server returns an error\n * 3. Any other error occurs during the connection process\n */\n const connect = useCallback(\n async ({ agentId, apiKey, dynamicVariables, metadata }: ConnectParams) => {\n try {\n // Prevent connecting if already in a connection state\n if (state !== \"disconnected\") {\n const errorMessage = `Connection attempt rejected: Already in a connection state (${state})`;\n console.warn(errorMessage);\n\n // Call the onError callback if provided\n if (options.onError) {\n options.onError(new Error(errorMessage));\n }\n\n // Return a rejected promise\n return Promise.reject(new Error(errorMessage));\n }\n\n setState(\"connecting\");\n\n const response = await fetch(HTTPS_API_ORIGIN, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n agent_id: agentId,\n metadata: {\n call_web: {\n dynamic_variables: dynamicVariables || {},\n metadata: metadata || {},\n },\n },\n }),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `Connection failed (${response.status}): ${errorText}`\n );\n }\n\n const data = await response.json();\n setConnectionDetail(data);\n\n if (options.onConnect) {\n options.onConnect();\n }\n } catch (err) {\n // Reset state on error\n setConnectionDetail(null);\n setTranscriptMap(new Map());\n setMessages([]);\n setState(\"disconnected\");\n\n const error = err instanceof Error ? err : new Error(String(err));\n\n if (options.onError) {\n options.onError(error);\n }\n }\n },\n [options]\n );\n\n // Disconnect from VoxAI service\n const disconnect = useCallback(() => {\n setConnectionDetail(null);\n setTranscriptMap(new Map());\n setMessages([]);\n setState(\"disconnected\");\n\n if (options.onDisconnect) {\n options.onDisconnect();\n }\n }, [options]);\n\n // Update the send function with debugging and error checking\n const send = useCallback(\n ({ message, digit }: { message?: string; digit?: number }) => {\n if (state === \"disconnected\") {\n console.warn(\"Cannot send message: Not connected to a conversation\");\n return;\n }\n\n if (message) {\n // Add the message to our local transcript map for immediate feedback\n const messageId = `user-text-${Date.now()}`;\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n newMap.set(messageId, {\n id: messageId,\n name: \"user\",\n message: message,\n timestamp: Date.now(),\n isFinal: true,\n });\n return newMap;\n });\n\n // Send message through the message channel to StateMonitor\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"send_text\",\n text: message,\n });\n } else {\n console.error(\"No message channel available to send message\");\n }\n }\n\n if (digit !== undefined) {\n // Send DTMF through the message channel to StateMonitor\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"send_dtmf\",\n digit: digit,\n });\n } else {\n console.error(\"No message channel available to send DTMF\");\n }\n }\n },\n [state]\n );\n\n // Update the audioWaveform function to return data for the requested speaker\n const audioWaveform = useCallback(\n ({\n speaker = \"agent\",\n barCount = 10,\n updateInterval = 20,\n }: {\n speaker?: \"agent\" | \"user\";\n barCount?: number;\n updateInterval?: number;\n }): number[] => {\n // Store the waveform configuration for StateMonitor to use\n waveformConfigRef.current = { speaker, barCount, updateInterval };\n\n // Send the configuration to StateMonitor if channel is available\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"waveform_config\",\n config: { speaker, barCount, updateInterval },\n });\n }\n\n // Get the waveform data for the requested speaker\n const speakerData = waveformDataMap[speaker] || [];\n\n // Return the current waveform data, or a default array if no data yet\n return speakerData.length > 0\n ? speakerData.slice(0, barCount) // Ensure we return only barCount items\n : Array(barCount).fill(0);\n },\n [waveformDataMap]\n );\n\n // Add toggleMic function that will be exposed in the hook's return value\n const toggleMic = useCallback(\n (value: boolean) => {\n setIsMicEnabled(value);\n\n // Send the command to the StateMonitor through the message channel\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"toggle_mic\",\n enabled: value,\n });\n } else {\n console.error(\"No message channel available to toggle microphone\");\n }\n },\n [options]\n );\n\n // Add setVolume function that will be exposed in the hook's return value\n const setVolume = useCallback((volume: number) => {\n // Validate volume (0-1 range)\n const validVolume = Math.min(Math.max(volume, 0), 1);\n\n // Send the command to the StateMonitor through the message channel\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"set_volume\",\n volume: validVolume,\n });\n } else {\n console.error(\"No message channel available to set volume\");\n }\n }, []);\n\n // Modify the useEffect hook that renders the LiveKit component\n useEffect(() => {\n if (!rootRef.current) return;\n\n if (connectionDetail) {\n // Only create a new LiveKit component if we don't have one or connection details changed\n if (!livekitComponentRef.current) {\n if (channelRef.current) {\n // Start port2 before passing it to StateMonitor\n channelRef.current.port2.start();\n }\n\n livekitComponentRef.current = (\n <LiveKitRoom\n serverUrl={connectionDetail.serverUrl}\n token={connectionDetail.participantToken}\n audio={true}\n video={false}\n connect={true}\n onDisconnected={disconnect}\n onError={(error) => {\n console.error(\"LiveKit connection error:\", error);\n disconnect();\n if (options.onError) {\n options.onError(\n new Error(`LiveKit connection error: ${error.message}`)\n );\n }\n }}\n >\n <RoomAudioRenderer />\n {channelRef.current && (\n <StateMonitor\n port={channelRef.current.port2}\n initialConfig={\n waveformConfigRef.current || {\n barCount: 10,\n updateInterval: 20,\n }\n }\n enableNoiseCancellation={\n options.enableNoiseCancellation ?? true\n }\n />\n )}\n </LiveKitRoom>\n );\n }\n\n rootRef.current.render(livekitComponentRef.current);\n } else {\n livekitComponentRef.current = null;\n rootRef.current.render(<></>);\n }\n }, [connectionDetail, disconnect, options.onError]);\n\n /**\n * Returns the VoxAI interface for controlling the conversation\n * @returns {Object} VoxAI interface\n * @property {Function} connect - Initiates a connection to the VoxAI service. Will reject with an error if already connected or in the process of connecting.\n * @property {Function} disconnect - Terminates the connection to the VoxAI service.\n * @property {VoxAgentState} state - The current state of the agent.\n * @property {VoxMessage[]} messages - An array of messages exchanged in the conversation.\n * @property {Function} send - Sends a message or DTMF digit to the agent.\n * @property {Function} audioWaveform - Returns audio waveform data for UI visualization.\n * @property {Function} toggleMic - Toggles the microphone on/off.\n * @property {Function} setVolume - Sets the volume of the agent's audio (0-1).\n */\n return {\n connect,\n disconnect,\n state,\n messages,\n send,\n audioWaveform,\n toggleMic,\n setVolume,\n };\n}\n\n/**\n * Component that monitors LiveKit state and communicates back to the main hook\n */\nfunction StateMonitor({\n port,\n initialConfig,\n enableNoiseCancellation = true,\n}: {\n port: MessagePort | undefined;\n initialConfig: {\n speaker?: \"agent\" | \"user\";\n barCount: number;\n updateInterval: number;\n };\n enableNoiseCancellation?: boolean;\n}) {\n const { agent, state } = useVoiceAssistant();\n const { send: sendChat } = useChat();\n\n // Initialize waveform config with the passed initial values, defaulting to \"agent\" if not specified\n const [waveformConfig, setWaveformConfig] = useState({\n speaker: initialConfig.speaker || \"agent\",\n barCount: initialConfig.barCount,\n updateInterval: initialConfig.updateInterval,\n });\n\n // Agent transcriptions\n const agentAudioTrack = useParticipantTracks(\n [Track.Source.Microphone],\n agent?.identity\n )[0];\n const agentTranscription = useTrackTranscription(agentAudioTrack);\n\n // Use the current config for the waveform, applying different settings based on speaker\n const agentAudioWaveform = useAudioWaveform(agentAudioTrack, {\n barCount:\n waveformConfig.speaker === \"agent\" ? waveformConfig.barCount : 120, // default if not the selected speaker\n updateInterval:\n waveformConfig.speaker === \"agent\" ? waveformConfig.updateInterval : 20,\n });\n\n // User transcriptions\n const localParticipant = useLocalParticipant();\n const localMessages = useTrackTranscription({\n publication: localParticipant.microphoneTrack,\n source: Track.Source.Microphone,\n participant: localParticipant.localParticipant,\n });\n const localAudioTrack = useParticipantTracks(\n [Track.Source.Microphone],\n localParticipant.localParticipant.identity\n )[0];\n const userAudioWaveform = useAudioWaveform(localAudioTrack, {\n barCount: waveformConfig.speaker === \"user\" ? waveformConfig.barCount : 120, // default if not the selected speaker\n updateInterval:\n waveformConfig.speaker === \"user\" ? waveformConfig.updateInterval : 20,\n });\n\n // Apply Krisp noise cancellation to the local microphone track\n useEffect(() => {\n const applyKrispNoiseFilter = async () => {\n const track = localParticipant.microphoneTrack?.track;\n if (track && track instanceof LocalAudioTrack) {\n try {\n // Only apply noise filter if enabled\n if (enableNoiseCancellation) {\n await track.setProcessor(KrispNoiseFilter());\n } else {\n // Remove any existing processor if noise cancellation is disabled\n await track.stopProcessor();\n }\n } catch (error) {\n console.error(\"Failed to set audio processor:\", error);\n }\n }\n };\n\n applyKrispNoiseFilter();\n }, [localParticipant.microphoneTrack, enableNoiseCancellation]);\n\n // Add separate effects to send agent and user waveform data\n useEffect(() => {\n if (!port || !agentAudioWaveform || !agentAudioWaveform.bars) return;\n\n // Send the agent waveform data\n port.postMessage({\n type: \"waveform_update\",\n waveformData: agentAudioWaveform.bars,\n speaker: \"agent\",\n });\n }, [port, agentAudioWaveform]);\n\n useEffect(() => {\n if (!port || !userAudioWaveform || !userAudioWaveform.bars) return;\n\n // Send the user waveform data\n port.postMessage({\n type: \"waveform_update\",\n waveformData: userAudioWaveform.bars,\n speaker: \"user\",\n });\n }, [port, userAudioWaveform]);\n\n // Listen for messages including config updates and mic toggle commands\n useEffect(() => {\n if (!port) return;\n\n const handleMessage = (event: MessageEvent) => {\n const data = event.data;\n\n if (data.type === \"waveform_config\" && data.config) {\n // Verify we have both required properties before updating\n if (\n typeof data.config.barCount === \"number\" &&\n typeof data.config.updateInterval === \"number\"\n ) {\n setWaveformConfig(data.config);\n }\n } else if (data.type === \"send_text\") {\n if (sendChat) {\n sendChat(data.text);\n } else {\n console.error(\"sendChat function is not available\");\n }\n } else if (data.type === \"send_dtmf\") {\n if (localParticipant.localParticipant) {\n // Use standard DTMF code (RFC 4733)\n const standardDtmfCode = 101; // Standard DTMF payload type\n localParticipant.localParticipant.publishDtmf(\n standardDtmfCode,\n data.digit\n );\n } else {\n console.error(\"Local participant is not available for DTMF\");\n }\n } else if (\n data.type === \"toggle_mic\" &&\n typeof data.enabled === \"boolean\"\n ) {\n // Handle microphone toggle\n if (localParticipant.localParticipant) {\n localParticipant.localParticipant\n .setMicrophoneEnabled(data.enabled)\n .catch((error) => {\n console.error(\"Failed to toggle microphone:\", error);\n });\n } else {\n console.error(\"Local participant is not available for mic toggle\");\n }\n } else if (\n data.type === \"set_volume\" &&\n typeof data.volume === \"number\"\n ) {\n // Handle volume control\n if (agent) {\n // The agent is a RemoteParticipant, so we can call setVolume directly\n try {\n agent.setVolume(data.volume);\n console.log(`Set agent volume to ${data.volume}`);\n } catch (error) {\n console.error(\"Failed to set agent volume:\", error);\n }\n } else {\n console.error(\"Agent is not available for volume control\");\n }\n }\n };\n\n // Make sure we start the port\n port.start();\n\n port.addEventListener(\"message\", handleMessage);\n\n return () => {\n port.removeEventListener(\"message\", handleMessage);\n };\n }, [port, sendChat, localParticipant, agent]);\n\n // Send agent state updates\n useEffect(() => {\n if (port) {\n port.postMessage({ type: \"state_update\", state });\n }\n }, [state, port]);\n\n // Send agent transcriptions\n useEffect(() => {\n if (port && agentTranscription.segments.length > 0) {\n const transcriptions = agentTranscription.segments.map((segment) => ({\n id: segment.id,\n text: segment.text,\n isFinal: segment.final,\n timestamp: Date.now(),\n speaker: \"agent\" as const,\n }));\n\n port.postMessage({\n type: \"transcription_update\",\n transcriptions,\n });\n }\n }, [agentTranscription.segments, port]);\n\n // Send user transcriptions\n useEffect(() => {\n if (port && localMessages.segments.length > 0) {\n const transcriptions = localMessages.segments.map((segment) => ({\n id: segment.id,\n text: segment.text,\n isFinal: segment.final,\n timestamp: Date.now(),\n speaker: \"user\" as const,\n }));\n\n port.postMessage({\n type: \"transcription_update\",\n transcriptions,\n });\n }\n }, [localMessages.segments, port]);\n\n // Add data channel hook for function calls\n const { message: functionCallsCollected } = useDataChannel(\n \"function_calls_collected\",\n (msg) => {\n if (!port) return;\n\n const textDecoder = new TextDecoder();\n const messageString =\n msg.payload instanceof Uint8Array\n ? textDecoder.decode(msg.payload)\n : String(msg.payload);\n\n let functionCallInfo: FunctionCallInfo[];\n try {\n functionCallInfo = JSON.parse(messageString);\n\n // Send function calls to main hook via the port\n port.postMessage({\n type: \"function_calls_collected\",\n functionCalls: functionCallInfo,\n });\n } catch (e) {\n console.error(\"Failed to parse function call log:\", e);\n }\n }\n );\n\n // Add data channel hook for function call results\n const { message: functionCallsFinished } = useDataChannel(\n \"function_calls_finished\",\n (msg) => {\n if (!port) return;\n\n const textDecoder = new TextDecoder();\n const messageString =\n msg.payload instanceof Uint8Array\n ? textDecoder.decode(msg.payload)\n : String(msg.payload);\n\n let functionCallResult: FunctionCallResult[];\n try {\n functionCallResult = JSON.parse(messageString);\n\n // Send function results to main hook via the port\n port.postMessage({\n type: \"function_calls_finished\",\n functionResults: functionCallResult,\n });\n } catch (e) {\n console.error(\"Failed to parse function call result:\", e);\n }\n }\n );\n\n return null;\n}\n"],"names":["_catch","body","recover","result","e","then","StateMonitor","_ref4","port","initialConfig","enableNoiseCancellation","agent","state","useVoiceAssistant","send","sendChat","useChat","waveformConfig","setWaveformConfig","useState","speaker","barCount","updateInterval","agentAudioTrack","useParticipantTracks","Track","Source","Microphone","identity","agentTranscription","useTrackTranscription","agentAudioWaveform","useAudioWaveform","localParticipant","useLocalParticipant","localMessages","publication","microphoneTrack","source","participant","localAudioTrack","userAudioWaveform","useEffect","applyKrispNoiseFilter","_localParticipant$mic","track","Promise","resolve","LocalAudioTrack","_temp3","setProcessor","KrispNoiseFilter","stopProcessor","error","console","reject","bars","postMessage","type","waveformData","handleMessage","event","data","config","text","publishDtmf","digit","enabled","setMicrophoneEnabled","catch","volume","setVolume","log","start","addEventListener","removeEventListener","segments","length","transcriptions","map","segment","id","isFinal","final","timestamp","Date","now","useDataChannel","msg","textDecoder","TextDecoder","messageString","payload","Uint8Array","decode","String","functionCallInfo","JSON","parse","functionCalls","functionCallResult","functionResults","options","connectionDetail","setConnectionDetail","setState","transcriptMap","setTranscriptMap","Map","messages","setMessages","prevMessagesRef","useRef","processedMessageIdsRef","Set","portalRootRef","rootRef","channelRef","livekitComponentRef","waveformDataMap","setWaveformDataMap","user","waveformConfigRef","isMicEnabled","setIsMicEnabled","allMessages","Array","from","values","sort","a","b","messagesString","stringify","current","onMessage","filter","has","forEach","add","channel","MessageChannel","port1","onmessage","handleTranscriptionUpdate","prevMap","functionCallsId","newMap","set","name","toolCalls","functionResultsId","toolCallResults","_channelRef$current","_channelRef$current2","close","port2","useCallback","t","_prevMap$get","messageType","existingTimestamp","get","message","div","document","createElement","style","display","appendChild","createRoot","unmount","removeChild","connect","_ref","agentId","apiKey","dynamicVariables","metadata","errorMessage","warn","onError","Error","fetch","method","headers","Authorization","agent_id","call_web","dynamic_variables","response","_temp2","_result2","_exit","json","onConnect","_temp","ok","errorText","status","err","disconnect","onDisconnect","_ref2","messageId","undefined","audioWaveform","_ref3","speakerData","slice","fill","toggleMic","value","validVolume","Math","min","max","_options$enableNoiseC","_jsxs","jsxs","LiveKitRoom","serverUrl","token","participantToken","audio","video","onDisconnected","children","_jsx","jsx","RoomAudioRenderer","render","_Fragment"],"mappings":"gMA8zBO,SAAEA,EAAAC,EAAAC,WAENC,EAAgBF,UAE0BG,GAC3C,OAAAF,EAAQE,EAGJ,WAAkBD,EAAAE,KAElBF,EAAiBE,UAAA,EAAOH,IAIpB,CApOV,SAASI,EAAYC,OAACC,KACpBA,EAAIC,cACJA,EAAaC,wBACbA,GAA0B,GAS3BH,EACC,MAAMI,MAAEA,EAAKC,MAAEA,GAAUC,uBACjBC,KAAMC,GAAaC,EAAOA,WAG3BC,EAAgBC,GAAqBC,EAAAA,SAAS,CACnDC,QAASX,EAAcW,SAAW,QAClCC,SAAUZ,EAAcY,SACxBC,eAAgBb,EAAca,iBAI1BC,EAAkBC,uBACtB,CAACC,QAAMC,OAAOC,YACT,MAALhB,OAAK,EAALA,EAAOiB,UACP,GACIC,EAAqBC,EAAqBA,sBAACP,GAG3CQ,EAAqBC,EAAAA,iBAAiBT,EAAiB,CAC3DF,SAC6B,UAA3BJ,EAAeG,QAAsBH,EAAeI,SAAW,IACjEC,eAC6B,UAA3BL,EAAeG,QAAsBH,EAAeK,eAAiB,KAInEW,EAAmBC,EAAmBA,sBACtCC,EAAgBL,EAAqBA,sBAAC,CAC1CM,YAAaH,EAAiBI,gBAC9BC,OAAQb,QAAMC,OAAOC,WACrBY,YAAaN,EAAiBA,mBAE1BO,EAAkBhB,EAAAA,qBACtB,CAACC,EAAAA,MAAMC,OAAOC,YACdM,EAAiBA,iBAAiBL,UAClC,GACIa,EAAoBT,mBAAiBQ,EAAiB,CAC1DnB,SAAqC,SAA3BJ,EAAeG,QAAqBH,EAAeI,SAAW,IACxEC,eAC6B,SAA3BL,EAAeG,QAAqBH,EAAeK,eAAiB,KA4NxE,OAxNAoB,EAAAA,UAAU,MACmBC,WAAc,IAAA,IAAAC,EACvC,MAAMC,EAAwC,OAAnCD,EAAGX,EAAiBI,sBAAe,EAAhCO,EAAkCC,MAAM,OAAAC,QAAAC,QAAA,WAAA,GAClDF,GAASA,aAAiBG,EAAAA,gBAAe,OAAAhD,EACvC,WAAA,MAAAiD,EAEEvC,EAAuBoC,QAAAC,QACnBF,EAAMK,aAAaC,uBAAmB9C,mBAAAyC,QAAAC,QAGtCF,EAAMO,iBAAe/C,sBAAA4C,GAAAA,EAAA5C,YAAA4C,EAAA5C,KAAA,WAAA,EAE/B,EAAC,SAAQgD,GACPC,QAAQD,MAAM,iCAAkCA,EAClD,GAZoD,GAcxD,CAAC,MAAAjD,GAAA0C,OAAAA,QAAAS,OAAAnD,EAAA,CAAA,CAEDuC,EACF,EAAG,CAACV,EAAiBI,gBAAiB3B,IAGtCgC,EAAAA,UAAU,KACHlC,GAASuB,GAAuBA,EAAmByB,MAGxDhD,EAAKiD,YAAY,CACfC,KAAM,kBACNC,aAAc5B,EAAmByB,KACjCpC,QAAS,SACV,EACA,CAACZ,EAAMuB,IAEVW,YAAU,KACHlC,GAASiC,GAAsBA,EAAkBe,MAGtDhD,EAAKiD,YAAY,CACfC,KAAM,kBACNC,aAAclB,EAAkBe,KAChCpC,QAAS,UAEV,CAACZ,EAAMiC,IAGVC,YAAU,KACR,IAAKlC,EAAM,OAEX,MAAMoD,EAAiBC,IACrB,MAAMC,EAAOD,EAAMC,KAEnB,GAAkB,oBAAdA,EAAKJ,MAA8BI,EAAKC,OAGR,iBAAzBD,EAAKC,OAAO1C,UACmB,iBAA/ByC,EAAKC,OAAOzC,gBAEnBJ,EAAkB4C,EAAKC,aAEpB,GAAkB,cAAdD,EAAKJ,KACV3C,EACFA,EAAS+C,EAAKE,MAEdV,QAAQD,MAAM,2CAEPS,GAAc,cAAdA,EAAKJ,KACVzB,EAAiBA,iBAGnBA,EAAiBA,iBAAiBgC,YADT,IAGvBH,EAAKI,OAGPZ,QAAQD,MAAM,oDAEX,GACS,eAAdS,EAAKJ,MACmB,kBAAjBI,EAAKK,QAGRlC,EAAiBA,iBACnBA,EAAiBA,iBACdmC,qBAAqBN,EAAKK,SAC1BE,MAAOhB,IACNC,QAAQD,MAAM,+BAAgCA,EAAK,GAGvDC,QAAQD,MAAM,6DAGF,eAAdS,EAAKJ,MACkB,iBAAhBI,EAAKQ,OAGZ,GAAI3D,EAEF,IACEA,EAAM4D,UAAUT,EAAKQ,QACrBhB,QAAQkB,2BAA2BV,EAAKQ,OAC1C,CAAE,MAAOjB,GACPC,QAAQD,MAAM,8BAA+BA,EAC/C,MAEAC,QAAQD,MAAM,4CAElB,EAQF,OAJA7C,EAAKiE,QAELjE,EAAKkE,iBAAiB,UAAWd,GAE1B,KACLpD,EAAKmE,oBAAoB,UAAWf,EACtC,CAAA,EACC,CAACpD,EAAMO,EAAUkB,EAAkBtB,IAGtC+B,EAASA,UAAC,KACJlC,GACFA,EAAKiD,YAAY,CAAEC,KAAM,eAAgB9C,SAC3C,EACC,CAACA,EAAOJ,IAGXkC,EAASA,UAAC,KACR,GAAIlC,GAAQqB,EAAmB+C,SAASC,OAAS,EAAG,CAClD,MAAMC,EAAiBjD,EAAmB+C,SAASG,IAAKC,KACtDC,GAAID,EAAQC,GACZjB,KAAMgB,EAAQhB,KACdkB,QAASF,EAAQG,MACjBC,UAAWC,KAAKC,MAChBlE,QAAS,WAGXZ,EAAKiD,YAAY,CACfC,KAAM,uBACNoB,kBAEJ,GACC,CAACjD,EAAmB+C,SAAUpE,IAGjCkC,YAAU,KACR,GAAIlC,GAAQ2B,EAAcyC,SAASC,OAAS,EAAG,CAC7C,MAAMC,EAAiB3C,EAAcyC,SAASG,IAAKC,IAAO,CACxDC,GAAID,EAAQC,GACZjB,KAAMgB,EAAQhB,KACdkB,QAASF,EAAQG,MACjBC,UAAWC,KAAKC,MAChBlE,QAAS,UAGXZ,EAAKiD,YAAY,CACfC,KAAM,uBACNoB,kBAEJ,GACC,CAAC3C,EAAcyC,SAAUpE,IAGgB+E,EAAAA,eAC1C,2BACCC,IACC,IAAKhF,EAAM,OAEX,MAAMiF,EAAc,IAAIC,YAClBC,EACJH,EAAII,mBAAmBC,WACnBJ,EAAYK,OAAON,EAAII,SACvBG,OAAOP,EAAII,SAEjB,IAAII,EACJ,IACEA,EAAmBC,KAAKC,MAAMP,GAG9BnF,EAAKiD,YAAY,CACfC,KAAM,2BACNyC,cAAeH,GAEnB,CAAE,MAAO5F,GACPkD,QAAQD,MAAM,qCAAsCjD,EACtD,IAKuCmF,EAAcA,eACvD,0BACCC,IACC,IAAKhF,EAAM,OAEX,MAAMiF,EAAc,IAAIC,YAClBC,EACJH,EAAII,mBAAmBC,WACnBJ,EAAYK,OAAON,EAAII,SACvBG,OAAOP,EAAII,SAEjB,IAAIQ,EACJ,IACEA,EAAqBH,KAAKC,MAAMP,GAGhCnF,EAAKiD,YAAY,CACfC,KAAM,0BACN2C,gBAAiBD,GAErB,CAAE,MAAOhG,GACPkD,QAAQD,MAAM,wCAAyCjD,EACzD,IAKN,IAAA,kBA3uBgB,SAASkG,YAAAA,IAAAA,EAAwB,IAE/C,MAAOC,EAAkBC,GACvBrF,WAAqC,OAChCP,EAAO6F,GAAYtF,WAAwB,iBAG3CuF,EAAeC,GAAoBxF,WACxC,IAAIyF,MAECC,EAAUC,GAAe3F,EAAQA,SAAe,IACjD4F,EAAkBC,SAAe,IAGjCC,EAAyBD,EAAAA,OAAoB,IAAIE,KAGjDC,EAAgBH,EAAAA,OAA8B,MAC9CI,EAAUJ,EAAMA,OAAc,MAG9BK,EAAaL,SAA8B,MAG3CM,EAAsBN,EAAAA,OAAwB,OAG7CO,EAAiBC,GAAsBrG,EAAAA,SAE5C,CACAR,MAAO,GACP8G,KAAM,KAIFC,EAAoBV,EAAAA,OAIhB,OAGHW,EAAcC,GAAmBzG,EAAAA,UAAkB,GAG1DuB,EAASA,UAAC,KACR,MAAMmF,EAAcC,MAAMC,KAAKrB,EAAcsB,UAAUC,KACrD,CAACC,EAAGC,IAAMD,EAAE9C,UAAY+C,EAAE/C,WAItBgD,EAAiBnC,KAAKoC,UAAUR,GAClCO,IAAmBrB,EAAgBuB,UACrCvB,EAAgBuB,QAAUF,EAC1BtB,EAAYe,GAGRvB,EAAQiC,WACVV,EACGW,OACEhD,GACCA,EAAIN,SACJM,EAAIP,KACHgC,EAAuBqB,QAAQG,IAAIjD,EAAIP,KAE3CyD,QAASlD,IACJA,EAAIP,KAENgC,EAAuBqB,QAAQK,IAAInD,EAAIP,UAEvCqB,EAAQiC,WAARjC,EAAQiC,UAAY/C,GACtB,GAGR,EACC,CAACkB,EAAeJ,EAAQiC,YAG3B7F,EAASA,UAAC,KACR,MAAMkG,EAAU,IAAIC,eA0DpB,OAxDAD,EAAQE,MAAMC,UAAa3I,IACzB,MAAM0D,EAAO1D,EAAE0D,KAEf,GAAkB,iBAAdA,EAAKJ,KACP+C,EAAS3C,EAAKlD,YACLkD,GAAc,yBAAdA,EAAKJ,KACdsF,EAA0BlF,EAAKgB,qBACtBhB,GAAc,oBAAdA,EAAKJ,MAA8BI,EAAK1C,QAEjDoG,EAAoByB,IAAa,IAC5BA,EACH,CAACnF,EAAK1C,SAAU0C,EAAKH,qBAGvBG,GAAc,6BAAdA,EAAKJ,MACLI,EAAKqC,cACL,CAEA,MAAM+C,oBAAoC7D,KAAKC,MAC/CqB,EAAkBsC,IAChB,MAAME,EAAS,IAAIvC,IAAIqC,GAQvB,OAPAE,EAAOC,IAAIF,EAAiB,CAC1BjE,GAAIiE,EACJG,KAAM,OACNC,UAAWxF,EAAKqC,cAChBf,UAAWC,KAAKC,MAChBJ,SAAS,IAEJiE,GAEX,SACgB,4BAAdrF,EAAKJ,MACLI,EAAKuC,gBACL,CAEA,MAAMkD,sBAAwClE,KAAKC,MACnDqB,EAAkBsC,IAChB,MAAME,EAAS,IAAIvC,IAAIqC,GAQvB,OAPAE,EAAOC,IAAIG,EAAmB,CAC5BtE,GAAIsE,EACJF,KAAM,OACNG,gBAAiB1F,EAAKuC,gBACtBjB,UAAWC,KAAKC,MAChBJ,SAAS,IAEJiE,GAEX,GAIFP,EAAQE,MAAMrE,QAGd4C,EAAWiB,QAAUM,EAEd,KAAKa,IAAAA,EAAAC,SACVD,EAAApC,EAAWiB,UAAXmB,EAAoBX,MAAMa,eAC1BD,EAAArC,EAAWiB,UAAXoB,EAAoBE,MAAMD,QAC1BtC,EAAWiB,QAAU,IAAA,CACvB,EACC,IAGH,MAAMU,EAA4Ba,cAC/B/E,IACC6B,EAAkBsC,IAChB,MAAME,EAAS,IAAIvC,IAAIqC,GAgBvB,OAdAnE,EAAe4D,QAASoB,IAAKC,IAAAA,EAC3B,MAAMC,EAA4B,UAAdF,EAAE1I,QAAsB,QAAU,OAEhD6I,GAAqC,OAAjBF,EAAAd,EAAQiB,IAAIJ,EAAE7E,UAAG,EAAjB8E,EAAmB3E,YAAa0E,EAAE1E,UAE5D+D,EAAOC,IAAIU,EAAE7E,GAAI,CACfA,GAAI6E,EAAE7E,GACNoE,KAAMW,EACNG,QAASL,EAAE9F,KACXoB,UAAW6E,EACX/E,QAAS4E,EAAE5E,SAEf,GAEOiE,KAGX,IAIFzG,YAAU,KACR,MAAM0H,EAAMC,SAASC,cAAc,OAMnC,OALAF,EAAIG,MAAMC,QAAU,OACpBH,SAASpK,KAAKwK,YAAYL,GAC1BjD,EAAcmB,QAAU8B,EACxBhD,EAAQkB,QAAUoC,EAAAA,WAAWN,GAEtB,KACDhD,EAAQkB,SACVlB,EAAQkB,QAAQqC,UAEdxD,EAAcmB,SAChB+B,SAASpK,KAAK2K,YAAYzD,EAAcmB,QAC1C,CACF,EACC,IAWH,MAAMuC,EAAUhB,cAAWiB,SAAAA,OAClBC,QAAEA,EAAOC,OAAEA,EAAMC,iBAAEA,EAAgBC,SAAEA,GAAyBJ,MAAIhI,OAAAA,QAAAC,QAAA/C,aAGrE,GAAc,iBAAVY,EAA0B,CAC5B,MAAMuK,EAAY,+DAAkEvK,EAAQ,IAS5F,OARA0C,QAAQ8H,KAAKD,GAGT7E,EAAQ+E,SACV/E,EAAQ+E,QAAQ,IAAIC,MAAMH,IAIrBrI,QAAQS,OAAO,IAAI+H,MAAMH,GAClC,CAEuB,OAAvB1E,EAAS,cAAc3D,QAAAC,QAEAwI,MAjUN,sCAiU8B,CAC7CC,OAAQ,OACRC,QAAS,CACPC,wBAAyBV,EACzB,eAAgB,oBAElB/K,KAAMgG,KAAKoC,UAAU,CACnBsD,SAAUZ,EACVG,SAAU,CACRU,SAAU,CACRC,kBAAmBZ,GAAoB,CAAA,EACvCC,SAAUA,GAAY,CAAA,SAI5B7K,cAfIyL,GAAQC,SAAAA,EAAAC,GAAAC,OAAAnJ,QAAAC,QAwBK+I,EAASI,QAAM7L,KAA5ByD,SAAAA,GACN0C,EAAoB1C,GAEhBwC,EAAQ6F,WACV7F,EAAQ6F,WAAYC,EAAAA,CAAAA,MAAAA,iBAXjBN,EAASO,GAAEvJ,OAAAA,QAAAC,QACU+I,EAAS9H,QAAM3D,KAAjCiM,SAAAA,GACN,MAAM,IAAIhB,MAAK,sBACSQ,EAASS,OAAYD,MAAAA,EAC3C,EAAA,IAAA,OAAAF,GAAAA,EAAA/L,KAAA+L,EAAA/L,KAAA0L,GAAAA,KASN,EAAC,SAAQS,GAEPhG,EAAoB,MACpBG,EAAiB,IAAIC,KACrBE,EAAY,IACZL,EAAS,gBAET,MAAMpD,EAAQmJ,aAAelB,MAAQkB,EAAM,IAAIlB,MAAMvF,OAAOyG,IAExDlG,EAAQ+E,SACV/E,EAAQ+E,QAAQhI,EAEpB,GACF,CAAC,MAAAjD,GAAA,OAAA0C,QAAAS,OAAAnD,EAAA,CAAA,EACD,CAACkG,IAIGmG,EAAa5C,EAAWA,YAAC,KAC7BrD,EAAoB,MACpBG,EAAiB,IAAIC,KACrBE,EAAY,IACZL,EAAS,gBAELH,EAAQoG,cACVpG,EAAQoG,cACV,EACC,CAACpG,IAGExF,EAAO+I,EAAWA,YACtB8C,QAACxC,QAAEA,EAAOjG,MAAEA,GAA6CyI,EACvD,GAAc,iBAAV/L,EAAJ,CAKA,GAAIuJ,EAAS,CAEX,MAAMyC,eAAyBvH,KAAKC,MACpCqB,EAAkBsC,IAChB,MAAME,EAAS,IAAIvC,IAAIqC,GAQvB,OAPAE,EAAOC,IAAIwD,EAAW,CACpB3H,GAAI2H,EACJvD,KAAM,OACNc,QAASA,EACT/E,UAAWC,KAAKC,MAChBJ,SAAS,IAEJiE,IAIL9B,EAAWiB,QACbjB,EAAWiB,QAAQQ,MAAMrF,YAAY,CACnCC,KAAM,YACNM,KAAMmG,IAGR7G,QAAQD,MAAM,+CAElB,MAEcwJ,IAAV3I,IAEEmD,EAAWiB,QACbjB,EAAWiB,QAAQQ,MAAMrF,YAAY,CACnCC,KAAM,YACNQ,MAAOA,IAGTZ,QAAQD,MAAM,6CApClB,MAFEC,QAAQ8H,KAAK,uDAwCf,EAEF,CAACxK,IAIGkM,EAAgBjD,EAAAA,YACpBkD,IAQe,IARd3L,QACCA,EAAU,QAAOC,SACjBA,EAAW,GAAEC,eACbA,EAAiB,IAKlByL,EAECrF,EAAkBY,QAAU,CAAElH,UAASC,WAAUC,kBAG7C+F,EAAWiB,SACbjB,EAAWiB,QAAQQ,MAAMrF,YAAY,CACnCC,KAAM,kBACNK,OAAQ,CAAE3C,UAASC,WAAUC,oBAKjC,MAAM0L,EAAczF,EAAgBnG,IAAY,GAGhD,OAAO4L,EAAYnI,OAAS,EACxBmI,EAAYC,MAAM,EAAG5L,GACrByG,MAAMzG,GAAU6L,KAAK,EAAC,EAE5B,CAAC3F,IAIG4F,EAAYtD,cACfuD,IACCxF,EAAgBwF,GAGZ/F,EAAWiB,QACbjB,EAAWiB,QAAQQ,MAAMrF,YAAY,CACnCC,KAAM,aACNS,QAASiJ,IAGX9J,QAAQD,MAAM,oDAChB,EAEF,CAACiD,IAIG/B,EAAYsF,cAAavF,IAE7B,MAAM+I,EAAcC,KAAKC,IAAID,KAAKE,IAAIlJ,EAAQ,GAAI,GAG9C+C,EAAWiB,QACbjB,EAAWiB,QAAQQ,MAAMrF,YAAY,CACnCC,KAAM,aACNY,OAAQ+I,IAGV/J,QAAQD,MAAM,6CAChB,EACC,IAsEH,OAnEAX,EAAAA,UAAU,KACR,GAAK0E,EAAQkB,QAEb,GAAI/B,EAAkB,KAEckH,EAA7BnG,EAAoBgB,UACnBjB,EAAWiB,SAEbjB,EAAWiB,QAAQsB,MAAMnF,QAG3B6C,EAAoBgB,QAClBoF,EAACC,KAAAC,eACCC,UAAWtH,EAAiBsH,UAC5BC,MAAOvH,EAAiBwH,iBACxBC,OAAO,EACPC,OAAO,EACPpD,SAAS,EACTqD,eAAgBzB,EAChBpB,QAAUhI,IACRC,QAAQD,MAAM,4BAA6BA,GAC3CoJ,IACInG,EAAQ+E,SACV/E,EAAQ+E,QACN,IAAIC,mCAAmCjI,EAAM8G,SAEjD,EACDgE,SAAA,CAEDC,EAACC,IAAAC,oBAAoB,CAAA,GACpBjH,EAAWiB,SACV8F,MAAC9N,EAAY,CACXE,KAAM6G,EAAWiB,QAAQsB,MACzBnJ,cACEiH,EAAkBY,SAAW,CAC3BjH,SAAU,GACVC,eAAgB,IAGpBZ,wBACiC,OADV+M,EACrBnH,EAAQ5F,0BAAuB+M,QAQ3CrG,EAAQkB,QAAQiG,OAAOjH,EAAoBgB,QAC7C,MACEhB,EAAoBgB,QAAU,KAC9BlB,EAAQkB,QAAQiG,OAAOH,EAAAC,IAAAG,WAAA,CAAA,GACzB,EACC,CAACjI,EAAkBkG,EAAYnG,EAAQ+E,UAcnC,CACLR,UACA4B,aACA7L,QACAiG,WACA/F,OACAgM,gBACAK,YACA5I,YAEJ"}
@@ -1,2 +1,2 @@
1
- import{jsxs as e,jsx as t,Fragment as n}from"react/jsx-runtime";import{LiveKitRoom as r,RoomAudioRenderer as a,useVoiceAssistant as o,useChat as s,useParticipantTracks as i,useTrackTranscription as c,useAudioWaveform as l,useLocalParticipant as u,useDataChannel as p}from"@livekit/components-react";import{Track as d}from"livekit-client";import{useState as m,useRef as f,useEffect as g,useCallback as y}from"react";import{createRoot as v}from"react-dom/client";function w(){return w=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)({}).hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},w.apply(null,arguments)}function b(o={}){const[s,i]=m(null),[c,l]=m("disconnected"),[u,p]=m(new Map),[d,b]=m([]),_=f(""),C=f(new Set),M=f(null),k=f(null),D=f(null),x=f(null),[E,F]=m({agent:[],user:[]}),I=f(null);g(()=>{const e=Array.from(u.values()).sort((e,t)=>e.timestamp-t.timestamp),t=JSON.stringify(e);t!==_.current&&(_.current=t,b(e),o.onMessage&&e.filter(e=>e.isFinal&&e.id&&!C.current.has(e.id)).forEach(e=>{e.id&&(C.current.add(e.id),null==o.onMessage||o.onMessage(e))}))},[u,o.onMessage]),g(()=>{const e=new MessageChannel;return e.port1.onmessage=e=>{const t=e.data;if("state_update"===t.type)l(t.state);else if("transcription_update"===t.type)S(t.transcriptions);else if("waveform_update"===t.type&&t.speaker)F(e=>w({},e,{[t.speaker]:t.waveformData}));else if("function_calls_collected"===t.type&&t.functionCalls){const e=`function-calls-${Date.now()}`;p(n=>{const r=new Map(n);return r.set(e,{id:e,name:"tool",toolCalls:t.functionCalls,timestamp:Date.now(),isFinal:!0}),r})}else if("function_calls_finished"===t.type&&t.functionResults){const e=`function-results-${Date.now()}`;p(n=>{const r=new Map(n);return r.set(e,{id:e,name:"tool",toolCallResults:t.functionResults,timestamp:Date.now(),isFinal:!0}),r})}},e.port1.start(),D.current=e,()=>{var e,t;null==(e=D.current)||e.port1.close(),null==(t=D.current)||t.port2.close(),D.current=null}},[]);const S=y(e=>{p(t=>{const n=new Map(t);return e.forEach(e=>{var r;const a="agent"===e.speaker?"agent":"user",o=(null==(r=t.get(e.id))?void 0:r.timestamp)||e.timestamp;n.set(e.id,{id:e.id,name:a,message:e.text,timestamp:o,isFinal:e.isFinal})}),n})},[]);g(()=>{const e=document.createElement("div");return e.style.display="none",document.body.appendChild(e),M.current=e,k.current=v(e),()=>{k.current&&k.current.unmount(),M.current&&document.body.removeChild(M.current)}},[]);const O=y(async({agentId:e,apiKey:t,dynamicVariables:n,metadata:r})=>{try{l("connecting");const a=await fetch("https://www.tryvox.co/api/agent/sdk",{method:"POST",headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/json"},body:JSON.stringify({agent_id:e,metadata:{call_web:{dynamic_variables:n||{},metadata:r||{}}}})});if(!a.ok){const e=await a.text();throw new Error(`Connection failed (${a.status}): ${e}`)}const s=await a.json();i(s),o.onConnect&&o.onConnect()}catch(e){i(null),p(new Map),b([]),l("disconnected");const t=e instanceof Error?e:new Error(String(e));o.onError&&o.onError(t)}},[o]),T=y(()=>{i(null),p(new Map),b([]),l("disconnected"),o.onDisconnect&&o.onDisconnect()},[o]),N=y(({message:e,digit:t})=>{if("disconnected"!==c){if(e){const t=`user-text-${Date.now()}`;p(n=>{const r=new Map(n);return r.set(t,{id:t,name:"user",message:e,timestamp:Date.now(),isFinal:!0}),r}),D.current?D.current.port1.postMessage({type:"send_text",text:e}):console.error("No message channel available to send message")}void 0!==t&&(D.current?D.current.port1.postMessage({type:"send_dtmf",digit:t}):console.error("No message channel available to send DTMF"))}else console.warn("Cannot send message: Not connected to a conversation")},[c]),$=y(({speaker:e="agent",barCount:t=10,updateInterval:n=20})=>{I.current={speaker:e,barCount:t,updateInterval:n},D.current&&D.current.port1.postMessage({type:"waveform_config",config:{speaker:e,barCount:t,updateInterval:n}});const r=E[e]||[];return r.length>0?r.slice(0,t):Array(t).fill(0)},[E]);return g(()=>{k.current&&(s?(x.current||(D.current&&D.current.port2.start(),x.current=e(r,{serverUrl:s.serverUrl,token:s.participantToken,audio:!0,video:!1,connect:!0,onDisconnected:T,onError:e=>{console.error("LiveKit connection error:",e),T(),o.onError&&o.onError(new Error(`LiveKit connection error: ${e.message}`))},children:[t(a,{}),D.current&&t(h,{port:D.current.port2,initialConfig:I.current||{barCount:10,updateInterval:20}})]})),k.current.render(x.current)):(x.current=null,k.current.render(t(n,{}))))},[s,T,o.onError]),{connect:O,disconnect:T,state:c,messages:d,send:N,audioWaveform:$}}function h({port:e,initialConfig:t}){const{agent:n,state:r}=o(),{send:a}=s(),[f,y]=m({speaker:t.speaker||"agent",barCount:t.barCount,updateInterval:t.updateInterval}),v=i([d.Source.Microphone],null==n?void 0:n.identity)[0],w=c(v),b=l(v,{barCount:"agent"===f.speaker?f.barCount:120,updateInterval:"agent"===f.speaker?f.updateInterval:20}),h=u(),_=c({publication:h.microphoneTrack,source:d.Source.Microphone,participant:h.localParticipant}),C=i([d.Source.Microphone],h.localParticipant.identity)[0],M=l(C,{barCount:"user"===f.speaker?f.barCount:120,updateInterval:"user"===f.speaker?f.updateInterval:20});return g(()=>{e&&b&&b.bars&&e.postMessage({type:"waveform_update",waveformData:b.bars,speaker:"agent"})},[e,b]),g(()=>{e&&M&&M.bars&&e.postMessage({type:"waveform_update",waveformData:M.bars,speaker:"user"})},[e,M]),g(()=>{if(!e)return;const t=e=>{const t=e.data;"waveform_config"===t.type&&t.config?"number"==typeof t.config.barCount&&"number"==typeof t.config.updateInterval&&y(t.config):"send_text"===t.type?a?a(t.text):console.error("sendChat function is not available"):"send_dtmf"===t.type&&(h.localParticipant?h.localParticipant.publishDtmf(101,t.digit):console.error("Local participant is not available for DTMF"))};return e.start(),e.addEventListener("message",t),()=>{e.removeEventListener("message",t)}},[e,a,h]),g(()=>{e&&e.postMessage({type:"state_update",state:r})},[r,e]),g(()=>{if(e&&w.segments.length>0){const t=w.segments.map(e=>({id:e.id,text:e.text,isFinal:e.final,timestamp:Date.now(),speaker:"agent"}));e.postMessage({type:"transcription_update",transcriptions:t})}},[w.segments,e]),g(()=>{if(e&&_.segments.length>0){const t=_.segments.map(e=>({id:e.id,text:e.text,isFinal:e.final,timestamp:Date.now(),speaker:"user"}));e.postMessage({type:"transcription_update",transcriptions:t})}},[_.segments,e]),p("function_calls_collected",t=>{if(!e)return;const n=new TextDecoder,r=t.payload instanceof Uint8Array?n.decode(t.payload):String(t.payload);let a;try{a=JSON.parse(r),e.postMessage({type:"function_calls_collected",functionCalls:a})}catch(e){console.error("Failed to parse function call log:",e)}}),p("function_calls_finished",t=>{if(!e)return;const n=new TextDecoder,r=t.payload instanceof Uint8Array?n.decode(t.payload):String(t.payload);let a;try{a=JSON.parse(r),e.postMessage({type:"function_calls_finished",functionResults:a})}catch(e){console.error("Failed to parse function call result:",e)}}),null}export{b as useVoxAI};
1
+ import{jsxs as e,jsx as t,Fragment as n}from"react/jsx-runtime";import{LiveKitRoom as r,RoomAudioRenderer as o,useVoiceAssistant as a,useChat as s,useParticipantTracks as i,useTrackTranscription as c,useAudioWaveform as l,useLocalParticipant as p,useDataChannel as u}from"@livekit/components-react";import{KrispNoiseFilter as d}from"@livekit/krisp-noise-filter";import{Track as m,LocalAudioTrack as f}from"livekit-client";import{useState as g,useRef as v,useEffect as y,useCallback as b}from"react";import{createRoot as w}from"react-dom/client";function h(){return h=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)({}).hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},h.apply(null,arguments)}function M(a={}){const[s,i]=g(null),[c,l]=g("disconnected"),[p,u]=g(new Map),[d,m]=g([]),f=v(""),M=v(new Set),C=v(null),k=v(null),E=v(null),D=v(null),[x,F]=g({agent:[],user:[]}),S=v(null),[I,N]=g(!0);y(()=>{const e=Array.from(p.values()).sort((e,t)=>e.timestamp-t.timestamp),t=JSON.stringify(e);t!==f.current&&(f.current=t,m(e),a.onMessage&&e.filter(e=>e.isFinal&&e.id&&!M.current.has(e.id)).forEach(e=>{e.id&&(M.current.add(e.id),null==a.onMessage||a.onMessage(e))}))},[p,a.onMessage]),y(()=>{const e=new MessageChannel;return e.port1.onmessage=e=>{const t=e.data;if("state_update"===t.type)l(t.state);else if("transcription_update"===t.type)P(t.transcriptions);else if("waveform_update"===t.type&&t.speaker)F(e=>h({},e,{[t.speaker]:t.waveformData}));else if("function_calls_collected"===t.type&&t.functionCalls){const e=`function-calls-${Date.now()}`;u(n=>{const r=new Map(n);return r.set(e,{id:e,name:"tool",toolCalls:t.functionCalls,timestamp:Date.now(),isFinal:!0}),r})}else if("function_calls_finished"===t.type&&t.functionResults){const e=`function-results-${Date.now()}`;u(n=>{const r=new Map(n);return r.set(e,{id:e,name:"tool",toolCallResults:t.functionResults,timestamp:Date.now(),isFinal:!0}),r})}},e.port1.start(),E.current=e,()=>{var e,t;null==(e=E.current)||e.port1.close(),null==(t=E.current)||t.port2.close(),E.current=null}},[]);const P=b(e=>{u(t=>{const n=new Map(t);return e.forEach(e=>{var r;const o="agent"===e.speaker?"agent":"user",a=(null==(r=t.get(e.id))?void 0:r.timestamp)||e.timestamp;n.set(e.id,{id:e.id,name:o,message:e.text,timestamp:a,isFinal:e.isFinal})}),n})},[]);y(()=>{const e=document.createElement("div");return e.style.display="none",document.body.appendChild(e),C.current=e,k.current=w(e),()=>{k.current&&k.current.unmount(),C.current&&document.body.removeChild(C.current)}},[]);const T=b(async({agentId:e,apiKey:t,dynamicVariables:n,metadata:r})=>{try{if("disconnected"!==c){const e=`Connection attempt rejected: Already in a connection state (${c})`;return console.warn(e),a.onError&&a.onError(new Error(e)),Promise.reject(new Error(e))}l("connecting");const o=await fetch("https://www.tryvox.co/api/agent/sdk",{method:"POST",headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/json"},body:JSON.stringify({agent_id:e,metadata:{call_web:{dynamic_variables:n||{},metadata:r||{}}}})});if(!o.ok){const e=await o.text();throw new Error(`Connection failed (${o.status}): ${e}`)}const s=await o.json();i(s),a.onConnect&&a.onConnect()}catch(e){i(null),u(new Map),m([]),l("disconnected");const t=e instanceof Error?e:new Error(String(e));a.onError&&a.onError(t)}},[a]),$=b(()=>{i(null),u(new Map),m([]),l("disconnected"),a.onDisconnect&&a.onDisconnect()},[a]),O=b(({message:e,digit:t})=>{if("disconnected"!==c){if(e){const t=`user-text-${Date.now()}`;u(n=>{const r=new Map(n);return r.set(t,{id:t,name:"user",message:e,timestamp:Date.now(),isFinal:!0}),r}),E.current?E.current.port1.postMessage({type:"send_text",text:e}):console.error("No message channel available to send message")}void 0!==t&&(E.current?E.current.port1.postMessage({type:"send_dtmf",digit:t}):console.error("No message channel available to send DTMF"))}else console.warn("Cannot send message: Not connected to a conversation")},[c]),j=b(({speaker:e="agent",barCount:t=10,updateInterval:n=20})=>{S.current={speaker:e,barCount:t,updateInterval:n},E.current&&E.current.port1.postMessage({type:"waveform_config",config:{speaker:e,barCount:t,updateInterval:n}});const r=x[e]||[];return r.length>0?r.slice(0,t):Array(t).fill(0)},[x]),A=b(e=>{N(e),E.current?E.current.port1.postMessage({type:"toggle_mic",enabled:e}):console.error("No message channel available to toggle microphone")},[a]),L=b(e=>{const t=Math.min(Math.max(e,0),1);E.current?E.current.port1.postMessage({type:"set_volume",volume:t}):console.error("No message channel available to set volume")},[]);return y(()=>{if(k.current)if(s){var i;D.current||(E.current&&E.current.port2.start(),D.current=e(r,{serverUrl:s.serverUrl,token:s.participantToken,audio:!0,video:!1,connect:!0,onDisconnected:$,onError:e=>{console.error("LiveKit connection error:",e),$(),a.onError&&a.onError(new Error(`LiveKit connection error: ${e.message}`))},children:[t(o,{}),E.current&&t(_,{port:E.current.port2,initialConfig:S.current||{barCount:10,updateInterval:20},enableNoiseCancellation:null==(i=a.enableNoiseCancellation)||i})]})),k.current.render(D.current)}else D.current=null,k.current.render(t(n,{}))},[s,$,a.onError]),{connect:T,disconnect:$,state:c,messages:d,send:O,audioWaveform:j,toggleMic:A,setVolume:L}}function _({port:e,initialConfig:t,enableNoiseCancellation:n=!0}){const{agent:r,state:o}=a(),{send:v}=s(),[b,w]=g({speaker:t.speaker||"agent",barCount:t.barCount,updateInterval:t.updateInterval}),h=i([m.Source.Microphone],null==r?void 0:r.identity)[0],M=c(h),_=l(h,{barCount:"agent"===b.speaker?b.barCount:120,updateInterval:"agent"===b.speaker?b.updateInterval:20}),C=p(),k=c({publication:C.microphoneTrack,source:m.Source.Microphone,participant:C.localParticipant}),E=i([m.Source.Microphone],C.localParticipant.identity)[0],D=l(E,{barCount:"user"===b.speaker?b.barCount:120,updateInterval:"user"===b.speaker?b.updateInterval:20});return y(()=>{(async()=>{var e;const t=null==(e=C.microphoneTrack)?void 0:e.track;if(t&&t instanceof f)try{n?await t.setProcessor(d()):await t.stopProcessor()}catch(e){console.error("Failed to set audio processor:",e)}})()},[C.microphoneTrack,n]),y(()=>{e&&_&&_.bars&&e.postMessage({type:"waveform_update",waveformData:_.bars,speaker:"agent"})},[e,_]),y(()=>{e&&D&&D.bars&&e.postMessage({type:"waveform_update",waveformData:D.bars,speaker:"user"})},[e,D]),y(()=>{if(!e)return;const t=e=>{const t=e.data;if("waveform_config"===t.type&&t.config)"number"==typeof t.config.barCount&&"number"==typeof t.config.updateInterval&&w(t.config);else if("send_text"===t.type)v?v(t.text):console.error("sendChat function is not available");else if("send_dtmf"===t.type)C.localParticipant?C.localParticipant.publishDtmf(101,t.digit):console.error("Local participant is not available for DTMF");else if("toggle_mic"===t.type&&"boolean"==typeof t.enabled)C.localParticipant?C.localParticipant.setMicrophoneEnabled(t.enabled).catch(e=>{console.error("Failed to toggle microphone:",e)}):console.error("Local participant is not available for mic toggle");else if("set_volume"===t.type&&"number"==typeof t.volume)if(r)try{r.setVolume(t.volume),console.log(`Set agent volume to ${t.volume}`)}catch(e){console.error("Failed to set agent volume:",e)}else console.error("Agent is not available for volume control")};return e.start(),e.addEventListener("message",t),()=>{e.removeEventListener("message",t)}},[e,v,C,r]),y(()=>{e&&e.postMessage({type:"state_update",state:o})},[o,e]),y(()=>{if(e&&M.segments.length>0){const t=M.segments.map(e=>({id:e.id,text:e.text,isFinal:e.final,timestamp:Date.now(),speaker:"agent"}));e.postMessage({type:"transcription_update",transcriptions:t})}},[M.segments,e]),y(()=>{if(e&&k.segments.length>0){const t=k.segments.map(e=>({id:e.id,text:e.text,isFinal:e.final,timestamp:Date.now(),speaker:"user"}));e.postMessage({type:"transcription_update",transcriptions:t})}},[k.segments,e]),u("function_calls_collected",t=>{if(!e)return;const n=new TextDecoder,r=t.payload instanceof Uint8Array?n.decode(t.payload):String(t.payload);let o;try{o=JSON.parse(r),e.postMessage({type:"function_calls_collected",functionCalls:o})}catch(e){console.error("Failed to parse function call log:",e)}}),u("function_calls_finished",t=>{if(!e)return;const n=new TextDecoder,r=t.payload instanceof Uint8Array?n.decode(t.payload):String(t.payload);let o;try{o=JSON.parse(r),e.postMessage({type:"function_calls_finished",functionResults:o})}catch(e){console.error("Failed to parse function call result:",e)}}),null}export{M as useVoxAI};
2
2
  //# sourceMappingURL=lib.modern.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"lib.modern.js","sources":["../src/hooks/useVoxAI.tsx"],"sourcesContent":["import {\n LiveKitRoom,\n RoomAudioRenderer,\n useLocalParticipant,\n useParticipantTracks,\n useTrackTranscription,\n useVoiceAssistant,\n useChat,\n useAudioWaveform,\n useDataChannel,\n} from \"@livekit/components-react\";\nimport { Track } from \"livekit-client\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { createRoot, Root } from \"react-dom/client\";\n\ntype VoxConnectionDetail = {\n serverUrl: string;\n roomName: string;\n participantName: string;\n participantToken: string;\n};\n\n/**\n * VoxAgentState\n * @description The state of the agent\n */\nexport type VoxAgentState =\n | \"disconnected\"\n | \"connecting\"\n | \"initializing\"\n | \"listening\"\n | \"thinking\"\n | \"speaking\";\n\n// API endpoint\nconst HTTPS_API_ORIGIN = \"https://www.tryvox.co/api/agent/sdk\";\n// const HTTPS_API_ORIGIN = \"http://localhost:3000/api/agent/sdk\";\n\n/**\n * Function call related types\n */\nexport interface FunctionCallInfo {\n tool_call_id: string;\n function_info: {\n name: string;\n description: string;\n arguments: Record<string, any>;\n };\n raw_arguments: string;\n arguments: Record<string, any>;\n}\n\nexport interface FunctionCallResult {\n call_info: {\n tool_call_id: string;\n function_info: {\n name: string;\n description: string;\n arguments: Record<string, any>;\n };\n raw_arguments: string;\n arguments: Record<string, any>;\n };\n result: Record<string, any> | null;\n exception: string | null;\n}\n\n/**\n * VoxMessage\n * @description The message type between the agent and the user\n */\nexport type VoxMessage = {\n id?: string;\n name: \"agent\" | \"user\" | \"tool\";\n message?: string;\n timestamp: number;\n isFinal?: boolean;\n toolCalls?: FunctionCallInfo[];\n toolCallResults?: FunctionCallResult[];\n};\n\n/**\n * VoxAIOptions\n * @description The callback functions for the useVoxAI hook\n */\nexport interface VoxAIOptions {\n onConnect?: () => void;\n onDisconnect?: () => void;\n onError?: (error: Error) => void;\n onMessage?: (message: VoxMessage) => void;\n}\n\n// Message channel event types\ntype MessageChannelEvent =\n | { type: \"state_update\"; state: VoxAgentState }\n | { type: \"transcription_update\"; transcriptions: TranscriptionSegment[] }\n | {\n type: \"waveform_update\";\n waveformData: number[];\n speaker: \"agent\" | \"user\";\n }\n | { type: \"function_calls_collected\"; functionCalls: FunctionCallInfo[] }\n | { type: \"function_calls_finished\"; functionResults: FunctionCallResult[] };\n\ntype TranscriptionSegment = {\n id: string;\n text: string;\n isFinal: boolean;\n timestamp: number;\n speaker: \"agent\" | \"user\";\n};\n\n/**\n * ConnectParams\n * @param agentId - The agent ID\n * @param apiKey - The API key\n * @param dynamicVariables - The dynamic variables\n * @param metadata - 이 메타데이터는 아웃바운드 웹훅, 통화 기록에 포함됩니다.\n */\nexport interface ConnectParams {\n agentId: string;\n apiKey: string;\n dynamicVariables?: Record<string, any>;\n metadata?: Record<string, any>;\n}\n\n/**\n * useVoxAI\n * @description The hook for integrating with vox.ai voice assistant\n * @param options - The options for the useVoxAI hook\n * @returns The useVoxAI hook\n * @example\n * const { connect, disconnect, state, messages, send } = useVoxAI({\n * onConnect: () => console.log(\"Connected\"),\n * onDisconnect: () => console.log(\"Disconnected\"),\n * onError: (error) => console.error(\"Error:\", error),\n * onMessage: (message) => console.log(\"Message:\", message),\n * });\n */\nexport function useVoxAI(options: VoxAIOptions = {}) {\n // Connection state\n const [connectionDetail, setConnectionDetail] =\n useState<VoxConnectionDetail | null>(null);\n const [state, setState] = useState<VoxAgentState>(\"disconnected\");\n\n // Message handling\n const [transcriptMap, setTranscriptMap] = useState<Map<string, VoxMessage>>(\n new Map()\n );\n const [messages, setMessages] = useState<VoxMessage[]>([]);\n const prevMessagesRef = useRef<string>(\"\");\n\n // Track which messages we've already sent to the onMessage callback\n const processedMessageIdsRef = useRef<Set<string>>(new Set());\n\n // DOM manipulation for LiveKit portal\n const portalRootRef = useRef<HTMLDivElement | null>(null);\n const rootRef = useRef<Root | null>(null);\n\n // Communication channel\n const channelRef = useRef<MessageChannel | null>(null);\n\n // Add this near the start of your useVoxAI hook\n const livekitComponentRef = useRef<React.ReactNode>(null);\n\n // Replace the single waveform state with a map for multiple speakers\n const [waveformDataMap, setWaveformDataMap] = useState<\n Record<string, number[]>\n >({\n agent: [],\n user: [],\n });\n\n // Add back the waveform config reference\n const waveformConfigRef = useRef<{\n speaker?: \"agent\" | \"user\";\n barCount: number;\n updateInterval: number;\n } | null>(null);\n\n // Update messages whenever transcriptMap changes\n useEffect(() => {\n const allMessages = Array.from(transcriptMap.values()).sort(\n (a, b) => a.timestamp - b.timestamp\n );\n\n // Only update if the messages have actually changed\n const messagesString = JSON.stringify(allMessages);\n if (messagesString !== prevMessagesRef.current) {\n prevMessagesRef.current = messagesString;\n setMessages(allMessages);\n\n // Only trigger onMessage for new final messages that haven't been processed yet\n if (options.onMessage) {\n allMessages\n .filter(\n (msg) =>\n msg.isFinal &&\n msg.id &&\n !processedMessageIdsRef.current.has(msg.id)\n )\n .forEach((msg) => {\n if (msg.id) {\n // Mark this message as processed\n processedMessageIdsRef.current.add(msg.id);\n // Call the callback\n options.onMessage?.(msg);\n }\n });\n }\n }\n }, [transcriptMap, options.onMessage]);\n\n // Initialize message channel - ensure ports are properly connected\n useEffect(() => {\n const channel = new MessageChannel();\n\n channel.port1.onmessage = (e) => {\n const data = e.data as MessageChannelEvent;\n\n if (data.type === \"state_update\") {\n setState(data.state);\n } else if (data.type === \"transcription_update\") {\n handleTranscriptionUpdate(data.transcriptions);\n } else if (data.type === \"waveform_update\" && data.speaker) {\n // Store the waveform data for the specific speaker\n setWaveformDataMap((prevMap) => ({\n ...prevMap,\n [data.speaker]: data.waveformData,\n }));\n } else if (\n data.type === \"function_calls_collected\" &&\n data.functionCalls\n ) {\n // Handle function calls\n const functionCallsId = `function-calls-${Date.now()}`;\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n newMap.set(functionCallsId, {\n id: functionCallsId,\n name: \"tool\",\n toolCalls: data.functionCalls,\n timestamp: Date.now(),\n isFinal: true,\n });\n return newMap;\n });\n } else if (\n data.type === \"function_calls_finished\" &&\n data.functionResults\n ) {\n // Handle function results\n const functionResultsId = `function-results-${Date.now()}`;\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n newMap.set(functionResultsId, {\n id: functionResultsId,\n name: \"tool\",\n toolCallResults: data.functionResults,\n timestamp: Date.now(),\n isFinal: true,\n });\n return newMap;\n });\n }\n };\n\n // Start the port\n channel.port1.start();\n\n // Store the channel reference\n channelRef.current = channel;\n\n return () => {\n channelRef.current?.port1.close();\n channelRef.current?.port2.close();\n channelRef.current = null;\n };\n }, []);\n\n // Process incoming transcriptions\n const handleTranscriptionUpdate = useCallback(\n (transcriptions: TranscriptionSegment[]) => {\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n\n transcriptions.forEach((t) => {\n const messageType = t.speaker === \"agent\" ? \"agent\" : \"user\";\n // Use existing timestamp if we already have this segment\n const existingTimestamp = prevMap.get(t.id)?.timestamp || t.timestamp;\n\n newMap.set(t.id, {\n id: t.id,\n name: messageType,\n message: t.text,\n timestamp: existingTimestamp,\n isFinal: t.isFinal,\n });\n });\n\n return newMap;\n });\n },\n []\n );\n\n // Set up DOM portal for LiveKit\n useEffect(() => {\n const div = document.createElement(\"div\");\n div.style.display = \"none\";\n document.body.appendChild(div);\n portalRootRef.current = div;\n rootRef.current = createRoot(div);\n\n return () => {\n if (rootRef.current) {\n rootRef.current.unmount();\n }\n if (portalRootRef.current) {\n document.body.removeChild(portalRootRef.current);\n }\n };\n }, []);\n\n // Connect to VoxAI service - updated to include dynamicVariables\n const connect = useCallback(\n async ({ agentId, apiKey, dynamicVariables, metadata }: ConnectParams) => {\n try {\n setState(\"connecting\");\n\n const response = await fetch(HTTPS_API_ORIGIN, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n agent_id: agentId,\n metadata: {\n call_web: {\n dynamic_variables: dynamicVariables || {},\n metadata: metadata || {},\n },\n },\n }),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `Connection failed (${response.status}): ${errorText}`\n );\n }\n\n const data = await response.json();\n setConnectionDetail(data);\n\n if (options.onConnect) {\n options.onConnect();\n }\n } catch (err) {\n // Reset state on error\n setConnectionDetail(null);\n setTranscriptMap(new Map());\n setMessages([]);\n setState(\"disconnected\");\n\n const error = err instanceof Error ? err : new Error(String(err));\n\n if (options.onError) {\n options.onError(error);\n }\n }\n },\n [options]\n );\n\n // Disconnect from VoxAI service\n const disconnect = useCallback(() => {\n setConnectionDetail(null);\n setTranscriptMap(new Map());\n setMessages([]);\n setState(\"disconnected\");\n\n if (options.onDisconnect) {\n options.onDisconnect();\n }\n }, [options]);\n\n // Update the send function with debugging and error checking\n const send = useCallback(\n ({ message, digit }: { message?: string; digit?: number }) => {\n if (state === \"disconnected\") {\n console.warn(\"Cannot send message: Not connected to a conversation\");\n return;\n }\n\n if (message) {\n // Add the message to our local transcript map for immediate feedback\n const messageId = `user-text-${Date.now()}`;\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n newMap.set(messageId, {\n id: messageId,\n name: \"user\",\n message: message,\n timestamp: Date.now(),\n isFinal: true,\n });\n return newMap;\n });\n\n // Send message through the message channel to StateMonitor\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"send_text\",\n text: message,\n });\n } else {\n console.error(\"No message channel available to send message\");\n }\n }\n\n if (digit !== undefined) {\n // Send DTMF through the message channel to StateMonitor\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"send_dtmf\",\n digit: digit,\n });\n } else {\n console.error(\"No message channel available to send DTMF\");\n }\n }\n },\n [state]\n );\n\n // Update the audioWaveform function to return data for the requested speaker\n const audioWaveform = useCallback(\n ({\n speaker = \"agent\",\n barCount = 10,\n updateInterval = 20,\n }: {\n speaker?: \"agent\" | \"user\";\n barCount?: number;\n updateInterval?: number;\n }): number[] => {\n // Store the waveform configuration for StateMonitor to use\n waveformConfigRef.current = { speaker, barCount, updateInterval };\n\n // Send the configuration to StateMonitor if channel is available\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"waveform_config\",\n config: { speaker, barCount, updateInterval },\n });\n }\n\n // Get the waveform data for the requested speaker\n const speakerData = waveformDataMap[speaker] || [];\n\n // Return the current waveform data, or a default array if no data yet\n return speakerData.length > 0\n ? speakerData.slice(0, barCount) // Ensure we return only barCount items\n : Array(barCount).fill(0);\n },\n [waveformDataMap]\n );\n\n // Modify the useEffect hook that renders the LiveKit component\n useEffect(() => {\n if (!rootRef.current) return;\n\n if (connectionDetail) {\n // Only create a new LiveKit component if we don't have one or connection details changed\n if (!livekitComponentRef.current) {\n if (channelRef.current) {\n // Start port2 before passing it to StateMonitor\n channelRef.current.port2.start();\n }\n\n livekitComponentRef.current = (\n <LiveKitRoom\n serverUrl={connectionDetail.serverUrl}\n token={connectionDetail.participantToken}\n audio={true}\n video={false}\n connect={true}\n onDisconnected={disconnect}\n onError={(error) => {\n console.error(\"LiveKit connection error:\", error);\n disconnect();\n if (options.onError) {\n options.onError(\n new Error(`LiveKit connection error: ${error.message}`)\n );\n }\n }}\n >\n <RoomAudioRenderer />\n {channelRef.current && (\n <StateMonitor\n port={channelRef.current.port2}\n initialConfig={\n waveformConfigRef.current || {\n barCount: 10,\n updateInterval: 20,\n }\n }\n />\n )}\n </LiveKitRoom>\n );\n }\n\n rootRef.current.render(livekitComponentRef.current);\n } else {\n livekitComponentRef.current = null;\n rootRef.current.render(<></>);\n }\n }, [connectionDetail, disconnect, options.onError]);\n\n return {\n connect,\n disconnect,\n state,\n messages,\n send,\n audioWaveform,\n };\n}\n\n/**\n * Component that monitors LiveKit state and communicates back to the main hook\n */\nfunction StateMonitor({\n port,\n initialConfig,\n}: {\n port: MessagePort | undefined;\n initialConfig: {\n speaker?: \"agent\" | \"user\";\n barCount: number;\n updateInterval: number;\n };\n}) {\n const { agent, state } = useVoiceAssistant();\n const { send: sendChat } = useChat();\n\n // Initialize waveform config with the passed initial values, defaulting to \"agent\" if not specified\n const [waveformConfig, setWaveformConfig] = useState({\n speaker: initialConfig.speaker || \"agent\",\n barCount: initialConfig.barCount,\n updateInterval: initialConfig.updateInterval,\n });\n\n // Agent transcriptions\n const agentAudioTrack = useParticipantTracks(\n [Track.Source.Microphone],\n agent?.identity\n )[0];\n const agentTranscription = useTrackTranscription(agentAudioTrack);\n\n // Use the current config for the waveform, applying different settings based on speaker\n const agentAudioWaveform = useAudioWaveform(agentAudioTrack, {\n barCount:\n waveformConfig.speaker === \"agent\" ? waveformConfig.barCount : 120, // default if not the selected speaker\n updateInterval:\n waveformConfig.speaker === \"agent\" ? waveformConfig.updateInterval : 20,\n });\n\n // User transcriptions\n const localParticipant = useLocalParticipant();\n const localMessages = useTrackTranscription({\n publication: localParticipant.microphoneTrack,\n source: Track.Source.Microphone,\n participant: localParticipant.localParticipant,\n });\n const localAudioTrack = useParticipantTracks(\n [Track.Source.Microphone],\n localParticipant.localParticipant.identity\n )[0];\n const userAudioWaveform = useAudioWaveform(localAudioTrack, {\n barCount: waveformConfig.speaker === \"user\" ? waveformConfig.barCount : 120, // default if not the selected speaker\n updateInterval:\n waveformConfig.speaker === \"user\" ? waveformConfig.updateInterval : 20,\n });\n\n // Add separate effects to send agent and user waveform data\n useEffect(() => {\n if (!port || !agentAudioWaveform || !agentAudioWaveform.bars) return;\n\n // Send the agent waveform data\n port.postMessage({\n type: \"waveform_update\",\n waveformData: agentAudioWaveform.bars,\n speaker: \"agent\",\n });\n }, [port, agentAudioWaveform]);\n\n useEffect(() => {\n if (!port || !userAudioWaveform || !userAudioWaveform.bars) return;\n\n // Send the user waveform data\n port.postMessage({\n type: \"waveform_update\",\n waveformData: userAudioWaveform.bars,\n speaker: \"user\",\n });\n }, [port, userAudioWaveform]);\n\n // Listen for messages including config updates\n useEffect(() => {\n if (!port) return;\n\n const handleMessage = (event: MessageEvent) => {\n const data = event.data;\n\n if (data.type === \"waveform_config\" && data.config) {\n // Verify we have both required properties before updating\n if (\n typeof data.config.barCount === \"number\" &&\n typeof data.config.updateInterval === \"number\"\n ) {\n setWaveformConfig(data.config);\n }\n } else if (data.type === \"send_text\") {\n if (sendChat) {\n sendChat(data.text);\n } else {\n console.error(\"sendChat function is not available\");\n }\n } else if (data.type === \"send_dtmf\") {\n if (localParticipant.localParticipant) {\n // Use standard DTMF code (RFC 4733)\n const standardDtmfCode = 101; // Standard DTMF payload type\n localParticipant.localParticipant.publishDtmf(\n standardDtmfCode,\n data.digit\n );\n } else {\n console.error(\"Local participant is not available for DTMF\");\n }\n }\n };\n\n // Make sure we start the port\n port.start();\n\n port.addEventListener(\"message\", handleMessage);\n\n return () => {\n port.removeEventListener(\"message\", handleMessage);\n };\n }, [port, sendChat, localParticipant]);\n\n // Send agent state updates\n useEffect(() => {\n if (port) {\n port.postMessage({ type: \"state_update\", state });\n }\n }, [state, port]);\n\n // Send agent transcriptions\n useEffect(() => {\n if (port && agentTranscription.segments.length > 0) {\n const transcriptions = agentTranscription.segments.map((segment) => ({\n id: segment.id,\n text: segment.text,\n isFinal: segment.final,\n timestamp: Date.now(),\n speaker: \"agent\" as const,\n }));\n\n port.postMessage({\n type: \"transcription_update\",\n transcriptions,\n });\n }\n }, [agentTranscription.segments, port]);\n\n // Send user transcriptions\n useEffect(() => {\n if (port && localMessages.segments.length > 0) {\n const transcriptions = localMessages.segments.map((segment) => ({\n id: segment.id,\n text: segment.text,\n isFinal: segment.final,\n timestamp: Date.now(),\n speaker: \"user\" as const,\n }));\n\n port.postMessage({\n type: \"transcription_update\",\n transcriptions,\n });\n }\n }, [localMessages.segments, port]);\n\n // Add data channel hook for function calls\n const { message: functionCallsCollected } = useDataChannel(\n \"function_calls_collected\",\n (msg) => {\n if (!port) return;\n\n const textDecoder = new TextDecoder();\n const messageString =\n msg.payload instanceof Uint8Array\n ? textDecoder.decode(msg.payload)\n : String(msg.payload);\n\n let functionCallInfo: FunctionCallInfo[];\n try {\n functionCallInfo = JSON.parse(messageString);\n\n // Send function calls to main hook via the port\n port.postMessage({\n type: \"function_calls_collected\",\n functionCalls: functionCallInfo,\n });\n } catch (e) {\n console.error(\"Failed to parse function call log:\", e);\n }\n }\n );\n\n // Add data channel hook for function call results\n const { message: functionCallsFinished } = useDataChannel(\n \"function_calls_finished\",\n (msg) => {\n if (!port) return;\n\n const textDecoder = new TextDecoder();\n const messageString =\n msg.payload instanceof Uint8Array\n ? textDecoder.decode(msg.payload)\n : String(msg.payload);\n\n let functionCallResult: FunctionCallResult[];\n try {\n functionCallResult = JSON.parse(messageString);\n\n // Send function results to main hook via the port\n port.postMessage({\n type: \"function_calls_finished\",\n functionResults: functionCallResult,\n });\n } catch (e) {\n console.error(\"Failed to parse function call result:\", e);\n }\n }\n );\n\n return null;\n}\n"],"names":["useVoxAI","options","connectionDetail","setConnectionDetail","useState","state","setState","transcriptMap","setTranscriptMap","Map","messages","setMessages","prevMessagesRef","useRef","processedMessageIdsRef","Set","portalRootRef","rootRef","channelRef","livekitComponentRef","waveformDataMap","setWaveformDataMap","agent","user","waveformConfigRef","useEffect","allMessages","Array","from","values","sort","a","b","timestamp","messagesString","JSON","stringify","current","onMessage","filter","msg","isFinal","id","has","forEach","add","channel","MessageChannel","port1","onmessage","e","data","type","handleTranscriptionUpdate","transcriptions","speaker","prevMap","_extends","waveformData","functionCalls","functionCallsId","Date","now","newMap","set","name","toolCalls","functionResults","functionResultsId","toolCallResults","start","_channelRef$current","_channelRef$current2","close","port2","useCallback","t","_prevMap$get","messageType","existingTimestamp","get","message","text","div","document","createElement","style","display","body","appendChild","createRoot","unmount","removeChild","connect","async","agentId","apiKey","dynamicVariables","metadata","response","fetch","method","headers","Authorization","agent_id","call_web","dynamic_variables","ok","errorText","Error","status","json","onConnect","err","error","String","onError","disconnect","onDisconnect","send","digit","messageId","postMessage","console","undefined","warn","audioWaveform","barCount","updateInterval","config","speakerData","length","slice","fill","_jsxs","LiveKitRoom","serverUrl","token","participantToken","audio","video","onDisconnected","children","_jsx","RoomAudioRenderer","StateMonitor","port","initialConfig","render","_Fragment","useVoiceAssistant","sendChat","useChat","waveformConfig","setWaveformConfig","agentAudioTrack","useParticipantTracks","Track","Source","Microphone","identity","agentTranscription","useTrackTranscription","agentAudioWaveform","useAudioWaveform","localParticipant","useLocalParticipant","localMessages","publication","microphoneTrack","source","participant","localAudioTrack","userAudioWaveform","bars","handleMessage","event","publishDtmf","addEventListener","removeEventListener","segments","map","segment","final","useDataChannel","textDecoder","TextDecoder","messageString","payload","Uint8Array","decode","functionCallInfo","parse","functionCallResult"],"mappings":"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"}
1
+ {"version":3,"file":"lib.modern.js","sources":["../src/hooks/useVoxAI.tsx"],"sourcesContent":["import {\n LiveKitRoom,\n RoomAudioRenderer,\n useLocalParticipant,\n useParticipantTracks,\n useTrackTranscription,\n useVoiceAssistant,\n useChat,\n useAudioWaveform,\n useDataChannel,\n useTrackVolume,\n} from \"@livekit/components-react\";\nimport { KrispNoiseFilter } from \"@livekit/krisp-noise-filter\";\nimport { LocalAudioTrack, Track } from \"livekit-client\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { createRoot, Root } from \"react-dom/client\";\n\ntype VoxConnectionDetail = {\n serverUrl: string;\n roomName: string;\n participantName: string;\n participantToken: string;\n};\n\n/**\n * VoxAgentState\n * @description The state of the agent\n */\nexport type VoxAgentState =\n | \"disconnected\"\n | \"connecting\"\n | \"initializing\"\n | \"listening\"\n | \"thinking\"\n | \"speaking\";\n\n// API endpoint\nconst HTTPS_API_ORIGIN = \"https://www.tryvox.co/api/agent/sdk\";\n// const HTTPS_API_ORIGIN = \"http://localhost:3000/api/agent/sdk\";\n\n/**\n * Function call related types\n */\nexport interface FunctionCallInfo {\n tool_call_id: string;\n function_info: {\n name: string;\n description: string;\n arguments: Record<string, any>;\n };\n raw_arguments: string;\n arguments: Record<string, any>;\n}\n\nexport interface FunctionCallResult {\n call_info: {\n tool_call_id: string;\n function_info: {\n name: string;\n description: string;\n arguments: Record<string, any>;\n };\n raw_arguments: string;\n arguments: Record<string, any>;\n };\n result: Record<string, any> | null;\n exception: string | null;\n}\n\n/**\n * VoxMessage\n * @description The message type between the agent and the user\n */\nexport type VoxMessage = {\n id?: string;\n name: \"agent\" | \"user\" | \"tool\";\n message?: string;\n timestamp: number;\n isFinal?: boolean;\n toolCalls?: FunctionCallInfo[];\n toolCallResults?: FunctionCallResult[];\n};\n\n/**\n * VoxAIOptions\n * @description The callback functions for the useVoxAI hook\n */\nexport interface VoxAIOptions {\n onConnect?: () => void;\n onDisconnect?: () => void;\n onError?: (error: Error) => void;\n onMessage?: (message: VoxMessage) => void;\n enableNoiseCancellation?: boolean;\n}\n\n// Message channel event types\ntype MessageChannelEvent =\n | { type: \"state_update\"; state: VoxAgentState }\n | { type: \"transcription_update\"; transcriptions: TranscriptionSegment[] }\n | {\n type: \"waveform_update\";\n waveformData: number[];\n speaker: \"agent\" | \"user\";\n }\n | { type: \"function_calls_collected\"; functionCalls: FunctionCallInfo[] }\n | { type: \"function_calls_finished\"; functionResults: FunctionCallResult[] };\n\ntype TranscriptionSegment = {\n id: string;\n text: string;\n isFinal: boolean;\n timestamp: number;\n speaker: \"agent\" | \"user\";\n};\n\n/**\n * ConnectParams\n * @param agentId - The agent ID\n * @param apiKey - The API key\n * @param dynamicVariables - The dynamic variables\n * @param metadata - 이 메타데이터는 아웃바운드 웹훅, 통화 기록에 포함됩니다.\n */\nexport interface ConnectParams {\n agentId: string;\n apiKey: string;\n dynamicVariables?: Record<string, any>;\n metadata?: Record<string, any>;\n}\n\n/**\n * useVoxAI\n * @description The hook for integrating with vox.ai voice assistant\n * @param options - The options for the useVoxAI hook\n * @returns The useVoxAI hook\n * @example\n * const { connect, disconnect, state, messages, send } = useVoxAI({\n * onConnect: () => console.log(\"Connected\"),\n * onDisconnect: () => console.log(\"Disconnected\"),\n * onError: (error) => console.error(\"Error:\", error),\n * onMessage: (message) => console.log(\"Message:\", message),\n * });\n */\nexport function useVoxAI(options: VoxAIOptions = {}) {\n // Connection state\n const [connectionDetail, setConnectionDetail] =\n useState<VoxConnectionDetail | null>(null);\n const [state, setState] = useState<VoxAgentState>(\"disconnected\");\n\n // Message handling\n const [transcriptMap, setTranscriptMap] = useState<Map<string, VoxMessage>>(\n new Map()\n );\n const [messages, setMessages] = useState<VoxMessage[]>([]);\n const prevMessagesRef = useRef<string>(\"\");\n\n // Track which messages we've already sent to the onMessage callback\n const processedMessageIdsRef = useRef<Set<string>>(new Set());\n\n // DOM manipulation for LiveKit portal\n const portalRootRef = useRef<HTMLDivElement | null>(null);\n const rootRef = useRef<Root | null>(null);\n\n // Communication channel\n const channelRef = useRef<MessageChannel | null>(null);\n\n // Add this near the start of your useVoxAI hook\n const livekitComponentRef = useRef<React.ReactNode>(null);\n\n // Replace the single waveform state with a map for multiple speakers\n const [waveformDataMap, setWaveformDataMap] = useState<\n Record<string, number[]>\n >({\n agent: [],\n user: [],\n });\n\n // Add back the waveform config reference\n const waveformConfigRef = useRef<{\n speaker?: \"agent\" | \"user\";\n barCount: number;\n updateInterval: number;\n } | null>(null);\n\n // Add a new state to track microphone status\n const [isMicEnabled, setIsMicEnabled] = useState<boolean>(true);\n\n // Update messages whenever transcriptMap changes\n useEffect(() => {\n const allMessages = Array.from(transcriptMap.values()).sort(\n (a, b) => a.timestamp - b.timestamp\n );\n\n // Only update if the messages have actually changed\n const messagesString = JSON.stringify(allMessages);\n if (messagesString !== prevMessagesRef.current) {\n prevMessagesRef.current = messagesString;\n setMessages(allMessages);\n\n // Only trigger onMessage for new final messages that haven't been processed yet\n if (options.onMessage) {\n allMessages\n .filter(\n (msg) =>\n msg.isFinal &&\n msg.id &&\n !processedMessageIdsRef.current.has(msg.id)\n )\n .forEach((msg) => {\n if (msg.id) {\n // Mark this message as processed\n processedMessageIdsRef.current.add(msg.id);\n // Call the callback\n options.onMessage?.(msg);\n }\n });\n }\n }\n }, [transcriptMap, options.onMessage]);\n\n // Initialize message channel - ensure ports are properly connected\n useEffect(() => {\n const channel = new MessageChannel();\n\n channel.port1.onmessage = (e) => {\n const data = e.data as MessageChannelEvent;\n\n if (data.type === \"state_update\") {\n setState(data.state);\n } else if (data.type === \"transcription_update\") {\n handleTranscriptionUpdate(data.transcriptions);\n } else if (data.type === \"waveform_update\" && data.speaker) {\n // Store the waveform data for the specific speaker\n setWaveformDataMap((prevMap) => ({\n ...prevMap,\n [data.speaker]: data.waveformData,\n }));\n } else if (\n data.type === \"function_calls_collected\" &&\n data.functionCalls\n ) {\n // Handle function calls\n const functionCallsId = `function-calls-${Date.now()}`;\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n newMap.set(functionCallsId, {\n id: functionCallsId,\n name: \"tool\",\n toolCalls: data.functionCalls,\n timestamp: Date.now(),\n isFinal: true,\n });\n return newMap;\n });\n } else if (\n data.type === \"function_calls_finished\" &&\n data.functionResults\n ) {\n // Handle function results\n const functionResultsId = `function-results-${Date.now()}`;\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n newMap.set(functionResultsId, {\n id: functionResultsId,\n name: \"tool\",\n toolCallResults: data.functionResults,\n timestamp: Date.now(),\n isFinal: true,\n });\n return newMap;\n });\n }\n };\n\n // Start the port\n channel.port1.start();\n\n // Store the channel reference\n channelRef.current = channel;\n\n return () => {\n channelRef.current?.port1.close();\n channelRef.current?.port2.close();\n channelRef.current = null;\n };\n }, []);\n\n // Process incoming transcriptions\n const handleTranscriptionUpdate = useCallback(\n (transcriptions: TranscriptionSegment[]) => {\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n\n transcriptions.forEach((t) => {\n const messageType = t.speaker === \"agent\" ? \"agent\" : \"user\";\n // Use existing timestamp if we already have this segment\n const existingTimestamp = prevMap.get(t.id)?.timestamp || t.timestamp;\n\n newMap.set(t.id, {\n id: t.id,\n name: messageType,\n message: t.text,\n timestamp: existingTimestamp,\n isFinal: t.isFinal,\n });\n });\n\n return newMap;\n });\n },\n []\n );\n\n // Set up DOM portal for LiveKit\n useEffect(() => {\n const div = document.createElement(\"div\");\n div.style.display = \"none\";\n document.body.appendChild(div);\n portalRootRef.current = div;\n rootRef.current = createRoot(div);\n\n return () => {\n if (rootRef.current) {\n rootRef.current.unmount();\n }\n if (portalRootRef.current) {\n document.body.removeChild(portalRootRef.current);\n }\n };\n }, []);\n\n // Connect to VoxAI service - updated to include dynamicVariables\n /**\n * Initiates a connection to the VoxAI service\n * @param {ConnectParams} options - Connection parameters\n * @returns {Promise} - Resolves when the connection is successful, rejects if:\n * 1. The connection is already in progress (state is not \"disconnected\")\n * 2. The server returns an error\n * 3. Any other error occurs during the connection process\n */\n const connect = useCallback(\n async ({ agentId, apiKey, dynamicVariables, metadata }: ConnectParams) => {\n try {\n // Prevent connecting if already in a connection state\n if (state !== \"disconnected\") {\n const errorMessage = `Connection attempt rejected: Already in a connection state (${state})`;\n console.warn(errorMessage);\n\n // Call the onError callback if provided\n if (options.onError) {\n options.onError(new Error(errorMessage));\n }\n\n // Return a rejected promise\n return Promise.reject(new Error(errorMessage));\n }\n\n setState(\"connecting\");\n\n const response = await fetch(HTTPS_API_ORIGIN, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n agent_id: agentId,\n metadata: {\n call_web: {\n dynamic_variables: dynamicVariables || {},\n metadata: metadata || {},\n },\n },\n }),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `Connection failed (${response.status}): ${errorText}`\n );\n }\n\n const data = await response.json();\n setConnectionDetail(data);\n\n if (options.onConnect) {\n options.onConnect();\n }\n } catch (err) {\n // Reset state on error\n setConnectionDetail(null);\n setTranscriptMap(new Map());\n setMessages([]);\n setState(\"disconnected\");\n\n const error = err instanceof Error ? err : new Error(String(err));\n\n if (options.onError) {\n options.onError(error);\n }\n }\n },\n [options]\n );\n\n // Disconnect from VoxAI service\n const disconnect = useCallback(() => {\n setConnectionDetail(null);\n setTranscriptMap(new Map());\n setMessages([]);\n setState(\"disconnected\");\n\n if (options.onDisconnect) {\n options.onDisconnect();\n }\n }, [options]);\n\n // Update the send function with debugging and error checking\n const send = useCallback(\n ({ message, digit }: { message?: string; digit?: number }) => {\n if (state === \"disconnected\") {\n console.warn(\"Cannot send message: Not connected to a conversation\");\n return;\n }\n\n if (message) {\n // Add the message to our local transcript map for immediate feedback\n const messageId = `user-text-${Date.now()}`;\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n newMap.set(messageId, {\n id: messageId,\n name: \"user\",\n message: message,\n timestamp: Date.now(),\n isFinal: true,\n });\n return newMap;\n });\n\n // Send message through the message channel to StateMonitor\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"send_text\",\n text: message,\n });\n } else {\n console.error(\"No message channel available to send message\");\n }\n }\n\n if (digit !== undefined) {\n // Send DTMF through the message channel to StateMonitor\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"send_dtmf\",\n digit: digit,\n });\n } else {\n console.error(\"No message channel available to send DTMF\");\n }\n }\n },\n [state]\n );\n\n // Update the audioWaveform function to return data for the requested speaker\n const audioWaveform = useCallback(\n ({\n speaker = \"agent\",\n barCount = 10,\n updateInterval = 20,\n }: {\n speaker?: \"agent\" | \"user\";\n barCount?: number;\n updateInterval?: number;\n }): number[] => {\n // Store the waveform configuration for StateMonitor to use\n waveformConfigRef.current = { speaker, barCount, updateInterval };\n\n // Send the configuration to StateMonitor if channel is available\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"waveform_config\",\n config: { speaker, barCount, updateInterval },\n });\n }\n\n // Get the waveform data for the requested speaker\n const speakerData = waveformDataMap[speaker] || [];\n\n // Return the current waveform data, or a default array if no data yet\n return speakerData.length > 0\n ? speakerData.slice(0, barCount) // Ensure we return only barCount items\n : Array(barCount).fill(0);\n },\n [waveformDataMap]\n );\n\n // Add toggleMic function that will be exposed in the hook's return value\n const toggleMic = useCallback(\n (value: boolean) => {\n setIsMicEnabled(value);\n\n // Send the command to the StateMonitor through the message channel\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"toggle_mic\",\n enabled: value,\n });\n } else {\n console.error(\"No message channel available to toggle microphone\");\n }\n },\n [options]\n );\n\n // Add setVolume function that will be exposed in the hook's return value\n const setVolume = useCallback((volume: number) => {\n // Validate volume (0-1 range)\n const validVolume = Math.min(Math.max(volume, 0), 1);\n\n // Send the command to the StateMonitor through the message channel\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"set_volume\",\n volume: validVolume,\n });\n } else {\n console.error(\"No message channel available to set volume\");\n }\n }, []);\n\n // Modify the useEffect hook that renders the LiveKit component\n useEffect(() => {\n if (!rootRef.current) return;\n\n if (connectionDetail) {\n // Only create a new LiveKit component if we don't have one or connection details changed\n if (!livekitComponentRef.current) {\n if (channelRef.current) {\n // Start port2 before passing it to StateMonitor\n channelRef.current.port2.start();\n }\n\n livekitComponentRef.current = (\n <LiveKitRoom\n serverUrl={connectionDetail.serverUrl}\n token={connectionDetail.participantToken}\n audio={true}\n video={false}\n connect={true}\n onDisconnected={disconnect}\n onError={(error) => {\n console.error(\"LiveKit connection error:\", error);\n disconnect();\n if (options.onError) {\n options.onError(\n new Error(`LiveKit connection error: ${error.message}`)\n );\n }\n }}\n >\n <RoomAudioRenderer />\n {channelRef.current && (\n <StateMonitor\n port={channelRef.current.port2}\n initialConfig={\n waveformConfigRef.current || {\n barCount: 10,\n updateInterval: 20,\n }\n }\n enableNoiseCancellation={\n options.enableNoiseCancellation ?? true\n }\n />\n )}\n </LiveKitRoom>\n );\n }\n\n rootRef.current.render(livekitComponentRef.current);\n } else {\n livekitComponentRef.current = null;\n rootRef.current.render(<></>);\n }\n }, [connectionDetail, disconnect, options.onError]);\n\n /**\n * Returns the VoxAI interface for controlling the conversation\n * @returns {Object} VoxAI interface\n * @property {Function} connect - Initiates a connection to the VoxAI service. Will reject with an error if already connected or in the process of connecting.\n * @property {Function} disconnect - Terminates the connection to the VoxAI service.\n * @property {VoxAgentState} state - The current state of the agent.\n * @property {VoxMessage[]} messages - An array of messages exchanged in the conversation.\n * @property {Function} send - Sends a message or DTMF digit to the agent.\n * @property {Function} audioWaveform - Returns audio waveform data for UI visualization.\n * @property {Function} toggleMic - Toggles the microphone on/off.\n * @property {Function} setVolume - Sets the volume of the agent's audio (0-1).\n */\n return {\n connect,\n disconnect,\n state,\n messages,\n send,\n audioWaveform,\n toggleMic,\n setVolume,\n };\n}\n\n/**\n * Component that monitors LiveKit state and communicates back to the main hook\n */\nfunction StateMonitor({\n port,\n initialConfig,\n enableNoiseCancellation = true,\n}: {\n port: MessagePort | undefined;\n initialConfig: {\n speaker?: \"agent\" | \"user\";\n barCount: number;\n updateInterval: number;\n };\n enableNoiseCancellation?: boolean;\n}) {\n const { agent, state } = useVoiceAssistant();\n const { send: sendChat } = useChat();\n\n // Initialize waveform config with the passed initial values, defaulting to \"agent\" if not specified\n const [waveformConfig, setWaveformConfig] = useState({\n speaker: initialConfig.speaker || \"agent\",\n barCount: initialConfig.barCount,\n updateInterval: initialConfig.updateInterval,\n });\n\n // Agent transcriptions\n const agentAudioTrack = useParticipantTracks(\n [Track.Source.Microphone],\n agent?.identity\n )[0];\n const agentTranscription = useTrackTranscription(agentAudioTrack);\n\n // Use the current config for the waveform, applying different settings based on speaker\n const agentAudioWaveform = useAudioWaveform(agentAudioTrack, {\n barCount:\n waveformConfig.speaker === \"agent\" ? waveformConfig.barCount : 120, // default if not the selected speaker\n updateInterval:\n waveformConfig.speaker === \"agent\" ? waveformConfig.updateInterval : 20,\n });\n\n // User transcriptions\n const localParticipant = useLocalParticipant();\n const localMessages = useTrackTranscription({\n publication: localParticipant.microphoneTrack,\n source: Track.Source.Microphone,\n participant: localParticipant.localParticipant,\n });\n const localAudioTrack = useParticipantTracks(\n [Track.Source.Microphone],\n localParticipant.localParticipant.identity\n )[0];\n const userAudioWaveform = useAudioWaveform(localAudioTrack, {\n barCount: waveformConfig.speaker === \"user\" ? waveformConfig.barCount : 120, // default if not the selected speaker\n updateInterval:\n waveformConfig.speaker === \"user\" ? waveformConfig.updateInterval : 20,\n });\n\n // Apply Krisp noise cancellation to the local microphone track\n useEffect(() => {\n const applyKrispNoiseFilter = async () => {\n const track = localParticipant.microphoneTrack?.track;\n if (track && track instanceof LocalAudioTrack) {\n try {\n // Only apply noise filter if enabled\n if (enableNoiseCancellation) {\n await track.setProcessor(KrispNoiseFilter());\n } else {\n // Remove any existing processor if noise cancellation is disabled\n await track.stopProcessor();\n }\n } catch (error) {\n console.error(\"Failed to set audio processor:\", error);\n }\n }\n };\n\n applyKrispNoiseFilter();\n }, [localParticipant.microphoneTrack, enableNoiseCancellation]);\n\n // Add separate effects to send agent and user waveform data\n useEffect(() => {\n if (!port || !agentAudioWaveform || !agentAudioWaveform.bars) return;\n\n // Send the agent waveform data\n port.postMessage({\n type: \"waveform_update\",\n waveformData: agentAudioWaveform.bars,\n speaker: \"agent\",\n });\n }, [port, agentAudioWaveform]);\n\n useEffect(() => {\n if (!port || !userAudioWaveform || !userAudioWaveform.bars) return;\n\n // Send the user waveform data\n port.postMessage({\n type: \"waveform_update\",\n waveformData: userAudioWaveform.bars,\n speaker: \"user\",\n });\n }, [port, userAudioWaveform]);\n\n // Listen for messages including config updates and mic toggle commands\n useEffect(() => {\n if (!port) return;\n\n const handleMessage = (event: MessageEvent) => {\n const data = event.data;\n\n if (data.type === \"waveform_config\" && data.config) {\n // Verify we have both required properties before updating\n if (\n typeof data.config.barCount === \"number\" &&\n typeof data.config.updateInterval === \"number\"\n ) {\n setWaveformConfig(data.config);\n }\n } else if (data.type === \"send_text\") {\n if (sendChat) {\n sendChat(data.text);\n } else {\n console.error(\"sendChat function is not available\");\n }\n } else if (data.type === \"send_dtmf\") {\n if (localParticipant.localParticipant) {\n // Use standard DTMF code (RFC 4733)\n const standardDtmfCode = 101; // Standard DTMF payload type\n localParticipant.localParticipant.publishDtmf(\n standardDtmfCode,\n data.digit\n );\n } else {\n console.error(\"Local participant is not available for DTMF\");\n }\n } else if (\n data.type === \"toggle_mic\" &&\n typeof data.enabled === \"boolean\"\n ) {\n // Handle microphone toggle\n if (localParticipant.localParticipant) {\n localParticipant.localParticipant\n .setMicrophoneEnabled(data.enabled)\n .catch((error) => {\n console.error(\"Failed to toggle microphone:\", error);\n });\n } else {\n console.error(\"Local participant is not available for mic toggle\");\n }\n } else if (\n data.type === \"set_volume\" &&\n typeof data.volume === \"number\"\n ) {\n // Handle volume control\n if (agent) {\n // The agent is a RemoteParticipant, so we can call setVolume directly\n try {\n agent.setVolume(data.volume);\n console.log(`Set agent volume to ${data.volume}`);\n } catch (error) {\n console.error(\"Failed to set agent volume:\", error);\n }\n } else {\n console.error(\"Agent is not available for volume control\");\n }\n }\n };\n\n // Make sure we start the port\n port.start();\n\n port.addEventListener(\"message\", handleMessage);\n\n return () => {\n port.removeEventListener(\"message\", handleMessage);\n };\n }, [port, sendChat, localParticipant, agent]);\n\n // Send agent state updates\n useEffect(() => {\n if (port) {\n port.postMessage({ type: \"state_update\", state });\n }\n }, [state, port]);\n\n // Send agent transcriptions\n useEffect(() => {\n if (port && agentTranscription.segments.length > 0) {\n const transcriptions = agentTranscription.segments.map((segment) => ({\n id: segment.id,\n text: segment.text,\n isFinal: segment.final,\n timestamp: Date.now(),\n speaker: \"agent\" as const,\n }));\n\n port.postMessage({\n type: \"transcription_update\",\n transcriptions,\n });\n }\n }, [agentTranscription.segments, port]);\n\n // Send user transcriptions\n useEffect(() => {\n if (port && localMessages.segments.length > 0) {\n const transcriptions = localMessages.segments.map((segment) => ({\n id: segment.id,\n text: segment.text,\n isFinal: segment.final,\n timestamp: Date.now(),\n speaker: \"user\" as const,\n }));\n\n port.postMessage({\n type: \"transcription_update\",\n transcriptions,\n });\n }\n }, [localMessages.segments, port]);\n\n // Add data channel hook for function calls\n const { message: functionCallsCollected } = useDataChannel(\n \"function_calls_collected\",\n (msg) => {\n if (!port) return;\n\n const textDecoder = new TextDecoder();\n const messageString =\n msg.payload instanceof Uint8Array\n ? textDecoder.decode(msg.payload)\n : String(msg.payload);\n\n let functionCallInfo: FunctionCallInfo[];\n try {\n functionCallInfo = JSON.parse(messageString);\n\n // Send function calls to main hook via the port\n port.postMessage({\n type: \"function_calls_collected\",\n functionCalls: functionCallInfo,\n });\n } catch (e) {\n console.error(\"Failed to parse function call log:\", e);\n }\n }\n );\n\n // Add data channel hook for function call results\n const { message: functionCallsFinished } = useDataChannel(\n \"function_calls_finished\",\n (msg) => {\n if (!port) return;\n\n const textDecoder = new TextDecoder();\n const messageString =\n msg.payload instanceof Uint8Array\n ? textDecoder.decode(msg.payload)\n : String(msg.payload);\n\n let functionCallResult: FunctionCallResult[];\n try {\n functionCallResult = JSON.parse(messageString);\n\n // Send function results to main hook via the port\n port.postMessage({\n type: \"function_calls_finished\",\n functionResults: functionCallResult,\n });\n } catch (e) {\n console.error(\"Failed to parse function call result:\", e);\n }\n }\n );\n\n return null;\n}\n"],"names":["useVoxAI","options","connectionDetail","setConnectionDetail","useState","state","setState","transcriptMap","setTranscriptMap","Map","messages","setMessages","prevMessagesRef","useRef","processedMessageIdsRef","Set","portalRootRef","rootRef","channelRef","livekitComponentRef","waveformDataMap","setWaveformDataMap","agent","user","waveformConfigRef","isMicEnabled","setIsMicEnabled","useEffect","allMessages","Array","from","values","sort","a","b","timestamp","messagesString","JSON","stringify","current","onMessage","filter","msg","isFinal","id","has","forEach","add","channel","MessageChannel","port1","onmessage","e","data","type","handleTranscriptionUpdate","transcriptions","speaker","prevMap","_extends","waveformData","functionCalls","functionCallsId","Date","now","newMap","set","name","toolCalls","functionResults","functionResultsId","toolCallResults","start","_channelRef$current","_channelRef$current2","close","port2","useCallback","t","_prevMap$get","messageType","existingTimestamp","get","message","text","div","document","createElement","style","display","body","appendChild","createRoot","unmount","removeChild","connect","async","agentId","apiKey","dynamicVariables","metadata","errorMessage","console","warn","onError","Error","Promise","reject","response","fetch","method","headers","Authorization","agent_id","call_web","dynamic_variables","ok","errorText","status","json","onConnect","err","error","String","disconnect","onDisconnect","send","digit","messageId","postMessage","undefined","audioWaveform","barCount","updateInterval","config","speakerData","length","slice","fill","toggleMic","value","enabled","setVolume","volume","validVolume","Math","min","max","_options$enableNoiseC","_jsxs","LiveKitRoom","serverUrl","token","participantToken","audio","video","onDisconnected","children","_jsx","RoomAudioRenderer","StateMonitor","port","initialConfig","enableNoiseCancellation","render","_Fragment","useVoiceAssistant","sendChat","useChat","waveformConfig","setWaveformConfig","agentAudioTrack","useParticipantTracks","Track","Source","Microphone","identity","agentTranscription","useTrackTranscription","agentAudioWaveform","useAudioWaveform","localParticipant","useLocalParticipant","localMessages","publication","microphoneTrack","source","participant","localAudioTrack","userAudioWaveform","_localParticipant$mic","track","LocalAudioTrack","setProcessor","KrispNoiseFilter","stopProcessor","applyKrispNoiseFilter","bars","handleMessage","event","publishDtmf","setMicrophoneEnabled","catch","log","addEventListener","removeEventListener","segments","map","segment","final","useDataChannel","textDecoder","TextDecoder","messageString","payload","Uint8Array","decode","functionCallInfo","parse","functionCallResult"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{jsxs as e,jsx as t,Fragment as n}from"react/jsx-runtime";import{LiveKitRoom as r,RoomAudioRenderer as o,useVoiceAssistant as a,useChat as s,useParticipantTracks as i,useTrackTranscription as c,useAudioWaveform as l,useLocalParticipant as u,useDataChannel as p}from"@livekit/components-react";import{Track as d}from"livekit-client";import{useState as m,useRef as f,useEffect as g,useCallback as v}from"react";import{createRoot as y}from"react-dom/client";function h(a){void 0===a&&(a={});const[s,i]=m(null),[c,l]=m("disconnected"),[u,p]=m(new Map),[d,h]=m([]),b=f(""),_=f(new Set),C=f(null),M=f(null),k=f(null),D=f(null),[x,E]=m({agent:[],user:[]}),F=f(null);g(()=>{const e=Array.from(u.values()).sort((e,t)=>e.timestamp-t.timestamp),t=JSON.stringify(e);t!==b.current&&(b.current=t,h(e),a.onMessage&&e.filter(e=>e.isFinal&&e.id&&!_.current.has(e.id)).forEach(e=>{e.id&&(_.current.add(e.id),null==a.onMessage||a.onMessage(e))}))},[u,a.onMessage]),g(()=>{const e=new MessageChannel;return e.port1.onmessage=e=>{const t=e.data;if("state_update"===t.type)l(t.state);else if("transcription_update"===t.type)I(t.transcriptions);else if("waveform_update"===t.type&&t.speaker)E(e=>({...e,[t.speaker]:t.waveformData}));else if("function_calls_collected"===t.type&&t.functionCalls){const e="function-calls-"+Date.now();p(n=>{const r=new Map(n);return r.set(e,{id:e,name:"tool",toolCalls:t.functionCalls,timestamp:Date.now(),isFinal:!0}),r})}else if("function_calls_finished"===t.type&&t.functionResults){const e="function-results-"+Date.now();p(n=>{const r=new Map(n);return r.set(e,{id:e,name:"tool",toolCallResults:t.functionResults,timestamp:Date.now(),isFinal:!0}),r})}},e.port1.start(),k.current=e,()=>{var e,t;null==(e=k.current)||e.port1.close(),null==(t=k.current)||t.port2.close(),k.current=null}},[]);const I=v(e=>{p(t=>{const n=new Map(t);return e.forEach(e=>{var r;const o="agent"===e.speaker?"agent":"user",a=(null==(r=t.get(e.id))?void 0:r.timestamp)||e.timestamp;n.set(e.id,{id:e.id,name:o,message:e.text,timestamp:a,isFinal:e.isFinal})}),n})},[]);g(()=>{const e=document.createElement("div");return e.style.display="none",document.body.appendChild(e),C.current=e,M.current=y(e),()=>{M.current&&M.current.unmount(),C.current&&document.body.removeChild(C.current)}},[]);const S=v(function(e){let{agentId:t,apiKey:n,dynamicVariables:r,metadata:o}=e;try{return Promise.resolve(function(e,s){try{var c=(l("connecting"),Promise.resolve(fetch("https://www.tryvox.co/api/agent/sdk",{method:"POST",headers:{Authorization:"Bearer "+n,"Content-Type":"application/json"},body:JSON.stringify({agent_id:t,metadata:{call_web:{dynamic_variables:r||{},metadata:o||{}}}})})).then(function(e){function t(t){return Promise.resolve(e.json()).then(function(e){i(e),a.onConnect&&a.onConnect()})}const n=function(){if(!e.ok)return Promise.resolve(e.text()).then(function(t){throw new Error("Connection failed ("+e.status+"): "+t)})}();return n&&n.then?n.then(t):t()}))}catch(e){return s(e)}return c&&c.then?c.then(void 0,s):c}(0,function(e){i(null),p(new Map),h([]),l("disconnected");const t=e instanceof Error?e:new Error(String(e));a.onError&&a.onError(t)}))}catch(e){return Promise.reject(e)}},[a]),P=v(()=>{i(null),p(new Map),h([]),l("disconnected"),a.onDisconnect&&a.onDisconnect()},[a]),T=v(e=>{let{message:t,digit:n}=e;if("disconnected"!==c){if(t){const e="user-text-"+Date.now();p(n=>{const r=new Map(n);return r.set(e,{id:e,name:"user",message:t,timestamp:Date.now(),isFinal:!0}),r}),k.current?k.current.port1.postMessage({type:"send_text",text:t}):console.error("No message channel available to send message")}void 0!==n&&(k.current?k.current.port1.postMessage({type:"send_dtmf",digit:n}):console.error("No message channel available to send DTMF"))}else console.warn("Cannot send message: Not connected to a conversation")},[c]),N=v(e=>{let{speaker:t="agent",barCount:n=10,updateInterval:r=20}=e;F.current={speaker:t,barCount:n,updateInterval:r},k.current&&k.current.port1.postMessage({type:"waveform_config",config:{speaker:t,barCount:n,updateInterval:r}});const o=x[t]||[];return o.length>0?o.slice(0,n):Array(n).fill(0)},[x]);return g(()=>{M.current&&(s?(D.current||(k.current&&k.current.port2.start(),D.current=e(r,{serverUrl:s.serverUrl,token:s.participantToken,audio:!0,video:!1,connect:!0,onDisconnected:P,onError:e=>{console.error("LiveKit connection error:",e),P(),a.onError&&a.onError(new Error("LiveKit connection error: "+e.message))},children:[t(o,{}),k.current&&t(w,{port:k.current.port2,initialConfig:F.current||{barCount:10,updateInterval:20}})]})),M.current.render(D.current)):(D.current=null,M.current.render(t(n,{}))))},[s,P,a.onError]),{connect:S,disconnect:P,state:c,messages:d,send:T,audioWaveform:N}}function w(e){let{port:t,initialConfig:n}=e;const{agent:r,state:o}=a(),{send:f}=s(),[v,y]=m({speaker:n.speaker||"agent",barCount:n.barCount,updateInterval:n.updateInterval}),h=i([d.Source.Microphone],null==r?void 0:r.identity)[0],w=c(h),b=l(h,{barCount:"agent"===v.speaker?v.barCount:120,updateInterval:"agent"===v.speaker?v.updateInterval:20}),_=u(),C=c({publication:_.microphoneTrack,source:d.Source.Microphone,participant:_.localParticipant}),M=i([d.Source.Microphone],_.localParticipant.identity)[0],k=l(M,{barCount:"user"===v.speaker?v.barCount:120,updateInterval:"user"===v.speaker?v.updateInterval:20});return g(()=>{t&&b&&b.bars&&t.postMessage({type:"waveform_update",waveformData:b.bars,speaker:"agent"})},[t,b]),g(()=>{t&&k&&k.bars&&t.postMessage({type:"waveform_update",waveformData:k.bars,speaker:"user"})},[t,k]),g(()=>{if(!t)return;const e=e=>{const t=e.data;"waveform_config"===t.type&&t.config?"number"==typeof t.config.barCount&&"number"==typeof t.config.updateInterval&&y(t.config):"send_text"===t.type?f?f(t.text):console.error("sendChat function is not available"):"send_dtmf"===t.type&&(_.localParticipant?_.localParticipant.publishDtmf(101,t.digit):console.error("Local participant is not available for DTMF"))};return t.start(),t.addEventListener("message",e),()=>{t.removeEventListener("message",e)}},[t,f,_]),g(()=>{t&&t.postMessage({type:"state_update",state:o})},[o,t]),g(()=>{if(t&&w.segments.length>0){const e=w.segments.map(e=>({id:e.id,text:e.text,isFinal:e.final,timestamp:Date.now(),speaker:"agent"}));t.postMessage({type:"transcription_update",transcriptions:e})}},[w.segments,t]),g(()=>{if(t&&C.segments.length>0){const e=C.segments.map(e=>({id:e.id,text:e.text,isFinal:e.final,timestamp:Date.now(),speaker:"user"}));t.postMessage({type:"transcription_update",transcriptions:e})}},[C.segments,t]),p("function_calls_collected",e=>{if(!t)return;const n=new TextDecoder,r=e.payload instanceof Uint8Array?n.decode(e.payload):String(e.payload);let o;try{o=JSON.parse(r),t.postMessage({type:"function_calls_collected",functionCalls:o})}catch(e){console.error("Failed to parse function call log:",e)}}),p("function_calls_finished",e=>{if(!t)return;const n=new TextDecoder,r=e.payload instanceof Uint8Array?n.decode(e.payload):String(e.payload);let o;try{o=JSON.parse(r),t.postMessage({type:"function_calls_finished",functionResults:o})}catch(e){console.error("Failed to parse function call result:",e)}}),null}export{h as useVoxAI};
1
+ import{jsxs as e,jsx as t,Fragment as n}from"react/jsx-runtime";import{LiveKitRoom as r,RoomAudioRenderer as o,useVoiceAssistant as a,useChat as s,useParticipantTracks as i,useTrackTranscription as c,useAudioWaveform as l,useLocalParticipant as u,useDataChannel as p}from"@livekit/components-react";import{KrispNoiseFilter as d}from"@livekit/krisp-noise-filter";import{Track as m,LocalAudioTrack as f}from"livekit-client";import{useState as g,useRef as v,useEffect as y,useCallback as h}from"react";import{createRoot as b}from"react-dom/client";function w(e,t){try{var n=e()}catch(e){return t(e)}return n&&n.then?n.then(void 0,t):n}function M(a){void 0===a&&(a={});const[s,i]=g(null),[c,l]=g("disconnected"),[u,p]=g(new Map),[d,m]=g([]),f=v(""),M=v(new Set),C=v(null),k=v(null),E=v(null),D=v(null),[P,x]=g({agent:[],user:[]}),F=v(null),[S,I]=g(!0);y(()=>{const e=Array.from(u.values()).sort((e,t)=>e.timestamp-t.timestamp),t=JSON.stringify(e);t!==f.current&&(f.current=t,m(e),a.onMessage&&e.filter(e=>e.isFinal&&e.id&&!M.current.has(e.id)).forEach(e=>{e.id&&(M.current.add(e.id),null==a.onMessage||a.onMessage(e))}))},[u,a.onMessage]),y(()=>{const e=new MessageChannel;return e.port1.onmessage=e=>{const t=e.data;if("state_update"===t.type)l(t.state);else if("transcription_update"===t.type)N(t.transcriptions);else if("waveform_update"===t.type&&t.speaker)x(e=>({...e,[t.speaker]:t.waveformData}));else if("function_calls_collected"===t.type&&t.functionCalls){const e="function-calls-"+Date.now();p(n=>{const r=new Map(n);return r.set(e,{id:e,name:"tool",toolCalls:t.functionCalls,timestamp:Date.now(),isFinal:!0}),r})}else if("function_calls_finished"===t.type&&t.functionResults){const e="function-results-"+Date.now();p(n=>{const r=new Map(n);return r.set(e,{id:e,name:"tool",toolCallResults:t.functionResults,timestamp:Date.now(),isFinal:!0}),r})}},e.port1.start(),E.current=e,()=>{var e,t;null==(e=E.current)||e.port1.close(),null==(t=E.current)||t.port2.close(),E.current=null}},[]);const N=h(e=>{p(t=>{const n=new Map(t);return e.forEach(e=>{var r;const o="agent"===e.speaker?"agent":"user",a=(null==(r=t.get(e.id))?void 0:r.timestamp)||e.timestamp;n.set(e.id,{id:e.id,name:o,message:e.text,timestamp:a,isFinal:e.isFinal})}),n})},[]);y(()=>{const e=document.createElement("div");return e.style.display="none",document.body.appendChild(e),C.current=e,k.current=b(e),()=>{k.current&&k.current.unmount(),C.current&&document.body.removeChild(C.current)}},[]);const T=h(function(e){let{agentId:t,apiKey:n,dynamicVariables:r,metadata:o}=e;try{return Promise.resolve(w(function(){if("disconnected"!==c){const e="Connection attempt rejected: Already in a connection state ("+c+")";return console.warn(e),a.onError&&a.onError(new Error(e)),Promise.reject(new Error(e))}return l("connecting"),Promise.resolve(fetch("https://www.tryvox.co/api/agent/sdk",{method:"POST",headers:{Authorization:"Bearer "+n,"Content-Type":"application/json"},body:JSON.stringify({agent_id:t,metadata:{call_web:{dynamic_variables:r||{},metadata:o||{}}}})})).then(function(e){function t(t){return Promise.resolve(e.json()).then(function(e){i(e),a.onConnect&&a.onConnect()})}const n=function(){if(!e.ok)return Promise.resolve(e.text()).then(function(t){throw new Error("Connection failed ("+e.status+"): "+t)})}();return n&&n.then?n.then(t):t()})},function(e){i(null),p(new Map),m([]),l("disconnected");const t=e instanceof Error?e:new Error(String(e));a.onError&&a.onError(t)}))}catch(e){return Promise.reject(e)}},[a]),j=h(()=>{i(null),p(new Map),m([]),l("disconnected"),a.onDisconnect&&a.onDisconnect()},[a]),A=h(e=>{let{message:t,digit:n}=e;if("disconnected"!==c){if(t){const e="user-text-"+Date.now();p(n=>{const r=new Map(n);return r.set(e,{id:e,name:"user",message:t,timestamp:Date.now(),isFinal:!0}),r}),E.current?E.current.port1.postMessage({type:"send_text",text:t}):console.error("No message channel available to send message")}void 0!==n&&(E.current?E.current.port1.postMessage({type:"send_dtmf",digit:n}):console.error("No message channel available to send DTMF"))}else console.warn("Cannot send message: Not connected to a conversation")},[c]),L=h(e=>{let{speaker:t="agent",barCount:n=10,updateInterval:r=20}=e;F.current={speaker:t,barCount:n,updateInterval:r},E.current&&E.current.port1.postMessage({type:"waveform_config",config:{speaker:t,barCount:n,updateInterval:r}});const o=P[t]||[];return o.length>0?o.slice(0,n):Array(n).fill(0)},[P]),O=h(e=>{I(e),E.current?E.current.port1.postMessage({type:"toggle_mic",enabled:e}):console.error("No message channel available to toggle microphone")},[a]),J=h(e=>{const t=Math.min(Math.max(e,0),1);E.current?E.current.port1.postMessage({type:"set_volume",volume:t}):console.error("No message channel available to set volume")},[]);return y(()=>{if(k.current)if(s){var i;D.current||(E.current&&E.current.port2.start(),D.current=e(r,{serverUrl:s.serverUrl,token:s.participantToken,audio:!0,video:!1,connect:!0,onDisconnected:j,onError:e=>{console.error("LiveKit connection error:",e),j(),a.onError&&a.onError(new Error("LiveKit connection error: "+e.message))},children:[t(o,{}),E.current&&t(_,{port:E.current.port2,initialConfig:F.current||{barCount:10,updateInterval:20},enableNoiseCancellation:null==(i=a.enableNoiseCancellation)||i})]})),k.current.render(D.current)}else D.current=null,k.current.render(t(n,{}))},[s,j,a.onError]),{connect:T,disconnect:j,state:c,messages:d,send:A,audioWaveform:L,toggleMic:O,setVolume:J}}function _(e){let{port:t,initialConfig:n,enableNoiseCancellation:r=!0}=e;const{agent:o,state:v}=a(),{send:h}=s(),[b,M]=g({speaker:n.speaker||"agent",barCount:n.barCount,updateInterval:n.updateInterval}),_=i([m.Source.Microphone],null==o?void 0:o.identity)[0],C=c(_),k=l(_,{barCount:"agent"===b.speaker?b.barCount:120,updateInterval:"agent"===b.speaker?b.updateInterval:20}),E=u(),D=c({publication:E.microphoneTrack,source:m.Source.Microphone,participant:E.localParticipant}),P=i([m.Source.Microphone],E.localParticipant.identity)[0],x=l(P,{barCount:"user"===b.speaker?b.barCount:120,updateInterval:"user"===b.speaker?b.updateInterval:20});return y(()=>{!function(){try{var e;const t=null==(e=E.microphoneTrack)?void 0:e.track;return Promise.resolve(function(){if(t&&t instanceof f)return w(function(){const e=r?Promise.resolve(t.setProcessor(d())).then(function(){}):Promise.resolve(t.stopProcessor()).then(function(){});if(e&&e.then)return e.then(function(){})},function(e){console.error("Failed to set audio processor:",e)})}())}catch(e){return Promise.reject(e)}}()},[E.microphoneTrack,r]),y(()=>{t&&k&&k.bars&&t.postMessage({type:"waveform_update",waveformData:k.bars,speaker:"agent"})},[t,k]),y(()=>{t&&x&&x.bars&&t.postMessage({type:"waveform_update",waveformData:x.bars,speaker:"user"})},[t,x]),y(()=>{if(!t)return;const e=e=>{const t=e.data;if("waveform_config"===t.type&&t.config)"number"==typeof t.config.barCount&&"number"==typeof t.config.updateInterval&&M(t.config);else if("send_text"===t.type)h?h(t.text):console.error("sendChat function is not available");else if("send_dtmf"===t.type)E.localParticipant?E.localParticipant.publishDtmf(101,t.digit):console.error("Local participant is not available for DTMF");else if("toggle_mic"===t.type&&"boolean"==typeof t.enabled)E.localParticipant?E.localParticipant.setMicrophoneEnabled(t.enabled).catch(e=>{console.error("Failed to toggle microphone:",e)}):console.error("Local participant is not available for mic toggle");else if("set_volume"===t.type&&"number"==typeof t.volume)if(o)try{o.setVolume(t.volume),console.log("Set agent volume to "+t.volume)}catch(e){console.error("Failed to set agent volume:",e)}else console.error("Agent is not available for volume control")};return t.start(),t.addEventListener("message",e),()=>{t.removeEventListener("message",e)}},[t,h,E,o]),y(()=>{t&&t.postMessage({type:"state_update",state:v})},[v,t]),y(()=>{if(t&&C.segments.length>0){const e=C.segments.map(e=>({id:e.id,text:e.text,isFinal:e.final,timestamp:Date.now(),speaker:"agent"}));t.postMessage({type:"transcription_update",transcriptions:e})}},[C.segments,t]),y(()=>{if(t&&D.segments.length>0){const e=D.segments.map(e=>({id:e.id,text:e.text,isFinal:e.final,timestamp:Date.now(),speaker:"user"}));t.postMessage({type:"transcription_update",transcriptions:e})}},[D.segments,t]),p("function_calls_collected",e=>{if(!t)return;const n=new TextDecoder,r=e.payload instanceof Uint8Array?n.decode(e.payload):String(e.payload);let o;try{o=JSON.parse(r),t.postMessage({type:"function_calls_collected",functionCalls:o})}catch(e){console.error("Failed to parse function call log:",e)}}),p("function_calls_finished",e=>{if(!t)return;const n=new TextDecoder,r=e.payload instanceof Uint8Array?n.decode(e.payload):String(e.payload);let o;try{o=JSON.parse(r),t.postMessage({type:"function_calls_finished",functionResults:o})}catch(e){console.error("Failed to parse function call result:",e)}}),null}export{M as useVoxAI};
2
2
  //# sourceMappingURL=lib.module.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"lib.module.js","sources":["../src/hooks/useVoxAI.tsx"],"sourcesContent":["import {\n LiveKitRoom,\n RoomAudioRenderer,\n useLocalParticipant,\n useParticipantTracks,\n useTrackTranscription,\n useVoiceAssistant,\n useChat,\n useAudioWaveform,\n useDataChannel,\n} from \"@livekit/components-react\";\nimport { Track } from \"livekit-client\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { createRoot, Root } from \"react-dom/client\";\n\ntype VoxConnectionDetail = {\n serverUrl: string;\n roomName: string;\n participantName: string;\n participantToken: string;\n};\n\n/**\n * VoxAgentState\n * @description The state of the agent\n */\nexport type VoxAgentState =\n | \"disconnected\"\n | \"connecting\"\n | \"initializing\"\n | \"listening\"\n | \"thinking\"\n | \"speaking\";\n\n// API endpoint\nconst HTTPS_API_ORIGIN = \"https://www.tryvox.co/api/agent/sdk\";\n// const HTTPS_API_ORIGIN = \"http://localhost:3000/api/agent/sdk\";\n\n/**\n * Function call related types\n */\nexport interface FunctionCallInfo {\n tool_call_id: string;\n function_info: {\n name: string;\n description: string;\n arguments: Record<string, any>;\n };\n raw_arguments: string;\n arguments: Record<string, any>;\n}\n\nexport interface FunctionCallResult {\n call_info: {\n tool_call_id: string;\n function_info: {\n name: string;\n description: string;\n arguments: Record<string, any>;\n };\n raw_arguments: string;\n arguments: Record<string, any>;\n };\n result: Record<string, any> | null;\n exception: string | null;\n}\n\n/**\n * VoxMessage\n * @description The message type between the agent and the user\n */\nexport type VoxMessage = {\n id?: string;\n name: \"agent\" | \"user\" | \"tool\";\n message?: string;\n timestamp: number;\n isFinal?: boolean;\n toolCalls?: FunctionCallInfo[];\n toolCallResults?: FunctionCallResult[];\n};\n\n/**\n * VoxAIOptions\n * @description The callback functions for the useVoxAI hook\n */\nexport interface VoxAIOptions {\n onConnect?: () => void;\n onDisconnect?: () => void;\n onError?: (error: Error) => void;\n onMessage?: (message: VoxMessage) => void;\n}\n\n// Message channel event types\ntype MessageChannelEvent =\n | { type: \"state_update\"; state: VoxAgentState }\n | { type: \"transcription_update\"; transcriptions: TranscriptionSegment[] }\n | {\n type: \"waveform_update\";\n waveformData: number[];\n speaker: \"agent\" | \"user\";\n }\n | { type: \"function_calls_collected\"; functionCalls: FunctionCallInfo[] }\n | { type: \"function_calls_finished\"; functionResults: FunctionCallResult[] };\n\ntype TranscriptionSegment = {\n id: string;\n text: string;\n isFinal: boolean;\n timestamp: number;\n speaker: \"agent\" | \"user\";\n};\n\n/**\n * ConnectParams\n * @param agentId - The agent ID\n * @param apiKey - The API key\n * @param dynamicVariables - The dynamic variables\n * @param metadata - 이 메타데이터는 아웃바운드 웹훅, 통화 기록에 포함됩니다.\n */\nexport interface ConnectParams {\n agentId: string;\n apiKey: string;\n dynamicVariables?: Record<string, any>;\n metadata?: Record<string, any>;\n}\n\n/**\n * useVoxAI\n * @description The hook for integrating with vox.ai voice assistant\n * @param options - The options for the useVoxAI hook\n * @returns The useVoxAI hook\n * @example\n * const { connect, disconnect, state, messages, send } = useVoxAI({\n * onConnect: () => console.log(\"Connected\"),\n * onDisconnect: () => console.log(\"Disconnected\"),\n * onError: (error) => console.error(\"Error:\", error),\n * onMessage: (message) => console.log(\"Message:\", message),\n * });\n */\nexport function useVoxAI(options: VoxAIOptions = {}) {\n // Connection state\n const [connectionDetail, setConnectionDetail] =\n useState<VoxConnectionDetail | null>(null);\n const [state, setState] = useState<VoxAgentState>(\"disconnected\");\n\n // Message handling\n const [transcriptMap, setTranscriptMap] = useState<Map<string, VoxMessage>>(\n new Map()\n );\n const [messages, setMessages] = useState<VoxMessage[]>([]);\n const prevMessagesRef = useRef<string>(\"\");\n\n // Track which messages we've already sent to the onMessage callback\n const processedMessageIdsRef = useRef<Set<string>>(new Set());\n\n // DOM manipulation for LiveKit portal\n const portalRootRef = useRef<HTMLDivElement | null>(null);\n const rootRef = useRef<Root | null>(null);\n\n // Communication channel\n const channelRef = useRef<MessageChannel | null>(null);\n\n // Add this near the start of your useVoxAI hook\n const livekitComponentRef = useRef<React.ReactNode>(null);\n\n // Replace the single waveform state with a map for multiple speakers\n const [waveformDataMap, setWaveformDataMap] = useState<\n Record<string, number[]>\n >({\n agent: [],\n user: [],\n });\n\n // Add back the waveform config reference\n const waveformConfigRef = useRef<{\n speaker?: \"agent\" | \"user\";\n barCount: number;\n updateInterval: number;\n } | null>(null);\n\n // Update messages whenever transcriptMap changes\n useEffect(() => {\n const allMessages = Array.from(transcriptMap.values()).sort(\n (a, b) => a.timestamp - b.timestamp\n );\n\n // Only update if the messages have actually changed\n const messagesString = JSON.stringify(allMessages);\n if (messagesString !== prevMessagesRef.current) {\n prevMessagesRef.current = messagesString;\n setMessages(allMessages);\n\n // Only trigger onMessage for new final messages that haven't been processed yet\n if (options.onMessage) {\n allMessages\n .filter(\n (msg) =>\n msg.isFinal &&\n msg.id &&\n !processedMessageIdsRef.current.has(msg.id)\n )\n .forEach((msg) => {\n if (msg.id) {\n // Mark this message as processed\n processedMessageIdsRef.current.add(msg.id);\n // Call the callback\n options.onMessage?.(msg);\n }\n });\n }\n }\n }, [transcriptMap, options.onMessage]);\n\n // Initialize message channel - ensure ports are properly connected\n useEffect(() => {\n const channel = new MessageChannel();\n\n channel.port1.onmessage = (e) => {\n const data = e.data as MessageChannelEvent;\n\n if (data.type === \"state_update\") {\n setState(data.state);\n } else if (data.type === \"transcription_update\") {\n handleTranscriptionUpdate(data.transcriptions);\n } else if (data.type === \"waveform_update\" && data.speaker) {\n // Store the waveform data for the specific speaker\n setWaveformDataMap((prevMap) => ({\n ...prevMap,\n [data.speaker]: data.waveformData,\n }));\n } else if (\n data.type === \"function_calls_collected\" &&\n data.functionCalls\n ) {\n // Handle function calls\n const functionCallsId = `function-calls-${Date.now()}`;\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n newMap.set(functionCallsId, {\n id: functionCallsId,\n name: \"tool\",\n toolCalls: data.functionCalls,\n timestamp: Date.now(),\n isFinal: true,\n });\n return newMap;\n });\n } else if (\n data.type === \"function_calls_finished\" &&\n data.functionResults\n ) {\n // Handle function results\n const functionResultsId = `function-results-${Date.now()}`;\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n newMap.set(functionResultsId, {\n id: functionResultsId,\n name: \"tool\",\n toolCallResults: data.functionResults,\n timestamp: Date.now(),\n isFinal: true,\n });\n return newMap;\n });\n }\n };\n\n // Start the port\n channel.port1.start();\n\n // Store the channel reference\n channelRef.current = channel;\n\n return () => {\n channelRef.current?.port1.close();\n channelRef.current?.port2.close();\n channelRef.current = null;\n };\n }, []);\n\n // Process incoming transcriptions\n const handleTranscriptionUpdate = useCallback(\n (transcriptions: TranscriptionSegment[]) => {\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n\n transcriptions.forEach((t) => {\n const messageType = t.speaker === \"agent\" ? \"agent\" : \"user\";\n // Use existing timestamp if we already have this segment\n const existingTimestamp = prevMap.get(t.id)?.timestamp || t.timestamp;\n\n newMap.set(t.id, {\n id: t.id,\n name: messageType,\n message: t.text,\n timestamp: existingTimestamp,\n isFinal: t.isFinal,\n });\n });\n\n return newMap;\n });\n },\n []\n );\n\n // Set up DOM portal for LiveKit\n useEffect(() => {\n const div = document.createElement(\"div\");\n div.style.display = \"none\";\n document.body.appendChild(div);\n portalRootRef.current = div;\n rootRef.current = createRoot(div);\n\n return () => {\n if (rootRef.current) {\n rootRef.current.unmount();\n }\n if (portalRootRef.current) {\n document.body.removeChild(portalRootRef.current);\n }\n };\n }, []);\n\n // Connect to VoxAI service - updated to include dynamicVariables\n const connect = useCallback(\n async ({ agentId, apiKey, dynamicVariables, metadata }: ConnectParams) => {\n try {\n setState(\"connecting\");\n\n const response = await fetch(HTTPS_API_ORIGIN, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n agent_id: agentId,\n metadata: {\n call_web: {\n dynamic_variables: dynamicVariables || {},\n metadata: metadata || {},\n },\n },\n }),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `Connection failed (${response.status}): ${errorText}`\n );\n }\n\n const data = await response.json();\n setConnectionDetail(data);\n\n if (options.onConnect) {\n options.onConnect();\n }\n } catch (err) {\n // Reset state on error\n setConnectionDetail(null);\n setTranscriptMap(new Map());\n setMessages([]);\n setState(\"disconnected\");\n\n const error = err instanceof Error ? err : new Error(String(err));\n\n if (options.onError) {\n options.onError(error);\n }\n }\n },\n [options]\n );\n\n // Disconnect from VoxAI service\n const disconnect = useCallback(() => {\n setConnectionDetail(null);\n setTranscriptMap(new Map());\n setMessages([]);\n setState(\"disconnected\");\n\n if (options.onDisconnect) {\n options.onDisconnect();\n }\n }, [options]);\n\n // Update the send function with debugging and error checking\n const send = useCallback(\n ({ message, digit }: { message?: string; digit?: number }) => {\n if (state === \"disconnected\") {\n console.warn(\"Cannot send message: Not connected to a conversation\");\n return;\n }\n\n if (message) {\n // Add the message to our local transcript map for immediate feedback\n const messageId = `user-text-${Date.now()}`;\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n newMap.set(messageId, {\n id: messageId,\n name: \"user\",\n message: message,\n timestamp: Date.now(),\n isFinal: true,\n });\n return newMap;\n });\n\n // Send message through the message channel to StateMonitor\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"send_text\",\n text: message,\n });\n } else {\n console.error(\"No message channel available to send message\");\n }\n }\n\n if (digit !== undefined) {\n // Send DTMF through the message channel to StateMonitor\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"send_dtmf\",\n digit: digit,\n });\n } else {\n console.error(\"No message channel available to send DTMF\");\n }\n }\n },\n [state]\n );\n\n // Update the audioWaveform function to return data for the requested speaker\n const audioWaveform = useCallback(\n ({\n speaker = \"agent\",\n barCount = 10,\n updateInterval = 20,\n }: {\n speaker?: \"agent\" | \"user\";\n barCount?: number;\n updateInterval?: number;\n }): number[] => {\n // Store the waveform configuration for StateMonitor to use\n waveformConfigRef.current = { speaker, barCount, updateInterval };\n\n // Send the configuration to StateMonitor if channel is available\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"waveform_config\",\n config: { speaker, barCount, updateInterval },\n });\n }\n\n // Get the waveform data for the requested speaker\n const speakerData = waveformDataMap[speaker] || [];\n\n // Return the current waveform data, or a default array if no data yet\n return speakerData.length > 0\n ? speakerData.slice(0, barCount) // Ensure we return only barCount items\n : Array(barCount).fill(0);\n },\n [waveformDataMap]\n );\n\n // Modify the useEffect hook that renders the LiveKit component\n useEffect(() => {\n if (!rootRef.current) return;\n\n if (connectionDetail) {\n // Only create a new LiveKit component if we don't have one or connection details changed\n if (!livekitComponentRef.current) {\n if (channelRef.current) {\n // Start port2 before passing it to StateMonitor\n channelRef.current.port2.start();\n }\n\n livekitComponentRef.current = (\n <LiveKitRoom\n serverUrl={connectionDetail.serverUrl}\n token={connectionDetail.participantToken}\n audio={true}\n video={false}\n connect={true}\n onDisconnected={disconnect}\n onError={(error) => {\n console.error(\"LiveKit connection error:\", error);\n disconnect();\n if (options.onError) {\n options.onError(\n new Error(`LiveKit connection error: ${error.message}`)\n );\n }\n }}\n >\n <RoomAudioRenderer />\n {channelRef.current && (\n <StateMonitor\n port={channelRef.current.port2}\n initialConfig={\n waveformConfigRef.current || {\n barCount: 10,\n updateInterval: 20,\n }\n }\n />\n )}\n </LiveKitRoom>\n );\n }\n\n rootRef.current.render(livekitComponentRef.current);\n } else {\n livekitComponentRef.current = null;\n rootRef.current.render(<></>);\n }\n }, [connectionDetail, disconnect, options.onError]);\n\n return {\n connect,\n disconnect,\n state,\n messages,\n send,\n audioWaveform,\n };\n}\n\n/**\n * Component that monitors LiveKit state and communicates back to the main hook\n */\nfunction StateMonitor({\n port,\n initialConfig,\n}: {\n port: MessagePort | undefined;\n initialConfig: {\n speaker?: \"agent\" | \"user\";\n barCount: number;\n updateInterval: number;\n };\n}) {\n const { agent, state } = useVoiceAssistant();\n const { send: sendChat } = useChat();\n\n // Initialize waveform config with the passed initial values, defaulting to \"agent\" if not specified\n const [waveformConfig, setWaveformConfig] = useState({\n speaker: initialConfig.speaker || \"agent\",\n barCount: initialConfig.barCount,\n updateInterval: initialConfig.updateInterval,\n });\n\n // Agent transcriptions\n const agentAudioTrack = useParticipantTracks(\n [Track.Source.Microphone],\n agent?.identity\n )[0];\n const agentTranscription = useTrackTranscription(agentAudioTrack);\n\n // Use the current config for the waveform, applying different settings based on speaker\n const agentAudioWaveform = useAudioWaveform(agentAudioTrack, {\n barCount:\n waveformConfig.speaker === \"agent\" ? waveformConfig.barCount : 120, // default if not the selected speaker\n updateInterval:\n waveformConfig.speaker === \"agent\" ? waveformConfig.updateInterval : 20,\n });\n\n // User transcriptions\n const localParticipant = useLocalParticipant();\n const localMessages = useTrackTranscription({\n publication: localParticipant.microphoneTrack,\n source: Track.Source.Microphone,\n participant: localParticipant.localParticipant,\n });\n const localAudioTrack = useParticipantTracks(\n [Track.Source.Microphone],\n localParticipant.localParticipant.identity\n )[0];\n const userAudioWaveform = useAudioWaveform(localAudioTrack, {\n barCount: waveformConfig.speaker === \"user\" ? waveformConfig.barCount : 120, // default if not the selected speaker\n updateInterval:\n waveformConfig.speaker === \"user\" ? waveformConfig.updateInterval : 20,\n });\n\n // Add separate effects to send agent and user waveform data\n useEffect(() => {\n if (!port || !agentAudioWaveform || !agentAudioWaveform.bars) return;\n\n // Send the agent waveform data\n port.postMessage({\n type: \"waveform_update\",\n waveformData: agentAudioWaveform.bars,\n speaker: \"agent\",\n });\n }, [port, agentAudioWaveform]);\n\n useEffect(() => {\n if (!port || !userAudioWaveform || !userAudioWaveform.bars) return;\n\n // Send the user waveform data\n port.postMessage({\n type: \"waveform_update\",\n waveformData: userAudioWaveform.bars,\n speaker: \"user\",\n });\n }, [port, userAudioWaveform]);\n\n // Listen for messages including config updates\n useEffect(() => {\n if (!port) return;\n\n const handleMessage = (event: MessageEvent) => {\n const data = event.data;\n\n if (data.type === \"waveform_config\" && data.config) {\n // Verify we have both required properties before updating\n if (\n typeof data.config.barCount === \"number\" &&\n typeof data.config.updateInterval === \"number\"\n ) {\n setWaveformConfig(data.config);\n }\n } else if (data.type === \"send_text\") {\n if (sendChat) {\n sendChat(data.text);\n } else {\n console.error(\"sendChat function is not available\");\n }\n } else if (data.type === \"send_dtmf\") {\n if (localParticipant.localParticipant) {\n // Use standard DTMF code (RFC 4733)\n const standardDtmfCode = 101; // Standard DTMF payload type\n localParticipant.localParticipant.publishDtmf(\n standardDtmfCode,\n data.digit\n );\n } else {\n console.error(\"Local participant is not available for DTMF\");\n }\n }\n };\n\n // Make sure we start the port\n port.start();\n\n port.addEventListener(\"message\", handleMessage);\n\n return () => {\n port.removeEventListener(\"message\", handleMessage);\n };\n }, [port, sendChat, localParticipant]);\n\n // Send agent state updates\n useEffect(() => {\n if (port) {\n port.postMessage({ type: \"state_update\", state });\n }\n }, [state, port]);\n\n // Send agent transcriptions\n useEffect(() => {\n if (port && agentTranscription.segments.length > 0) {\n const transcriptions = agentTranscription.segments.map((segment) => ({\n id: segment.id,\n text: segment.text,\n isFinal: segment.final,\n timestamp: Date.now(),\n speaker: \"agent\" as const,\n }));\n\n port.postMessage({\n type: \"transcription_update\",\n transcriptions,\n });\n }\n }, [agentTranscription.segments, port]);\n\n // Send user transcriptions\n useEffect(() => {\n if (port && localMessages.segments.length > 0) {\n const transcriptions = localMessages.segments.map((segment) => ({\n id: segment.id,\n text: segment.text,\n isFinal: segment.final,\n timestamp: Date.now(),\n speaker: \"user\" as const,\n }));\n\n port.postMessage({\n type: \"transcription_update\",\n transcriptions,\n });\n }\n }, [localMessages.segments, port]);\n\n // Add data channel hook for function calls\n const { message: functionCallsCollected } = useDataChannel(\n \"function_calls_collected\",\n (msg) => {\n if (!port) return;\n\n const textDecoder = new TextDecoder();\n const messageString =\n msg.payload instanceof Uint8Array\n ? textDecoder.decode(msg.payload)\n : String(msg.payload);\n\n let functionCallInfo: FunctionCallInfo[];\n try {\n functionCallInfo = JSON.parse(messageString);\n\n // Send function calls to main hook via the port\n port.postMessage({\n type: \"function_calls_collected\",\n functionCalls: functionCallInfo,\n });\n } catch (e) {\n console.error(\"Failed to parse function call log:\", e);\n }\n }\n );\n\n // Add data channel hook for function call results\n const { message: functionCallsFinished } = useDataChannel(\n \"function_calls_finished\",\n (msg) => {\n if (!port) return;\n\n const textDecoder = new TextDecoder();\n const messageString =\n msg.payload instanceof Uint8Array\n ? textDecoder.decode(msg.payload)\n : String(msg.payload);\n\n let functionCallResult: FunctionCallResult[];\n try {\n functionCallResult = JSON.parse(messageString);\n\n // Send function results to main hook via the port\n port.postMessage({\n type: \"function_calls_finished\",\n functionResults: functionCallResult,\n });\n } catch (e) {\n console.error(\"Failed to parse function call result:\", e);\n }\n }\n );\n\n return null;\n}\n"],"names":["useVoxAI","options","connectionDetail","setConnectionDetail","useState","state","setState","transcriptMap","setTranscriptMap","Map","messages","setMessages","prevMessagesRef","useRef","processedMessageIdsRef","Set","portalRootRef","rootRef","channelRef","livekitComponentRef","waveformDataMap","setWaveformDataMap","agent","user","waveformConfigRef","useEffect","allMessages","Array","from","values","sort","a","b","timestamp","messagesString","JSON","stringify","current","onMessage","filter","msg","isFinal","id","has","forEach","add","channel","MessageChannel","port1","onmessage","e","data","type","handleTranscriptionUpdate","transcriptions","speaker","prevMap","waveformData","functionCalls","functionCallsId","Date","now","newMap","set","name","toolCalls","functionResults","functionResultsId","toolCallResults","start","_channelRef$current","_channelRef$current2","close","port2","useCallback","t","_prevMap$get","messageType","existingTimestamp","get","message","text","div","document","createElement","style","display","body","appendChild","createRoot","unmount","removeChild","connect","_ref","agentId","apiKey","dynamicVariables","metadata","Promise","resolve","fetch","method","headers","Authorization","agent_id","call_web","dynamic_variables","then","response","_temp2","_result2","json","onConnect","_temp","ok","errorText","Error","status","_catch","err","error","String","onError","reject","disconnect","onDisconnect","send","_ref2","digit","messageId","postMessage","console","undefined","warn","audioWaveform","_ref3","barCount","updateInterval","config","speakerData","length","slice","fill","_jsxs","LiveKitRoom","serverUrl","token","participantToken","audio","video","onDisconnected","children","_jsx","RoomAudioRenderer","StateMonitor","port","initialConfig","render","_Fragment","_ref4","useVoiceAssistant","sendChat","useChat","waveformConfig","setWaveformConfig","agentAudioTrack","useParticipantTracks","Track","Source","Microphone","identity","agentTranscription","useTrackTranscription","agentAudioWaveform","useAudioWaveform","localParticipant","useLocalParticipant","localMessages","publication","microphoneTrack","source","participant","localAudioTrack","userAudioWaveform","bars","handleMessage","event","publishDtmf","addEventListener","removeEventListener","segments","map","segment","final","useDataChannel","textDecoder","TextDecoder","messageString","payload","Uint8Array","decode","functionCallInfo","parse","functionCallResult"],"mappings":"6cA2IgB,SAAAA,EAASC,QAAA,IAAAA,IAAAA,EAAwB,CAAA,GAE/C,MAAOC,EAAkBC,GACvBC,EAAqC,OAChCC,EAAOC,GAAYF,EAAwB,iBAG3CG,EAAeC,GAAoBJ,EACxC,IAAIK,MAECC,EAAUC,GAAeP,EAAuB,IACjDQ,EAAkBC,EAAe,IAGjCC,EAAyBD,EAAoB,IAAIE,KAGjDC,EAAgBH,EAA8B,MAC9CI,EAAUJ,EAAoB,MAG9BK,EAAaL,EAA8B,MAG3CM,EAAsBN,EAAwB,OAG7CO,EAAiBC,GAAsBjB,EAE5C,CACAkB,MAAO,GACPC,KAAM,KAIFC,EAAoBX,EAIhB,MAGVY,EAAU,KACR,MAAMC,EAAcC,MAAMC,KAAKrB,EAAcsB,UAAUC,KACrD,CAACC,EAAGC,IAAMD,EAAEE,UAAYD,EAAEC,WAItBC,EAAiBC,KAAKC,UAAUV,GAClCQ,IAAmBtB,EAAgByB,UACrCzB,EAAgByB,QAAUH,EAC1BvB,EAAYe,GAGRzB,EAAQqC,WACVZ,EACGa,OACEC,GACCA,EAAIC,SACJD,EAAIE,KACH5B,EAAuBuB,QAAQM,IAAIH,EAAIE,KAE3CE,QAASJ,IACJA,EAAIE,KAEN5B,EAAuBuB,QAAQQ,IAAIL,EAAIE,UAEvCzC,EAAQqC,WAARrC,EAAQqC,UAAYE,GACtB,GAGR,EACC,CAACjC,EAAeN,EAAQqC,YAG3Bb,EAAU,KACR,MAAMqB,EAAU,IAAIC,eA0DpB,OAxDAD,EAAQE,MAAMC,UAAaC,IACzB,MAAMC,EAAOD,EAAEC,KAEf,GAAkB,iBAAdA,EAAKC,KACP9C,EAAS6C,EAAK9C,YACT,GAAkB,yBAAd8C,EAAKC,KACdC,EAA0BF,EAAKG,wBACR,oBAAdH,EAAKC,MAA8BD,EAAKI,QAEjDlC,EAAoBmC,IAAO,IACtBA,EACH,CAACL,EAAKI,SAAUJ,EAAKM,qBAElB,GACS,6BAAdN,EAAKC,MACLD,EAAKO,cACL,CAEA,MAAMC,EAAoCC,kBAAAA,KAAKC,MAC/CrD,EAAkBgD,IAChB,MAAMM,EAAS,IAAIrD,IAAI+C,GAQvB,OAPAM,EAAOC,IAAIJ,EAAiB,CAC1BjB,GAAIiB,EACJK,KAAM,OACNC,UAAWd,EAAKO,cAChBzB,UAAW2B,KAAKC,MAChBpB,SAAS,IAEJqB,GAEX,SACgB,4BAAdX,EAAKC,MACLD,EAAKe,gBACL,CAEA,MAAMC,EAAiB,oBAAuBP,KAAKC,MACnDrD,EAAkBgD,IAChB,MAAMM,EAAS,IAAIrD,IAAI+C,GAQvB,OAPAM,EAAOC,IAAII,EAAmB,CAC5BzB,GAAIyB,EACJH,KAAM,OACNI,gBAAiBjB,EAAKe,gBACtBjC,UAAW2B,KAAKC,MAChBpB,SAAS,IAEJqB,GAEX,GAIFhB,EAAQE,MAAMqB,QAGdnD,EAAWmB,QAAUS,EAEd,KAAK,IAAAwB,EAAAC,EACQ,OAAlBD,EAAApD,EAAWmB,UAAXiC,EAAoBtB,MAAMwB,QAC1BD,OAAAA,EAAArD,EAAWmB,UAAXkC,EAAoBE,MAAMD,QAC1BtD,EAAWmB,QAAU,IAAA,CACvB,EACC,IAGH,MAAMgB,EAA4BqB,EAC/BpB,IACC9C,EAAkBgD,IAChB,MAAMM,EAAS,IAAIrD,IAAI+C,GAgBvB,OAdAF,EAAeV,QAAS+B,QAAKC,EAC3B,MAAMC,EAA4B,UAAdF,EAAEpB,QAAsB,QAAU,OAEhDuB,UAAoBF,EAAApB,EAAQuB,IAAIJ,EAAEjC,YAAdkC,EAAmB3C,YAAa0C,EAAE1C,UAE5D6B,EAAOC,IAAIY,EAAEjC,GAAI,CACfA,GAAIiC,EAAEjC,GACNsB,KAAMa,EACNG,QAASL,EAAEM,KACXhD,UAAW6C,EACXrC,QAASkC,EAAElC,SAEf,GAEOqB,GAEX,EACA,IAIFrC,EAAU,KACR,MAAMyD,EAAMC,SAASC,cAAc,OAMnC,OALAF,EAAIG,MAAMC,QAAU,OACpBH,SAASI,KAAKC,YAAYN,GAC1BlE,EAAcqB,QAAU6C,EACxBjE,EAAQoB,QAAUoD,EAAWP,GAEtB,KACDjE,EAAQoB,SACVpB,EAAQoB,QAAQqD,UAEd1E,EAAcqB,SAChB8C,SAASI,KAAKI,YAAY3E,EAAcqB,QAC1C,CACF,EACC,IAGH,MAAMuD,EAAUlB,EAAWmB,SAAAA,OAClBC,QAAEA,EAAOC,OAAEA,EAAMC,iBAAEA,EAAgBC,SAAEA,GAAyBJ,EAAA,WAAIK,QAAAC,iCAErE7F,EAAS,cAAc4F,QAAAC,QAEAC,MAvSN,sCAuS8B,CAC7CC,OAAQ,OACRC,QAAS,CACPC,cAAa,UAAYR,EACzB,eAAgB,oBAElBR,KAAMpD,KAAKC,UAAU,CACnBoE,SAAUV,EACVG,SAAU,CACRQ,SAAU,CACRC,kBAAmBV,GAAoB,CAAE,EACzCC,SAAUA,GAAY,CAAA,SAI5BU,cAfIC,GAAQ,SAAAC,EAAAC,GAAA,OAAAZ,QAAAC,QAwBKS,EAASG,QAAMJ,KAAA,SAA5BxD,GACNhD,EAAoBgD,GAEhBlD,EAAQ+G,WACV/G,EAAQ+G,WAAY,EAAA,CAAA,MAAAC,EAXlB,WAAA,IAACL,EAASM,GAAEhB,OAAAA,QAAAC,QACUS,EAAS3B,QAAM0B,KAAA,SAAjCQ,GACN,MAAU,IAAAC,MAAK,sBACSR,EAASS,aAAYF,EAC3C,GAJA,UAIAF,GAAAA,EAAAN,KAAAM,EAAAN,KAAAE,GAAAA,GASN,6DAlCuES,CACnE,WAiCKC,GAEPpH,EAAoB,MACpBK,EAAiB,IAAIC,KACrBE,EAAY,IACZL,EAAS,gBAET,MAAMkH,EAAQD,aAAeH,MAAQG,EAAM,IAAIH,MAAMK,OAAOF,IAExDtH,EAAQyH,SACVzH,EAAQyH,QAAQF,EAEpB,GACF,CAAC,MAAAtE,GAAAgD,OAAAA,QAAAyB,OAAAzE,EAAA,CAAA,EACD,CAACjD,IAIG2H,EAAalD,EAAY,KAC7BvE,EAAoB,MACpBK,EAAiB,IAAIC,KACrBE,EAAY,IACZL,EAAS,gBAELL,EAAQ4H,cACV5H,EAAQ4H,cACV,EACC,CAAC5H,IAGE6H,EAAOpD,EACXqD,IAA6D,IAA5D/C,QAAEA,EAAOgD,MAAEA,GAA6CD,EACvD,GAAc,iBAAV1H,EAAJ,CAKA,GAAI2E,EAAS,CAEX,MAAMiD,EAAS,aAAgBrE,KAAKC,MACpCrD,EAAkBgD,IAChB,MAAMM,EAAS,IAAIrD,IAAI+C,GAQvB,OAPAM,EAAOC,IAAIkE,EAAW,CACpBvF,GAAIuF,EACJjE,KAAM,OACNgB,QAASA,EACT/C,UAAW2B,KAAKC,MAChBpB,SAAS,IAEJqB,IAIL5C,EAAWmB,QACbnB,EAAWmB,QAAQW,MAAMkF,YAAY,CACnC9E,KAAM,YACN6B,KAAMD,IAGRmD,QAAQX,MAAM,+CAElB,MAEcY,IAAVJ,IAEE9G,EAAWmB,QACbnB,EAAWmB,QAAQW,MAAMkF,YAAY,CACnC9E,KAAM,YACN4E,MAAOA,IAGTG,QAAQX,MAAM,6CApClB,MAFEW,QAAQE,KAAK,uDAwCf,EAEF,CAAChI,IAIGiI,EAAgB5D,EACpB6D,IAAC,IAAAhF,QACCA,EAAU,QAAOiF,SACjBA,EAAW,GAAEC,eACbA,EAAiB,IAKlBF,EAEC/G,EAAkBa,QAAU,CAAEkB,UAASiF,WAAUC,kBAG7CvH,EAAWmB,SACbnB,EAAWmB,QAAQW,MAAMkF,YAAY,CACnC9E,KAAM,kBACNsF,OAAQ,CAAEnF,UAASiF,WAAUC,oBAKjC,MAAME,EAAcvH,EAAgBmC,IAAY,GAGhD,OAAOoF,EAAYC,OAAS,EACxBD,EAAYE,MAAM,EAAGL,GACrB7G,MAAM6G,GAAUM,KAAK,EAAC,EAE5B,CAAC1H,IAwDH,OApDAK,EAAU,KACHR,EAAQoB,UAETnC,GAEGiB,EAAoBkB,UACnBnB,EAAWmB,SAEbnB,EAAWmB,QAAQoC,MAAMJ,QAG3BlD,EAAoBkB,QAClB0G,EAACC,GACCC,UAAW/I,EAAiB+I,UAC5BC,MAAOhJ,EAAiBiJ,iBACxBC,OAAO,EACPC,OAAO,EACPzD,SAAS,EACT0D,eAAgB1B,EAChBF,QAAUF,IACRW,QAAQX,MAAM,4BAA6BA,GAC3CI,IACI3H,EAAQyH,SACVzH,EAAQyH,QACN,IAAIN,MAAmCI,6BAAAA,EAAMxC,SAEjD,EACDuE,SAAA,CAEDC,EAACC,EAAoB,CAAA,GACpBvI,EAAWmB,SACVmH,EAACE,EAAY,CACXC,KAAMzI,EAAWmB,QAAQoC,MACzBmF,cACEpI,EAAkBa,SAAW,CAC3BmG,SAAU,GACVC,eAAgB,UAS9BxH,EAAQoB,QAAQwH,OAAO1I,EAAoBkB,WAE3ClB,EAAoBkB,QAAU,KAC9BpB,EAAQoB,QAAQwH,OAAOL,EAAAM,EAAA,CAAA,KACzB,EACC,CAAC5J,EAAkB0H,EAAY3H,EAAQyH,UAEnC,CACL9B,UACAgC,aACAvH,QACAK,WACAoH,OACAQ,gBAEJ,CAKA,SAASoB,EAAYK,OAACJ,KACpBA,EAAIC,cACJA,GAQDG,EACC,MAAMzI,MAAEA,EAAKjB,MAAEA,GAAU2J,KACjBlC,KAAMmC,GAAaC,KAGpBC,EAAgBC,GAAqBhK,EAAS,CACnDmD,QAASqG,EAAcrG,SAAW,QAClCiF,SAAUoB,EAAcpB,SACxBC,eAAgBmB,EAAcnB,iBAI1B4B,EAAkBC,EACtB,CAACC,EAAMC,OAAOC,YACT,MAALnJ,OAAK,EAALA,EAAOoJ,UACP,GACIC,EAAqBC,EAAsBP,GAG3CQ,EAAqBC,EAAiBT,EAAiB,CAC3D7B,SAC6B,UAA3B2B,EAAe5G,QAAsB4G,EAAe3B,SAAW,IACjEC,eAC6B,UAA3B0B,EAAe5G,QAAsB4G,EAAe1B,eAAiB,KAInEsC,EAAmBC,IACnBC,EAAgBL,EAAsB,CAC1CM,YAAaH,EAAiBI,gBAC9BC,OAAQb,EAAMC,OAAOC,WACrBY,YAAaN,EAAiBA,mBAE1BO,EAAkBhB,EACtB,CAACC,EAAMC,OAAOC,YACdM,EAAiBA,iBAAiBL,UAClC,GACIa,EAAoBT,EAAiBQ,EAAiB,CAC1D9C,SAAqC,SAA3B2B,EAAe5G,QAAqB4G,EAAe3B,SAAW,IACxEC,eAC6B,SAA3B0B,EAAe5G,QAAqB4G,EAAe1B,eAAiB,KAwKxE,OApKAhH,EAAU,KACHkI,GAASkB,GAAuBA,EAAmBW,MAGxD7B,EAAKzB,YAAY,CACf9E,KAAM,kBACNK,aAAcoH,EAAmBW,KACjCjI,QAAS,SAEb,EAAG,CAACoG,EAAMkB,IAEVpJ,EAAU,KACHkI,GAAS4B,GAAsBA,EAAkBC,MAGtD7B,EAAKzB,YAAY,CACf9E,KAAM,kBACNK,aAAc8H,EAAkBC,KAChCjI,QAAS,QAEb,EAAG,CAACoG,EAAM4B,IAGV9J,EAAU,KACR,IAAKkI,EAAM,OAEX,MAAM8B,EAAiBC,IACrB,MAAMvI,EAAOuI,EAAMvI,KAED,oBAAdA,EAAKC,MAA8BD,EAAKuF,OAGR,iBAAzBvF,EAAKuF,OAAOF,UACmB,iBAA/BrF,EAAKuF,OAAOD,gBAEnB2B,EAAkBjH,EAAKuF,QAEF,cAAdvF,EAAKC,KACV6G,EACFA,EAAS9G,EAAK8B,MAEdkD,QAAQX,MAAM,sCAEO,cAAdrE,EAAKC,OACV2H,EAAiBA,iBAGnBA,EAAiBA,iBAAiBY,YADT,IAGvBxI,EAAK6E,OAGPG,QAAQX,MAAM,+CAElB,EAQF,OAJAmC,EAAKtF,QAELsF,EAAKiC,iBAAiB,UAAWH,GAE1B,KACL9B,EAAKkC,oBAAoB,UAAWJ,EAAa,CACnD,EACC,CAAC9B,EAAMM,EAAUc,IAGpBtJ,EAAU,KACJkI,GACFA,EAAKzB,YAAY,CAAE9E,KAAM,eAAgB/C,SAC3C,EACC,CAACA,EAAOsJ,IAGXlI,EAAU,KACR,GAAIkI,GAAQgB,EAAmBmB,SAASlD,OAAS,EAAG,CAClD,MAAMtF,EAAiBqH,EAAmBmB,SAASC,IAAKC,IAAO,CAC7DtJ,GAAIsJ,EAAQtJ,GACZuC,KAAM+G,EAAQ/G,KACdxC,QAASuJ,EAAQC,MACjBhK,UAAW2B,KAAKC,MAChBN,QAAS,WAGXoG,EAAKzB,YAAY,CACf9E,KAAM,uBACNE,kBAEJ,GACC,CAACqH,EAAmBmB,SAAUnC,IAGjClI,EAAU,KACR,GAAIkI,GAAQsB,EAAca,SAASlD,OAAS,EAAG,CAC7C,MAAMtF,EAAiB2H,EAAca,SAASC,IAAKC,IAAO,CACxDtJ,GAAIsJ,EAAQtJ,GACZuC,KAAM+G,EAAQ/G,KACdxC,QAASuJ,EAAQC,MACjBhK,UAAW2B,KAAKC,MAChBN,QAAS,UAGXoG,EAAKzB,YAAY,CACf9E,KAAM,uBACNE,kBAEJ,GACC,CAAC2H,EAAca,SAAUnC,IAGgBuC,EAC1C,2BACC1J,IACC,IAAKmH,EAAM,OAEX,MAAMwC,EAAc,IAAIC,YAClBC,EACJ7J,EAAI8J,mBAAmBC,WACnBJ,EAAYK,OAAOhK,EAAI8J,SACvB7E,OAAOjF,EAAI8J,SAEjB,IAAIG,EACJ,IACEA,EAAmBtK,KAAKuK,MAAML,GAG9B1C,EAAKzB,YAAY,CACf9E,KAAM,2BACNM,cAAe+I,GAEnB,CAAE,MAAOvJ,GACPiF,QAAQX,MAAM,qCAAsCtE,EACtD,IAKuCgJ,EACzC,0BACC1J,IACC,IAAKmH,EAAM,OAEX,MAAMwC,EAAc,IAAIC,YAClBC,EACJ7J,EAAI8J,mBAAmBC,WACnBJ,EAAYK,OAAOhK,EAAI8J,SACvB7E,OAAOjF,EAAI8J,SAEjB,IAAIK,EACJ,IACEA,EAAqBxK,KAAKuK,MAAML,GAGhC1C,EAAKzB,YAAY,CACf9E,KAAM,0BACNc,gBAAiByI,GAErB,CAAE,MAAOzJ,GACPiF,QAAQX,MAAM,wCAAyCtE,EACzD,IAKN,IAAA"}
1
+ {"version":3,"file":"lib.module.js","sources":["../src/hooks/useVoxAI.tsx"],"sourcesContent":["import {\n LiveKitRoom,\n RoomAudioRenderer,\n useLocalParticipant,\n useParticipantTracks,\n useTrackTranscription,\n useVoiceAssistant,\n useChat,\n useAudioWaveform,\n useDataChannel,\n useTrackVolume,\n} from \"@livekit/components-react\";\nimport { KrispNoiseFilter } from \"@livekit/krisp-noise-filter\";\nimport { LocalAudioTrack, Track } from \"livekit-client\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { createRoot, Root } from \"react-dom/client\";\n\ntype VoxConnectionDetail = {\n serverUrl: string;\n roomName: string;\n participantName: string;\n participantToken: string;\n};\n\n/**\n * VoxAgentState\n * @description The state of the agent\n */\nexport type VoxAgentState =\n | \"disconnected\"\n | \"connecting\"\n | \"initializing\"\n | \"listening\"\n | \"thinking\"\n | \"speaking\";\n\n// API endpoint\nconst HTTPS_API_ORIGIN = \"https://www.tryvox.co/api/agent/sdk\";\n// const HTTPS_API_ORIGIN = \"http://localhost:3000/api/agent/sdk\";\n\n/**\n * Function call related types\n */\nexport interface FunctionCallInfo {\n tool_call_id: string;\n function_info: {\n name: string;\n description: string;\n arguments: Record<string, any>;\n };\n raw_arguments: string;\n arguments: Record<string, any>;\n}\n\nexport interface FunctionCallResult {\n call_info: {\n tool_call_id: string;\n function_info: {\n name: string;\n description: string;\n arguments: Record<string, any>;\n };\n raw_arguments: string;\n arguments: Record<string, any>;\n };\n result: Record<string, any> | null;\n exception: string | null;\n}\n\n/**\n * VoxMessage\n * @description The message type between the agent and the user\n */\nexport type VoxMessage = {\n id?: string;\n name: \"agent\" | \"user\" | \"tool\";\n message?: string;\n timestamp: number;\n isFinal?: boolean;\n toolCalls?: FunctionCallInfo[];\n toolCallResults?: FunctionCallResult[];\n};\n\n/**\n * VoxAIOptions\n * @description The callback functions for the useVoxAI hook\n */\nexport interface VoxAIOptions {\n onConnect?: () => void;\n onDisconnect?: () => void;\n onError?: (error: Error) => void;\n onMessage?: (message: VoxMessage) => void;\n enableNoiseCancellation?: boolean;\n}\n\n// Message channel event types\ntype MessageChannelEvent =\n | { type: \"state_update\"; state: VoxAgentState }\n | { type: \"transcription_update\"; transcriptions: TranscriptionSegment[] }\n | {\n type: \"waveform_update\";\n waveformData: number[];\n speaker: \"agent\" | \"user\";\n }\n | { type: \"function_calls_collected\"; functionCalls: FunctionCallInfo[] }\n | { type: \"function_calls_finished\"; functionResults: FunctionCallResult[] };\n\ntype TranscriptionSegment = {\n id: string;\n text: string;\n isFinal: boolean;\n timestamp: number;\n speaker: \"agent\" | \"user\";\n};\n\n/**\n * ConnectParams\n * @param agentId - The agent ID\n * @param apiKey - The API key\n * @param dynamicVariables - The dynamic variables\n * @param metadata - 이 메타데이터는 아웃바운드 웹훅, 통화 기록에 포함됩니다.\n */\nexport interface ConnectParams {\n agentId: string;\n apiKey: string;\n dynamicVariables?: Record<string, any>;\n metadata?: Record<string, any>;\n}\n\n/**\n * useVoxAI\n * @description The hook for integrating with vox.ai voice assistant\n * @param options - The options for the useVoxAI hook\n * @returns The useVoxAI hook\n * @example\n * const { connect, disconnect, state, messages, send } = useVoxAI({\n * onConnect: () => console.log(\"Connected\"),\n * onDisconnect: () => console.log(\"Disconnected\"),\n * onError: (error) => console.error(\"Error:\", error),\n * onMessage: (message) => console.log(\"Message:\", message),\n * });\n */\nexport function useVoxAI(options: VoxAIOptions = {}) {\n // Connection state\n const [connectionDetail, setConnectionDetail] =\n useState<VoxConnectionDetail | null>(null);\n const [state, setState] = useState<VoxAgentState>(\"disconnected\");\n\n // Message handling\n const [transcriptMap, setTranscriptMap] = useState<Map<string, VoxMessage>>(\n new Map()\n );\n const [messages, setMessages] = useState<VoxMessage[]>([]);\n const prevMessagesRef = useRef<string>(\"\");\n\n // Track which messages we've already sent to the onMessage callback\n const processedMessageIdsRef = useRef<Set<string>>(new Set());\n\n // DOM manipulation for LiveKit portal\n const portalRootRef = useRef<HTMLDivElement | null>(null);\n const rootRef = useRef<Root | null>(null);\n\n // Communication channel\n const channelRef = useRef<MessageChannel | null>(null);\n\n // Add this near the start of your useVoxAI hook\n const livekitComponentRef = useRef<React.ReactNode>(null);\n\n // Replace the single waveform state with a map for multiple speakers\n const [waveformDataMap, setWaveformDataMap] = useState<\n Record<string, number[]>\n >({\n agent: [],\n user: [],\n });\n\n // Add back the waveform config reference\n const waveformConfigRef = useRef<{\n speaker?: \"agent\" | \"user\";\n barCount: number;\n updateInterval: number;\n } | null>(null);\n\n // Add a new state to track microphone status\n const [isMicEnabled, setIsMicEnabled] = useState<boolean>(true);\n\n // Update messages whenever transcriptMap changes\n useEffect(() => {\n const allMessages = Array.from(transcriptMap.values()).sort(\n (a, b) => a.timestamp - b.timestamp\n );\n\n // Only update if the messages have actually changed\n const messagesString = JSON.stringify(allMessages);\n if (messagesString !== prevMessagesRef.current) {\n prevMessagesRef.current = messagesString;\n setMessages(allMessages);\n\n // Only trigger onMessage for new final messages that haven't been processed yet\n if (options.onMessage) {\n allMessages\n .filter(\n (msg) =>\n msg.isFinal &&\n msg.id &&\n !processedMessageIdsRef.current.has(msg.id)\n )\n .forEach((msg) => {\n if (msg.id) {\n // Mark this message as processed\n processedMessageIdsRef.current.add(msg.id);\n // Call the callback\n options.onMessage?.(msg);\n }\n });\n }\n }\n }, [transcriptMap, options.onMessage]);\n\n // Initialize message channel - ensure ports are properly connected\n useEffect(() => {\n const channel = new MessageChannel();\n\n channel.port1.onmessage = (e) => {\n const data = e.data as MessageChannelEvent;\n\n if (data.type === \"state_update\") {\n setState(data.state);\n } else if (data.type === \"transcription_update\") {\n handleTranscriptionUpdate(data.transcriptions);\n } else if (data.type === \"waveform_update\" && data.speaker) {\n // Store the waveform data for the specific speaker\n setWaveformDataMap((prevMap) => ({\n ...prevMap,\n [data.speaker]: data.waveformData,\n }));\n } else if (\n data.type === \"function_calls_collected\" &&\n data.functionCalls\n ) {\n // Handle function calls\n const functionCallsId = `function-calls-${Date.now()}`;\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n newMap.set(functionCallsId, {\n id: functionCallsId,\n name: \"tool\",\n toolCalls: data.functionCalls,\n timestamp: Date.now(),\n isFinal: true,\n });\n return newMap;\n });\n } else if (\n data.type === \"function_calls_finished\" &&\n data.functionResults\n ) {\n // Handle function results\n const functionResultsId = `function-results-${Date.now()}`;\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n newMap.set(functionResultsId, {\n id: functionResultsId,\n name: \"tool\",\n toolCallResults: data.functionResults,\n timestamp: Date.now(),\n isFinal: true,\n });\n return newMap;\n });\n }\n };\n\n // Start the port\n channel.port1.start();\n\n // Store the channel reference\n channelRef.current = channel;\n\n return () => {\n channelRef.current?.port1.close();\n channelRef.current?.port2.close();\n channelRef.current = null;\n };\n }, []);\n\n // Process incoming transcriptions\n const handleTranscriptionUpdate = useCallback(\n (transcriptions: TranscriptionSegment[]) => {\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n\n transcriptions.forEach((t) => {\n const messageType = t.speaker === \"agent\" ? \"agent\" : \"user\";\n // Use existing timestamp if we already have this segment\n const existingTimestamp = prevMap.get(t.id)?.timestamp || t.timestamp;\n\n newMap.set(t.id, {\n id: t.id,\n name: messageType,\n message: t.text,\n timestamp: existingTimestamp,\n isFinal: t.isFinal,\n });\n });\n\n return newMap;\n });\n },\n []\n );\n\n // Set up DOM portal for LiveKit\n useEffect(() => {\n const div = document.createElement(\"div\");\n div.style.display = \"none\";\n document.body.appendChild(div);\n portalRootRef.current = div;\n rootRef.current = createRoot(div);\n\n return () => {\n if (rootRef.current) {\n rootRef.current.unmount();\n }\n if (portalRootRef.current) {\n document.body.removeChild(portalRootRef.current);\n }\n };\n }, []);\n\n // Connect to VoxAI service - updated to include dynamicVariables\n /**\n * Initiates a connection to the VoxAI service\n * @param {ConnectParams} options - Connection parameters\n * @returns {Promise} - Resolves when the connection is successful, rejects if:\n * 1. The connection is already in progress (state is not \"disconnected\")\n * 2. The server returns an error\n * 3. Any other error occurs during the connection process\n */\n const connect = useCallback(\n async ({ agentId, apiKey, dynamicVariables, metadata }: ConnectParams) => {\n try {\n // Prevent connecting if already in a connection state\n if (state !== \"disconnected\") {\n const errorMessage = `Connection attempt rejected: Already in a connection state (${state})`;\n console.warn(errorMessage);\n\n // Call the onError callback if provided\n if (options.onError) {\n options.onError(new Error(errorMessage));\n }\n\n // Return a rejected promise\n return Promise.reject(new Error(errorMessage));\n }\n\n setState(\"connecting\");\n\n const response = await fetch(HTTPS_API_ORIGIN, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n agent_id: agentId,\n metadata: {\n call_web: {\n dynamic_variables: dynamicVariables || {},\n metadata: metadata || {},\n },\n },\n }),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `Connection failed (${response.status}): ${errorText}`\n );\n }\n\n const data = await response.json();\n setConnectionDetail(data);\n\n if (options.onConnect) {\n options.onConnect();\n }\n } catch (err) {\n // Reset state on error\n setConnectionDetail(null);\n setTranscriptMap(new Map());\n setMessages([]);\n setState(\"disconnected\");\n\n const error = err instanceof Error ? err : new Error(String(err));\n\n if (options.onError) {\n options.onError(error);\n }\n }\n },\n [options]\n );\n\n // Disconnect from VoxAI service\n const disconnect = useCallback(() => {\n setConnectionDetail(null);\n setTranscriptMap(new Map());\n setMessages([]);\n setState(\"disconnected\");\n\n if (options.onDisconnect) {\n options.onDisconnect();\n }\n }, [options]);\n\n // Update the send function with debugging and error checking\n const send = useCallback(\n ({ message, digit }: { message?: string; digit?: number }) => {\n if (state === \"disconnected\") {\n console.warn(\"Cannot send message: Not connected to a conversation\");\n return;\n }\n\n if (message) {\n // Add the message to our local transcript map for immediate feedback\n const messageId = `user-text-${Date.now()}`;\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n newMap.set(messageId, {\n id: messageId,\n name: \"user\",\n message: message,\n timestamp: Date.now(),\n isFinal: true,\n });\n return newMap;\n });\n\n // Send message through the message channel to StateMonitor\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"send_text\",\n text: message,\n });\n } else {\n console.error(\"No message channel available to send message\");\n }\n }\n\n if (digit !== undefined) {\n // Send DTMF through the message channel to StateMonitor\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"send_dtmf\",\n digit: digit,\n });\n } else {\n console.error(\"No message channel available to send DTMF\");\n }\n }\n },\n [state]\n );\n\n // Update the audioWaveform function to return data for the requested speaker\n const audioWaveform = useCallback(\n ({\n speaker = \"agent\",\n barCount = 10,\n updateInterval = 20,\n }: {\n speaker?: \"agent\" | \"user\";\n barCount?: number;\n updateInterval?: number;\n }): number[] => {\n // Store the waveform configuration for StateMonitor to use\n waveformConfigRef.current = { speaker, barCount, updateInterval };\n\n // Send the configuration to StateMonitor if channel is available\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"waveform_config\",\n config: { speaker, barCount, updateInterval },\n });\n }\n\n // Get the waveform data for the requested speaker\n const speakerData = waveformDataMap[speaker] || [];\n\n // Return the current waveform data, or a default array if no data yet\n return speakerData.length > 0\n ? speakerData.slice(0, barCount) // Ensure we return only barCount items\n : Array(barCount).fill(0);\n },\n [waveformDataMap]\n );\n\n // Add toggleMic function that will be exposed in the hook's return value\n const toggleMic = useCallback(\n (value: boolean) => {\n setIsMicEnabled(value);\n\n // Send the command to the StateMonitor through the message channel\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"toggle_mic\",\n enabled: value,\n });\n } else {\n console.error(\"No message channel available to toggle microphone\");\n }\n },\n [options]\n );\n\n // Add setVolume function that will be exposed in the hook's return value\n const setVolume = useCallback((volume: number) => {\n // Validate volume (0-1 range)\n const validVolume = Math.min(Math.max(volume, 0), 1);\n\n // Send the command to the StateMonitor through the message channel\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"set_volume\",\n volume: validVolume,\n });\n } else {\n console.error(\"No message channel available to set volume\");\n }\n }, []);\n\n // Modify the useEffect hook that renders the LiveKit component\n useEffect(() => {\n if (!rootRef.current) return;\n\n if (connectionDetail) {\n // Only create a new LiveKit component if we don't have one or connection details changed\n if (!livekitComponentRef.current) {\n if (channelRef.current) {\n // Start port2 before passing it to StateMonitor\n channelRef.current.port2.start();\n }\n\n livekitComponentRef.current = (\n <LiveKitRoom\n serverUrl={connectionDetail.serverUrl}\n token={connectionDetail.participantToken}\n audio={true}\n video={false}\n connect={true}\n onDisconnected={disconnect}\n onError={(error) => {\n console.error(\"LiveKit connection error:\", error);\n disconnect();\n if (options.onError) {\n options.onError(\n new Error(`LiveKit connection error: ${error.message}`)\n );\n }\n }}\n >\n <RoomAudioRenderer />\n {channelRef.current && (\n <StateMonitor\n port={channelRef.current.port2}\n initialConfig={\n waveformConfigRef.current || {\n barCount: 10,\n updateInterval: 20,\n }\n }\n enableNoiseCancellation={\n options.enableNoiseCancellation ?? true\n }\n />\n )}\n </LiveKitRoom>\n );\n }\n\n rootRef.current.render(livekitComponentRef.current);\n } else {\n livekitComponentRef.current = null;\n rootRef.current.render(<></>);\n }\n }, [connectionDetail, disconnect, options.onError]);\n\n /**\n * Returns the VoxAI interface for controlling the conversation\n * @returns {Object} VoxAI interface\n * @property {Function} connect - Initiates a connection to the VoxAI service. Will reject with an error if already connected or in the process of connecting.\n * @property {Function} disconnect - Terminates the connection to the VoxAI service.\n * @property {VoxAgentState} state - The current state of the agent.\n * @property {VoxMessage[]} messages - An array of messages exchanged in the conversation.\n * @property {Function} send - Sends a message or DTMF digit to the agent.\n * @property {Function} audioWaveform - Returns audio waveform data for UI visualization.\n * @property {Function} toggleMic - Toggles the microphone on/off.\n * @property {Function} setVolume - Sets the volume of the agent's audio (0-1).\n */\n return {\n connect,\n disconnect,\n state,\n messages,\n send,\n audioWaveform,\n toggleMic,\n setVolume,\n };\n}\n\n/**\n * Component that monitors LiveKit state and communicates back to the main hook\n */\nfunction StateMonitor({\n port,\n initialConfig,\n enableNoiseCancellation = true,\n}: {\n port: MessagePort | undefined;\n initialConfig: {\n speaker?: \"agent\" | \"user\";\n barCount: number;\n updateInterval: number;\n };\n enableNoiseCancellation?: boolean;\n}) {\n const { agent, state } = useVoiceAssistant();\n const { send: sendChat } = useChat();\n\n // Initialize waveform config with the passed initial values, defaulting to \"agent\" if not specified\n const [waveformConfig, setWaveformConfig] = useState({\n speaker: initialConfig.speaker || \"agent\",\n barCount: initialConfig.barCount,\n updateInterval: initialConfig.updateInterval,\n });\n\n // Agent transcriptions\n const agentAudioTrack = useParticipantTracks(\n [Track.Source.Microphone],\n agent?.identity\n )[0];\n const agentTranscription = useTrackTranscription(agentAudioTrack);\n\n // Use the current config for the waveform, applying different settings based on speaker\n const agentAudioWaveform = useAudioWaveform(agentAudioTrack, {\n barCount:\n waveformConfig.speaker === \"agent\" ? waveformConfig.barCount : 120, // default if not the selected speaker\n updateInterval:\n waveformConfig.speaker === \"agent\" ? waveformConfig.updateInterval : 20,\n });\n\n // User transcriptions\n const localParticipant = useLocalParticipant();\n const localMessages = useTrackTranscription({\n publication: localParticipant.microphoneTrack,\n source: Track.Source.Microphone,\n participant: localParticipant.localParticipant,\n });\n const localAudioTrack = useParticipantTracks(\n [Track.Source.Microphone],\n localParticipant.localParticipant.identity\n )[0];\n const userAudioWaveform = useAudioWaveform(localAudioTrack, {\n barCount: waveformConfig.speaker === \"user\" ? waveformConfig.barCount : 120, // default if not the selected speaker\n updateInterval:\n waveformConfig.speaker === \"user\" ? waveformConfig.updateInterval : 20,\n });\n\n // Apply Krisp noise cancellation to the local microphone track\n useEffect(() => {\n const applyKrispNoiseFilter = async () => {\n const track = localParticipant.microphoneTrack?.track;\n if (track && track instanceof LocalAudioTrack) {\n try {\n // Only apply noise filter if enabled\n if (enableNoiseCancellation) {\n await track.setProcessor(KrispNoiseFilter());\n } else {\n // Remove any existing processor if noise cancellation is disabled\n await track.stopProcessor();\n }\n } catch (error) {\n console.error(\"Failed to set audio processor:\", error);\n }\n }\n };\n\n applyKrispNoiseFilter();\n }, [localParticipant.microphoneTrack, enableNoiseCancellation]);\n\n // Add separate effects to send agent and user waveform data\n useEffect(() => {\n if (!port || !agentAudioWaveform || !agentAudioWaveform.bars) return;\n\n // Send the agent waveform data\n port.postMessage({\n type: \"waveform_update\",\n waveformData: agentAudioWaveform.bars,\n speaker: \"agent\",\n });\n }, [port, agentAudioWaveform]);\n\n useEffect(() => {\n if (!port || !userAudioWaveform || !userAudioWaveform.bars) return;\n\n // Send the user waveform data\n port.postMessage({\n type: \"waveform_update\",\n waveformData: userAudioWaveform.bars,\n speaker: \"user\",\n });\n }, [port, userAudioWaveform]);\n\n // Listen for messages including config updates and mic toggle commands\n useEffect(() => {\n if (!port) return;\n\n const handleMessage = (event: MessageEvent) => {\n const data = event.data;\n\n if (data.type === \"waveform_config\" && data.config) {\n // Verify we have both required properties before updating\n if (\n typeof data.config.barCount === \"number\" &&\n typeof data.config.updateInterval === \"number\"\n ) {\n setWaveformConfig(data.config);\n }\n } else if (data.type === \"send_text\") {\n if (sendChat) {\n sendChat(data.text);\n } else {\n console.error(\"sendChat function is not available\");\n }\n } else if (data.type === \"send_dtmf\") {\n if (localParticipant.localParticipant) {\n // Use standard DTMF code (RFC 4733)\n const standardDtmfCode = 101; // Standard DTMF payload type\n localParticipant.localParticipant.publishDtmf(\n standardDtmfCode,\n data.digit\n );\n } else {\n console.error(\"Local participant is not available for DTMF\");\n }\n } else if (\n data.type === \"toggle_mic\" &&\n typeof data.enabled === \"boolean\"\n ) {\n // Handle microphone toggle\n if (localParticipant.localParticipant) {\n localParticipant.localParticipant\n .setMicrophoneEnabled(data.enabled)\n .catch((error) => {\n console.error(\"Failed to toggle microphone:\", error);\n });\n } else {\n console.error(\"Local participant is not available for mic toggle\");\n }\n } else if (\n data.type === \"set_volume\" &&\n typeof data.volume === \"number\"\n ) {\n // Handle volume control\n if (agent) {\n // The agent is a RemoteParticipant, so we can call setVolume directly\n try {\n agent.setVolume(data.volume);\n console.log(`Set agent volume to ${data.volume}`);\n } catch (error) {\n console.error(\"Failed to set agent volume:\", error);\n }\n } else {\n console.error(\"Agent is not available for volume control\");\n }\n }\n };\n\n // Make sure we start the port\n port.start();\n\n port.addEventListener(\"message\", handleMessage);\n\n return () => {\n port.removeEventListener(\"message\", handleMessage);\n };\n }, [port, sendChat, localParticipant, agent]);\n\n // Send agent state updates\n useEffect(() => {\n if (port) {\n port.postMessage({ type: \"state_update\", state });\n }\n }, [state, port]);\n\n // Send agent transcriptions\n useEffect(() => {\n if (port && agentTranscription.segments.length > 0) {\n const transcriptions = agentTranscription.segments.map((segment) => ({\n id: segment.id,\n text: segment.text,\n isFinal: segment.final,\n timestamp: Date.now(),\n speaker: \"agent\" as const,\n }));\n\n port.postMessage({\n type: \"transcription_update\",\n transcriptions,\n });\n }\n }, [agentTranscription.segments, port]);\n\n // Send user transcriptions\n useEffect(() => {\n if (port && localMessages.segments.length > 0) {\n const transcriptions = localMessages.segments.map((segment) => ({\n id: segment.id,\n text: segment.text,\n isFinal: segment.final,\n timestamp: Date.now(),\n speaker: \"user\" as const,\n }));\n\n port.postMessage({\n type: \"transcription_update\",\n transcriptions,\n });\n }\n }, [localMessages.segments, port]);\n\n // Add data channel hook for function calls\n const { message: functionCallsCollected } = useDataChannel(\n \"function_calls_collected\",\n (msg) => {\n if (!port) return;\n\n const textDecoder = new TextDecoder();\n const messageString =\n msg.payload instanceof Uint8Array\n ? textDecoder.decode(msg.payload)\n : String(msg.payload);\n\n let functionCallInfo: FunctionCallInfo[];\n try {\n functionCallInfo = JSON.parse(messageString);\n\n // Send function calls to main hook via the port\n port.postMessage({\n type: \"function_calls_collected\",\n functionCalls: functionCallInfo,\n });\n } catch (e) {\n console.error(\"Failed to parse function call log:\", e);\n }\n }\n );\n\n // Add data channel hook for function call results\n const { message: functionCallsFinished } = useDataChannel(\n \"function_calls_finished\",\n (msg) => {\n if (!port) return;\n\n const textDecoder = new TextDecoder();\n const messageString =\n msg.payload instanceof Uint8Array\n ? textDecoder.decode(msg.payload)\n : String(msg.payload);\n\n let functionCallResult: FunctionCallResult[];\n try {\n functionCallResult = JSON.parse(messageString);\n\n // Send function results to main hook via the port\n port.postMessage({\n type: \"function_calls_finished\",\n functionResults: functionCallResult,\n });\n } catch (e) {\n console.error(\"Failed to parse function call result:\", e);\n }\n }\n );\n\n return null;\n}\n"],"names":["_catch","body","recover","result","e","then","useVoxAI","options","connectionDetail","setConnectionDetail","useState","state","setState","transcriptMap","setTranscriptMap","Map","messages","setMessages","prevMessagesRef","useRef","processedMessageIdsRef","Set","portalRootRef","rootRef","channelRef","livekitComponentRef","waveformDataMap","setWaveformDataMap","agent","user","waveformConfigRef","isMicEnabled","setIsMicEnabled","useEffect","allMessages","Array","from","values","sort","a","b","timestamp","messagesString","JSON","stringify","current","onMessage","filter","msg","isFinal","id","has","forEach","add","channel","MessageChannel","port1","onmessage","data","type","handleTranscriptionUpdate","transcriptions","speaker","prevMap","waveformData","functionCalls","functionCallsId","Date","now","newMap","set","name","toolCalls","functionResults","functionResultsId","toolCallResults","start","_channelRef$current","_channelRef$current2","close","port2","useCallback","t","_prevMap$get","messageType","existingTimestamp","get","message","text","div","document","createElement","style","display","appendChild","createRoot","unmount","removeChild","connect","_ref","agentId","apiKey","dynamicVariables","metadata","Promise","resolve","errorMessage","console","warn","onError","Error","reject","fetch","method","headers","Authorization","agent_id","call_web","dynamic_variables","response","_temp2","_result2","_exit","json","onConnect","_temp","ok","errorText","status","err","error","String","disconnect","onDisconnect","send","_ref2","digit","messageId","postMessage","undefined","audioWaveform","_ref3","barCount","updateInterval","config","speakerData","length","slice","fill","toggleMic","value","enabled","setVolume","volume","validVolume","Math","min","max","_options$enableNoiseC","_jsxs","LiveKitRoom","serverUrl","token","participantToken","audio","video","onDisconnected","children","_jsx","RoomAudioRenderer","StateMonitor","port","initialConfig","enableNoiseCancellation","render","_Fragment","_ref4","useVoiceAssistant","sendChat","useChat","waveformConfig","setWaveformConfig","agentAudioTrack","useParticipantTracks","Track","Source","Microphone","identity","agentTranscription","useTrackTranscription","agentAudioWaveform","useAudioWaveform","localParticipant","useLocalParticipant","localMessages","publication","microphoneTrack","source","participant","localAudioTrack","userAudioWaveform","applyKrispNoiseFilter","_localParticipant$mic","track","LocalAudioTrack","_temp3","setProcessor","KrispNoiseFilter","stopProcessor","bars","handleMessage","event","publishDtmf","setMicrophoneEnabled","catch","log","addEventListener","removeEventListener","segments","map","segment","final","useDataChannel","textDecoder","TextDecoder","messageString","payload","Uint8Array","decode","functionCallInfo","parse","functionCallResult"],"mappings":"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"}
package/dist/lib.umd.js CHANGED
@@ -1,2 +1,2 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("react/jsx-runtime"),require("@livekit/components-react"),require("livekit-client"),require("react"),require("react-dom/client")):"function"==typeof define&&define.amd?define(["exports","react/jsx-runtime","@livekit/components-react","livekit-client","react","react-dom/client"],t):t((e||self).react={},e.jsxRuntime,e.componentsReact,e.livekitClient,e.react,e.client)}(this,function(e,t,n,r,a,s){function o(e){let{port:t,initialConfig:s}=e;const{agent:o,state:i}=n.useVoiceAssistant(),{send:c}=n.useChat(),[u,l]=a.useState({speaker:s.speaker||"agent",barCount:s.barCount,updateInterval:s.updateInterval}),p=n.useParticipantTracks([r.Track.Source.Microphone],null==o?void 0:o.identity)[0],d=n.useTrackTranscription(p),f=n.useAudioWaveform(p,{barCount:"agent"===u.speaker?u.barCount:120,updateInterval:"agent"===u.speaker?u.updateInterval:20}),m=n.useLocalParticipant(),g=n.useTrackTranscription({publication:m.microphoneTrack,source:r.Track.Source.Microphone,participant:m.localParticipant}),v=n.useParticipantTracks([r.Track.Source.Microphone],m.localParticipant.identity)[0],y=n.useAudioWaveform(v,{barCount:"user"===u.speaker?u.barCount:120,updateInterval:"user"===u.speaker?u.updateInterval:20});return a.useEffect(()=>{t&&f&&f.bars&&t.postMessage({type:"waveform_update",waveformData:f.bars,speaker:"agent"})},[t,f]),a.useEffect(()=>{t&&y&&y.bars&&t.postMessage({type:"waveform_update",waveformData:y.bars,speaker:"user"})},[t,y]),a.useEffect(()=>{if(!t)return;const e=e=>{const t=e.data;"waveform_config"===t.type&&t.config?"number"==typeof t.config.barCount&&"number"==typeof t.config.updateInterval&&l(t.config):"send_text"===t.type?c?c(t.text):console.error("sendChat function is not available"):"send_dtmf"===t.type&&(m.localParticipant?m.localParticipant.publishDtmf(101,t.digit):console.error("Local participant is not available for DTMF"))};return t.start(),t.addEventListener("message",e),()=>{t.removeEventListener("message",e)}},[t,c,m]),a.useEffect(()=>{t&&t.postMessage({type:"state_update",state:i})},[i,t]),a.useEffect(()=>{if(t&&d.segments.length>0){const e=d.segments.map(e=>({id:e.id,text:e.text,isFinal:e.final,timestamp:Date.now(),speaker:"agent"}));t.postMessage({type:"transcription_update",transcriptions:e})}},[d.segments,t]),a.useEffect(()=>{if(t&&g.segments.length>0){const e=g.segments.map(e=>({id:e.id,text:e.text,isFinal:e.final,timestamp:Date.now(),speaker:"user"}));t.postMessage({type:"transcription_update",transcriptions:e})}},[g.segments,t]),n.useDataChannel("function_calls_collected",e=>{if(!t)return;const n=new TextDecoder,r=e.payload instanceof Uint8Array?n.decode(e.payload):String(e.payload);let a;try{a=JSON.parse(r),t.postMessage({type:"function_calls_collected",functionCalls:a})}catch(e){console.error("Failed to parse function call log:",e)}}),n.useDataChannel("function_calls_finished",e=>{if(!t)return;const n=new TextDecoder,r=e.payload instanceof Uint8Array?n.decode(e.payload):String(e.payload);let a;try{a=JSON.parse(r),t.postMessage({type:"function_calls_finished",functionResults:a})}catch(e){console.error("Failed to parse function call result:",e)}}),null}e.useVoxAI=function(e){void 0===e&&(e={});const[r,i]=a.useState(null),[c,u]=a.useState("disconnected"),[l,p]=a.useState(new Map),[d,f]=a.useState([]),m=a.useRef(""),g=a.useRef(new Set),v=a.useRef(null),y=a.useRef(null),h=a.useRef(null),k=a.useRef(null),[b,w]=a.useState({agent:[],user:[]}),C=a.useRef(null);a.useEffect(()=>{const t=Array.from(l.values()).sort((e,t)=>e.timestamp-t.timestamp),n=JSON.stringify(t);n!==m.current&&(m.current=n,f(t),e.onMessage&&t.filter(e=>e.isFinal&&e.id&&!g.current.has(e.id)).forEach(t=>{t.id&&(g.current.add(t.id),null==e.onMessage||e.onMessage(t))}))},[l,e.onMessage]),a.useEffect(()=>{const e=new MessageChannel;return e.port1.onmessage=e=>{const t=e.data;if("state_update"===t.type)u(t.state);else if("transcription_update"===t.type)_(t.transcriptions);else if("waveform_update"===t.type&&t.speaker)w(e=>({...e,[t.speaker]:t.waveformData}));else if("function_calls_collected"===t.type&&t.functionCalls){const e="function-calls-"+Date.now();p(n=>{const r=new Map(n);return r.set(e,{id:e,name:"tool",toolCalls:t.functionCalls,timestamp:Date.now(),isFinal:!0}),r})}else if("function_calls_finished"===t.type&&t.functionResults){const e="function-results-"+Date.now();p(n=>{const r=new Map(n);return r.set(e,{id:e,name:"tool",toolCallResults:t.functionResults,timestamp:Date.now(),isFinal:!0}),r})}},e.port1.start(),h.current=e,()=>{var e,t;null==(e=h.current)||e.port1.close(),null==(t=h.current)||t.port2.close(),h.current=null}},[]);const _=a.useCallback(e=>{p(t=>{const n=new Map(t);return e.forEach(e=>{var r;const a="agent"===e.speaker?"agent":"user",s=(null==(r=t.get(e.id))?void 0:r.timestamp)||e.timestamp;n.set(e.id,{id:e.id,name:a,message:e.text,timestamp:s,isFinal:e.isFinal})}),n})},[]);a.useEffect(()=>{const e=document.createElement("div");return e.style.display="none",document.body.appendChild(e),v.current=e,y.current=s.createRoot(e),()=>{y.current&&y.current.unmount(),v.current&&document.body.removeChild(v.current)}},[]);const M=a.useCallback(function(t){let{agentId:n,apiKey:r,dynamicVariables:a,metadata:s}=t;try{return Promise.resolve(function(t,o){try{var c=(u("connecting"),Promise.resolve(fetch("https://www.tryvox.co/api/agent/sdk",{method:"POST",headers:{Authorization:"Bearer "+r,"Content-Type":"application/json"},body:JSON.stringify({agent_id:n,metadata:{call_web:{dynamic_variables:a||{},metadata:s||{}}}})})).then(function(t){function n(n){return Promise.resolve(t.json()).then(function(t){i(t),e.onConnect&&e.onConnect()})}const r=function(){if(!t.ok)return Promise.resolve(t.text()).then(function(e){throw new Error("Connection failed ("+t.status+"): "+e)})}();return r&&r.then?r.then(n):n()}))}catch(e){return o(e)}return c&&c.then?c.then(void 0,o):c}(0,function(t){i(null),p(new Map),f([]),u("disconnected");const n=t instanceof Error?t:new Error(String(t));e.onError&&e.onError(n)}))}catch(e){return Promise.reject(e)}},[e]),x=a.useCallback(()=>{i(null),p(new Map),f([]),u("disconnected"),e.onDisconnect&&e.onDisconnect()},[e]),E=a.useCallback(e=>{let{message:t,digit:n}=e;if("disconnected"!==c){if(t){const e="user-text-"+Date.now();p(n=>{const r=new Map(n);return r.set(e,{id:e,name:"user",message:t,timestamp:Date.now(),isFinal:!0}),r}),h.current?h.current.port1.postMessage({type:"send_text",text:t}):console.error("No message channel available to send message")}void 0!==n&&(h.current?h.current.port1.postMessage({type:"send_dtmf",digit:n}):console.error("No message channel available to send DTMF"))}else console.warn("Cannot send message: Not connected to a conversation")},[c]),D=a.useCallback(e=>{let{speaker:t="agent",barCount:n=10,updateInterval:r=20}=e;C.current={speaker:t,barCount:n,updateInterval:r},h.current&&h.current.port1.postMessage({type:"waveform_config",config:{speaker:t,barCount:n,updateInterval:r}});const a=b[t]||[];return a.length>0?a.slice(0,n):Array(n).fill(0)},[b]);return a.useEffect(()=>{y.current&&(r?(k.current||(h.current&&h.current.port2.start(),k.current=t.jsxs(n.LiveKitRoom,{serverUrl:r.serverUrl,token:r.participantToken,audio:!0,video:!1,connect:!0,onDisconnected:x,onError:t=>{console.error("LiveKit connection error:",t),x(),e.onError&&e.onError(new Error("LiveKit connection error: "+t.message))},children:[t.jsx(n.RoomAudioRenderer,{}),h.current&&t.jsx(o,{port:h.current.port2,initialConfig:C.current||{barCount:10,updateInterval:20}})]})),y.current.render(k.current)):(k.current=null,y.current.render(t.jsx(t.Fragment,{}))))},[r,x,e.onError]),{connect:M,disconnect:x,state:c,messages:d,send:E,audioWaveform:D}}});
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("react/jsx-runtime"),require("@livekit/components-react"),require("@livekit/krisp-noise-filter"),require("livekit-client"),require("react"),require("react-dom/client")):"function"==typeof define&&define.amd?define(["exports","react/jsx-runtime","@livekit/components-react","@livekit/krisp-noise-filter","livekit-client","react","react-dom/client"],t):t((e||self).react={},e.jsxRuntime,e.componentsReact,e.krispNoiseFilter,e.livekitClient,e.react,e.client)}(this,function(e,t,n,r,o,a,s){function i(e,t){try{var n=e()}catch(e){return t(e)}return n&&n.then?n.then(void 0,t):n}function c(e){let{port:t,initialConfig:s,enableNoiseCancellation:c=!0}=e;const{agent:l,state:u}=n.useVoiceAssistant(),{send:p}=n.useChat(),[f,d]=a.useState({speaker:s.speaker||"agent",barCount:s.barCount,updateInterval:s.updateInterval}),m=n.useParticipantTracks([o.Track.Source.Microphone],null==l?void 0:l.identity)[0],g=n.useTrackTranscription(m),v=n.useAudioWaveform(m,{barCount:"agent"===f.speaker?f.barCount:120,updateInterval:"agent"===f.speaker?f.updateInterval:20}),y=n.useLocalParticipant(),h=n.useTrackTranscription({publication:y.microphoneTrack,source:o.Track.Source.Microphone,participant:y.localParticipant}),b=n.useParticipantTracks([o.Track.Source.Microphone],y.localParticipant.identity)[0],k=n.useAudioWaveform(b,{barCount:"user"===f.speaker?f.barCount:120,updateInterval:"user"===f.speaker?f.updateInterval:20});return a.useEffect(()=>{!function(){try{var e;const t=null==(e=y.microphoneTrack)?void 0:e.track;return Promise.resolve(function(){if(t&&t instanceof o.LocalAudioTrack)return i(function(){const e=c?Promise.resolve(t.setProcessor(r.KrispNoiseFilter())).then(function(){}):Promise.resolve(t.stopProcessor()).then(function(){});if(e&&e.then)return e.then(function(){})},function(e){console.error("Failed to set audio processor:",e)})}())}catch(e){return Promise.reject(e)}}()},[y.microphoneTrack,c]),a.useEffect(()=>{t&&v&&v.bars&&t.postMessage({type:"waveform_update",waveformData:v.bars,speaker:"agent"})},[t,v]),a.useEffect(()=>{t&&k&&k.bars&&t.postMessage({type:"waveform_update",waveformData:k.bars,speaker:"user"})},[t,k]),a.useEffect(()=>{if(!t)return;const e=e=>{const t=e.data;if("waveform_config"===t.type&&t.config)"number"==typeof t.config.barCount&&"number"==typeof t.config.updateInterval&&d(t.config);else if("send_text"===t.type)p?p(t.text):console.error("sendChat function is not available");else if("send_dtmf"===t.type)y.localParticipant?y.localParticipant.publishDtmf(101,t.digit):console.error("Local participant is not available for DTMF");else if("toggle_mic"===t.type&&"boolean"==typeof t.enabled)y.localParticipant?y.localParticipant.setMicrophoneEnabled(t.enabled).catch(e=>{console.error("Failed to toggle microphone:",e)}):console.error("Local participant is not available for mic toggle");else if("set_volume"===t.type&&"number"==typeof t.volume)if(l)try{l.setVolume(t.volume),console.log("Set agent volume to "+t.volume)}catch(e){console.error("Failed to set agent volume:",e)}else console.error("Agent is not available for volume control")};return t.start(),t.addEventListener("message",e),()=>{t.removeEventListener("message",e)}},[t,p,y,l]),a.useEffect(()=>{t&&t.postMessage({type:"state_update",state:u})},[u,t]),a.useEffect(()=>{if(t&&g.segments.length>0){const e=g.segments.map(e=>({id:e.id,text:e.text,isFinal:e.final,timestamp:Date.now(),speaker:"agent"}));t.postMessage({type:"transcription_update",transcriptions:e})}},[g.segments,t]),a.useEffect(()=>{if(t&&h.segments.length>0){const e=h.segments.map(e=>({id:e.id,text:e.text,isFinal:e.final,timestamp:Date.now(),speaker:"user"}));t.postMessage({type:"transcription_update",transcriptions:e})}},[h.segments,t]),n.useDataChannel("function_calls_collected",e=>{if(!t)return;const n=new TextDecoder,r=e.payload instanceof Uint8Array?n.decode(e.payload):String(e.payload);let o;try{o=JSON.parse(r),t.postMessage({type:"function_calls_collected",functionCalls:o})}catch(e){console.error("Failed to parse function call log:",e)}}),n.useDataChannel("function_calls_finished",e=>{if(!t)return;const n=new TextDecoder,r=e.payload instanceof Uint8Array?n.decode(e.payload):String(e.payload);let o;try{o=JSON.parse(r),t.postMessage({type:"function_calls_finished",functionResults:o})}catch(e){console.error("Failed to parse function call result:",e)}}),null}e.useVoxAI=function(e){void 0===e&&(e={});const[r,o]=a.useState(null),[l,u]=a.useState("disconnected"),[p,f]=a.useState(new Map),[d,m]=a.useState([]),g=a.useRef(""),v=a.useRef(new Set),y=a.useRef(null),h=a.useRef(null),b=a.useRef(null),k=a.useRef(null),[C,w]=a.useState({agent:[],user:[]}),M=a.useRef(null),[_,E]=a.useState(!0);a.useEffect(()=>{const t=Array.from(p.values()).sort((e,t)=>e.timestamp-t.timestamp),n=JSON.stringify(t);n!==g.current&&(g.current=n,m(t),e.onMessage&&t.filter(e=>e.isFinal&&e.id&&!v.current.has(e.id)).forEach(t=>{t.id&&(v.current.add(t.id),null==e.onMessage||e.onMessage(t))}))},[p,e.onMessage]),a.useEffect(()=>{const e=new MessageChannel;return e.port1.onmessage=e=>{const t=e.data;if("state_update"===t.type)u(t.state);else if("transcription_update"===t.type)x(t.transcriptions);else if("waveform_update"===t.type&&t.speaker)w(e=>({...e,[t.speaker]:t.waveformData}));else if("function_calls_collected"===t.type&&t.functionCalls){const e="function-calls-"+Date.now();f(n=>{const r=new Map(n);return r.set(e,{id:e,name:"tool",toolCalls:t.functionCalls,timestamp:Date.now(),isFinal:!0}),r})}else if("function_calls_finished"===t.type&&t.functionResults){const e="function-results-"+Date.now();f(n=>{const r=new Map(n);return r.set(e,{id:e,name:"tool",toolCallResults:t.functionResults,timestamp:Date.now(),isFinal:!0}),r})}},e.port1.start(),b.current=e,()=>{var e,t;null==(e=b.current)||e.port1.close(),null==(t=b.current)||t.port2.close(),b.current=null}},[]);const x=a.useCallback(e=>{f(t=>{const n=new Map(t);return e.forEach(e=>{var r;const o="agent"===e.speaker?"agent":"user",a=(null==(r=t.get(e.id))?void 0:r.timestamp)||e.timestamp;n.set(e.id,{id:e.id,name:o,message:e.text,timestamp:a,isFinal:e.isFinal})}),n})},[]);a.useEffect(()=>{const e=document.createElement("div");return e.style.display="none",document.body.appendChild(e),y.current=e,h.current=s.createRoot(e),()=>{h.current&&h.current.unmount(),y.current&&document.body.removeChild(y.current)}},[]);const P=a.useCallback(function(t){let{agentId:n,apiKey:r,dynamicVariables:a,metadata:s}=t;try{return Promise.resolve(i(function(){if("disconnected"!==l){const t="Connection attempt rejected: Already in a connection state ("+l+")";return console.warn(t),e.onError&&e.onError(new Error(t)),Promise.reject(new Error(t))}return u("connecting"),Promise.resolve(fetch("https://www.tryvox.co/api/agent/sdk",{method:"POST",headers:{Authorization:"Bearer "+r,"Content-Type":"application/json"},body:JSON.stringify({agent_id:n,metadata:{call_web:{dynamic_variables:a||{},metadata:s||{}}}})})).then(function(t){function n(n){return Promise.resolve(t.json()).then(function(t){o(t),e.onConnect&&e.onConnect()})}const r=function(){if(!t.ok)return Promise.resolve(t.text()).then(function(e){throw new Error("Connection failed ("+t.status+"): "+e)})}();return r&&r.then?r.then(n):n()})},function(t){o(null),f(new Map),m([]),u("disconnected");const n=t instanceof Error?t:new Error(String(t));e.onError&&e.onError(n)}))}catch(e){return Promise.reject(e)}},[e]),T=a.useCallback(()=>{o(null),f(new Map),m([]),u("disconnected"),e.onDisconnect&&e.onDisconnect()},[e]),D=a.useCallback(e=>{let{message:t,digit:n}=e;if("disconnected"!==l){if(t){const e="user-text-"+Date.now();f(n=>{const r=new Map(n);return r.set(e,{id:e,name:"user",message:t,timestamp:Date.now(),isFinal:!0}),r}),b.current?b.current.port1.postMessage({type:"send_text",text:t}):console.error("No message channel available to send message")}void 0!==n&&(b.current?b.current.port1.postMessage({type:"send_dtmf",digit:n}):console.error("No message channel available to send DTMF"))}else console.warn("Cannot send message: Not connected to a conversation")},[l]),S=a.useCallback(e=>{let{speaker:t="agent",barCount:n=10,updateInterval:r=20}=e;M.current={speaker:t,barCount:n,updateInterval:r},b.current&&b.current.port1.postMessage({type:"waveform_config",config:{speaker:t,barCount:n,updateInterval:r}});const o=C[t]||[];return o.length>0?o.slice(0,n):Array(n).fill(0)},[C]),F=a.useCallback(e=>{E(e),b.current?b.current.port1.postMessage({type:"toggle_mic",enabled:e}):console.error("No message channel available to toggle microphone")},[e]),R=a.useCallback(e=>{const t=Math.min(Math.max(e,0),1);b.current?b.current.port1.postMessage({type:"set_volume",volume:t}):console.error("No message channel available to set volume")},[]);return a.useEffect(()=>{if(h.current)if(r){var o;k.current||(b.current&&b.current.port2.start(),k.current=t.jsxs(n.LiveKitRoom,{serverUrl:r.serverUrl,token:r.participantToken,audio:!0,video:!1,connect:!0,onDisconnected:T,onError:t=>{console.error("LiveKit connection error:",t),T(),e.onError&&e.onError(new Error("LiveKit connection error: "+t.message))},children:[t.jsx(n.RoomAudioRenderer,{}),b.current&&t.jsx(c,{port:b.current.port2,initialConfig:M.current||{barCount:10,updateInterval:20},enableNoiseCancellation:null==(o=e.enableNoiseCancellation)||o})]})),h.current.render(k.current)}else k.current=null,h.current.render(t.jsx(t.Fragment,{}))},[r,T,e.onError]),{connect:P,disconnect:T,state:l,messages:d,send:D,audioWaveform:S,toggleMic:F,setVolume:R}}});
2
2
  //# sourceMappingURL=lib.umd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"lib.umd.js","sources":["../src/hooks/useVoxAI.tsx"],"sourcesContent":["import {\n LiveKitRoom,\n RoomAudioRenderer,\n useLocalParticipant,\n useParticipantTracks,\n useTrackTranscription,\n useVoiceAssistant,\n useChat,\n useAudioWaveform,\n useDataChannel,\n} from \"@livekit/components-react\";\nimport { Track } from \"livekit-client\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { createRoot, Root } from \"react-dom/client\";\n\ntype VoxConnectionDetail = {\n serverUrl: string;\n roomName: string;\n participantName: string;\n participantToken: string;\n};\n\n/**\n * VoxAgentState\n * @description The state of the agent\n */\nexport type VoxAgentState =\n | \"disconnected\"\n | \"connecting\"\n | \"initializing\"\n | \"listening\"\n | \"thinking\"\n | \"speaking\";\n\n// API endpoint\nconst HTTPS_API_ORIGIN = \"https://www.tryvox.co/api/agent/sdk\";\n// const HTTPS_API_ORIGIN = \"http://localhost:3000/api/agent/sdk\";\n\n/**\n * Function call related types\n */\nexport interface FunctionCallInfo {\n tool_call_id: string;\n function_info: {\n name: string;\n description: string;\n arguments: Record<string, any>;\n };\n raw_arguments: string;\n arguments: Record<string, any>;\n}\n\nexport interface FunctionCallResult {\n call_info: {\n tool_call_id: string;\n function_info: {\n name: string;\n description: string;\n arguments: Record<string, any>;\n };\n raw_arguments: string;\n arguments: Record<string, any>;\n };\n result: Record<string, any> | null;\n exception: string | null;\n}\n\n/**\n * VoxMessage\n * @description The message type between the agent and the user\n */\nexport type VoxMessage = {\n id?: string;\n name: \"agent\" | \"user\" | \"tool\";\n message?: string;\n timestamp: number;\n isFinal?: boolean;\n toolCalls?: FunctionCallInfo[];\n toolCallResults?: FunctionCallResult[];\n};\n\n/**\n * VoxAIOptions\n * @description The callback functions for the useVoxAI hook\n */\nexport interface VoxAIOptions {\n onConnect?: () => void;\n onDisconnect?: () => void;\n onError?: (error: Error) => void;\n onMessage?: (message: VoxMessage) => void;\n}\n\n// Message channel event types\ntype MessageChannelEvent =\n | { type: \"state_update\"; state: VoxAgentState }\n | { type: \"transcription_update\"; transcriptions: TranscriptionSegment[] }\n | {\n type: \"waveform_update\";\n waveformData: number[];\n speaker: \"agent\" | \"user\";\n }\n | { type: \"function_calls_collected\"; functionCalls: FunctionCallInfo[] }\n | { type: \"function_calls_finished\"; functionResults: FunctionCallResult[] };\n\ntype TranscriptionSegment = {\n id: string;\n text: string;\n isFinal: boolean;\n timestamp: number;\n speaker: \"agent\" | \"user\";\n};\n\n/**\n * ConnectParams\n * @param agentId - The agent ID\n * @param apiKey - The API key\n * @param dynamicVariables - The dynamic variables\n * @param metadata - 이 메타데이터는 아웃바운드 웹훅, 통화 기록에 포함됩니다.\n */\nexport interface ConnectParams {\n agentId: string;\n apiKey: string;\n dynamicVariables?: Record<string, any>;\n metadata?: Record<string, any>;\n}\n\n/**\n * useVoxAI\n * @description The hook for integrating with vox.ai voice assistant\n * @param options - The options for the useVoxAI hook\n * @returns The useVoxAI hook\n * @example\n * const { connect, disconnect, state, messages, send } = useVoxAI({\n * onConnect: () => console.log(\"Connected\"),\n * onDisconnect: () => console.log(\"Disconnected\"),\n * onError: (error) => console.error(\"Error:\", error),\n * onMessage: (message) => console.log(\"Message:\", message),\n * });\n */\nexport function useVoxAI(options: VoxAIOptions = {}) {\n // Connection state\n const [connectionDetail, setConnectionDetail] =\n useState<VoxConnectionDetail | null>(null);\n const [state, setState] = useState<VoxAgentState>(\"disconnected\");\n\n // Message handling\n const [transcriptMap, setTranscriptMap] = useState<Map<string, VoxMessage>>(\n new Map()\n );\n const [messages, setMessages] = useState<VoxMessage[]>([]);\n const prevMessagesRef = useRef<string>(\"\");\n\n // Track which messages we've already sent to the onMessage callback\n const processedMessageIdsRef = useRef<Set<string>>(new Set());\n\n // DOM manipulation for LiveKit portal\n const portalRootRef = useRef<HTMLDivElement | null>(null);\n const rootRef = useRef<Root | null>(null);\n\n // Communication channel\n const channelRef = useRef<MessageChannel | null>(null);\n\n // Add this near the start of your useVoxAI hook\n const livekitComponentRef = useRef<React.ReactNode>(null);\n\n // Replace the single waveform state with a map for multiple speakers\n const [waveformDataMap, setWaveformDataMap] = useState<\n Record<string, number[]>\n >({\n agent: [],\n user: [],\n });\n\n // Add back the waveform config reference\n const waveformConfigRef = useRef<{\n speaker?: \"agent\" | \"user\";\n barCount: number;\n updateInterval: number;\n } | null>(null);\n\n // Update messages whenever transcriptMap changes\n useEffect(() => {\n const allMessages = Array.from(transcriptMap.values()).sort(\n (a, b) => a.timestamp - b.timestamp\n );\n\n // Only update if the messages have actually changed\n const messagesString = JSON.stringify(allMessages);\n if (messagesString !== prevMessagesRef.current) {\n prevMessagesRef.current = messagesString;\n setMessages(allMessages);\n\n // Only trigger onMessage for new final messages that haven't been processed yet\n if (options.onMessage) {\n allMessages\n .filter(\n (msg) =>\n msg.isFinal &&\n msg.id &&\n !processedMessageIdsRef.current.has(msg.id)\n )\n .forEach((msg) => {\n if (msg.id) {\n // Mark this message as processed\n processedMessageIdsRef.current.add(msg.id);\n // Call the callback\n options.onMessage?.(msg);\n }\n });\n }\n }\n }, [transcriptMap, options.onMessage]);\n\n // Initialize message channel - ensure ports are properly connected\n useEffect(() => {\n const channel = new MessageChannel();\n\n channel.port1.onmessage = (e) => {\n const data = e.data as MessageChannelEvent;\n\n if (data.type === \"state_update\") {\n setState(data.state);\n } else if (data.type === \"transcription_update\") {\n handleTranscriptionUpdate(data.transcriptions);\n } else if (data.type === \"waveform_update\" && data.speaker) {\n // Store the waveform data for the specific speaker\n setWaveformDataMap((prevMap) => ({\n ...prevMap,\n [data.speaker]: data.waveformData,\n }));\n } else if (\n data.type === \"function_calls_collected\" &&\n data.functionCalls\n ) {\n // Handle function calls\n const functionCallsId = `function-calls-${Date.now()}`;\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n newMap.set(functionCallsId, {\n id: functionCallsId,\n name: \"tool\",\n toolCalls: data.functionCalls,\n timestamp: Date.now(),\n isFinal: true,\n });\n return newMap;\n });\n } else if (\n data.type === \"function_calls_finished\" &&\n data.functionResults\n ) {\n // Handle function results\n const functionResultsId = `function-results-${Date.now()}`;\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n newMap.set(functionResultsId, {\n id: functionResultsId,\n name: \"tool\",\n toolCallResults: data.functionResults,\n timestamp: Date.now(),\n isFinal: true,\n });\n return newMap;\n });\n }\n };\n\n // Start the port\n channel.port1.start();\n\n // Store the channel reference\n channelRef.current = channel;\n\n return () => {\n channelRef.current?.port1.close();\n channelRef.current?.port2.close();\n channelRef.current = null;\n };\n }, []);\n\n // Process incoming transcriptions\n const handleTranscriptionUpdate = useCallback(\n (transcriptions: TranscriptionSegment[]) => {\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n\n transcriptions.forEach((t) => {\n const messageType = t.speaker === \"agent\" ? \"agent\" : \"user\";\n // Use existing timestamp if we already have this segment\n const existingTimestamp = prevMap.get(t.id)?.timestamp || t.timestamp;\n\n newMap.set(t.id, {\n id: t.id,\n name: messageType,\n message: t.text,\n timestamp: existingTimestamp,\n isFinal: t.isFinal,\n });\n });\n\n return newMap;\n });\n },\n []\n );\n\n // Set up DOM portal for LiveKit\n useEffect(() => {\n const div = document.createElement(\"div\");\n div.style.display = \"none\";\n document.body.appendChild(div);\n portalRootRef.current = div;\n rootRef.current = createRoot(div);\n\n return () => {\n if (rootRef.current) {\n rootRef.current.unmount();\n }\n if (portalRootRef.current) {\n document.body.removeChild(portalRootRef.current);\n }\n };\n }, []);\n\n // Connect to VoxAI service - updated to include dynamicVariables\n const connect = useCallback(\n async ({ agentId, apiKey, dynamicVariables, metadata }: ConnectParams) => {\n try {\n setState(\"connecting\");\n\n const response = await fetch(HTTPS_API_ORIGIN, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n agent_id: agentId,\n metadata: {\n call_web: {\n dynamic_variables: dynamicVariables || {},\n metadata: metadata || {},\n },\n },\n }),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `Connection failed (${response.status}): ${errorText}`\n );\n }\n\n const data = await response.json();\n setConnectionDetail(data);\n\n if (options.onConnect) {\n options.onConnect();\n }\n } catch (err) {\n // Reset state on error\n setConnectionDetail(null);\n setTranscriptMap(new Map());\n setMessages([]);\n setState(\"disconnected\");\n\n const error = err instanceof Error ? err : new Error(String(err));\n\n if (options.onError) {\n options.onError(error);\n }\n }\n },\n [options]\n );\n\n // Disconnect from VoxAI service\n const disconnect = useCallback(() => {\n setConnectionDetail(null);\n setTranscriptMap(new Map());\n setMessages([]);\n setState(\"disconnected\");\n\n if (options.onDisconnect) {\n options.onDisconnect();\n }\n }, [options]);\n\n // Update the send function with debugging and error checking\n const send = useCallback(\n ({ message, digit }: { message?: string; digit?: number }) => {\n if (state === \"disconnected\") {\n console.warn(\"Cannot send message: Not connected to a conversation\");\n return;\n }\n\n if (message) {\n // Add the message to our local transcript map for immediate feedback\n const messageId = `user-text-${Date.now()}`;\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n newMap.set(messageId, {\n id: messageId,\n name: \"user\",\n message: message,\n timestamp: Date.now(),\n isFinal: true,\n });\n return newMap;\n });\n\n // Send message through the message channel to StateMonitor\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"send_text\",\n text: message,\n });\n } else {\n console.error(\"No message channel available to send message\");\n }\n }\n\n if (digit !== undefined) {\n // Send DTMF through the message channel to StateMonitor\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"send_dtmf\",\n digit: digit,\n });\n } else {\n console.error(\"No message channel available to send DTMF\");\n }\n }\n },\n [state]\n );\n\n // Update the audioWaveform function to return data for the requested speaker\n const audioWaveform = useCallback(\n ({\n speaker = \"agent\",\n barCount = 10,\n updateInterval = 20,\n }: {\n speaker?: \"agent\" | \"user\";\n barCount?: number;\n updateInterval?: number;\n }): number[] => {\n // Store the waveform configuration for StateMonitor to use\n waveformConfigRef.current = { speaker, barCount, updateInterval };\n\n // Send the configuration to StateMonitor if channel is available\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"waveform_config\",\n config: { speaker, barCount, updateInterval },\n });\n }\n\n // Get the waveform data for the requested speaker\n const speakerData = waveformDataMap[speaker] || [];\n\n // Return the current waveform data, or a default array if no data yet\n return speakerData.length > 0\n ? speakerData.slice(0, barCount) // Ensure we return only barCount items\n : Array(barCount).fill(0);\n },\n [waveformDataMap]\n );\n\n // Modify the useEffect hook that renders the LiveKit component\n useEffect(() => {\n if (!rootRef.current) return;\n\n if (connectionDetail) {\n // Only create a new LiveKit component if we don't have one or connection details changed\n if (!livekitComponentRef.current) {\n if (channelRef.current) {\n // Start port2 before passing it to StateMonitor\n channelRef.current.port2.start();\n }\n\n livekitComponentRef.current = (\n <LiveKitRoom\n serverUrl={connectionDetail.serverUrl}\n token={connectionDetail.participantToken}\n audio={true}\n video={false}\n connect={true}\n onDisconnected={disconnect}\n onError={(error) => {\n console.error(\"LiveKit connection error:\", error);\n disconnect();\n if (options.onError) {\n options.onError(\n new Error(`LiveKit connection error: ${error.message}`)\n );\n }\n }}\n >\n <RoomAudioRenderer />\n {channelRef.current && (\n <StateMonitor\n port={channelRef.current.port2}\n initialConfig={\n waveformConfigRef.current || {\n barCount: 10,\n updateInterval: 20,\n }\n }\n />\n )}\n </LiveKitRoom>\n );\n }\n\n rootRef.current.render(livekitComponentRef.current);\n } else {\n livekitComponentRef.current = null;\n rootRef.current.render(<></>);\n }\n }, [connectionDetail, disconnect, options.onError]);\n\n return {\n connect,\n disconnect,\n state,\n messages,\n send,\n audioWaveform,\n };\n}\n\n/**\n * Component that monitors LiveKit state and communicates back to the main hook\n */\nfunction StateMonitor({\n port,\n initialConfig,\n}: {\n port: MessagePort | undefined;\n initialConfig: {\n speaker?: \"agent\" | \"user\";\n barCount: number;\n updateInterval: number;\n };\n}) {\n const { agent, state } = useVoiceAssistant();\n const { send: sendChat } = useChat();\n\n // Initialize waveform config with the passed initial values, defaulting to \"agent\" if not specified\n const [waveformConfig, setWaveformConfig] = useState({\n speaker: initialConfig.speaker || \"agent\",\n barCount: initialConfig.barCount,\n updateInterval: initialConfig.updateInterval,\n });\n\n // Agent transcriptions\n const agentAudioTrack = useParticipantTracks(\n [Track.Source.Microphone],\n agent?.identity\n )[0];\n const agentTranscription = useTrackTranscription(agentAudioTrack);\n\n // Use the current config for the waveform, applying different settings based on speaker\n const agentAudioWaveform = useAudioWaveform(agentAudioTrack, {\n barCount:\n waveformConfig.speaker === \"agent\" ? waveformConfig.barCount : 120, // default if not the selected speaker\n updateInterval:\n waveformConfig.speaker === \"agent\" ? waveformConfig.updateInterval : 20,\n });\n\n // User transcriptions\n const localParticipant = useLocalParticipant();\n const localMessages = useTrackTranscription({\n publication: localParticipant.microphoneTrack,\n source: Track.Source.Microphone,\n participant: localParticipant.localParticipant,\n });\n const localAudioTrack = useParticipantTracks(\n [Track.Source.Microphone],\n localParticipant.localParticipant.identity\n )[0];\n const userAudioWaveform = useAudioWaveform(localAudioTrack, {\n barCount: waveformConfig.speaker === \"user\" ? waveformConfig.barCount : 120, // default if not the selected speaker\n updateInterval:\n waveformConfig.speaker === \"user\" ? waveformConfig.updateInterval : 20,\n });\n\n // Add separate effects to send agent and user waveform data\n useEffect(() => {\n if (!port || !agentAudioWaveform || !agentAudioWaveform.bars) return;\n\n // Send the agent waveform data\n port.postMessage({\n type: \"waveform_update\",\n waveformData: agentAudioWaveform.bars,\n speaker: \"agent\",\n });\n }, [port, agentAudioWaveform]);\n\n useEffect(() => {\n if (!port || !userAudioWaveform || !userAudioWaveform.bars) return;\n\n // Send the user waveform data\n port.postMessage({\n type: \"waveform_update\",\n waveformData: userAudioWaveform.bars,\n speaker: \"user\",\n });\n }, [port, userAudioWaveform]);\n\n // Listen for messages including config updates\n useEffect(() => {\n if (!port) return;\n\n const handleMessage = (event: MessageEvent) => {\n const data = event.data;\n\n if (data.type === \"waveform_config\" && data.config) {\n // Verify we have both required properties before updating\n if (\n typeof data.config.barCount === \"number\" &&\n typeof data.config.updateInterval === \"number\"\n ) {\n setWaveformConfig(data.config);\n }\n } else if (data.type === \"send_text\") {\n if (sendChat) {\n sendChat(data.text);\n } else {\n console.error(\"sendChat function is not available\");\n }\n } else if (data.type === \"send_dtmf\") {\n if (localParticipant.localParticipant) {\n // Use standard DTMF code (RFC 4733)\n const standardDtmfCode = 101; // Standard DTMF payload type\n localParticipant.localParticipant.publishDtmf(\n standardDtmfCode,\n data.digit\n );\n } else {\n console.error(\"Local participant is not available for DTMF\");\n }\n }\n };\n\n // Make sure we start the port\n port.start();\n\n port.addEventListener(\"message\", handleMessage);\n\n return () => {\n port.removeEventListener(\"message\", handleMessage);\n };\n }, [port, sendChat, localParticipant]);\n\n // Send agent state updates\n useEffect(() => {\n if (port) {\n port.postMessage({ type: \"state_update\", state });\n }\n }, [state, port]);\n\n // Send agent transcriptions\n useEffect(() => {\n if (port && agentTranscription.segments.length > 0) {\n const transcriptions = agentTranscription.segments.map((segment) => ({\n id: segment.id,\n text: segment.text,\n isFinal: segment.final,\n timestamp: Date.now(),\n speaker: \"agent\" as const,\n }));\n\n port.postMessage({\n type: \"transcription_update\",\n transcriptions,\n });\n }\n }, [agentTranscription.segments, port]);\n\n // Send user transcriptions\n useEffect(() => {\n if (port && localMessages.segments.length > 0) {\n const transcriptions = localMessages.segments.map((segment) => ({\n id: segment.id,\n text: segment.text,\n isFinal: segment.final,\n timestamp: Date.now(),\n speaker: \"user\" as const,\n }));\n\n port.postMessage({\n type: \"transcription_update\",\n transcriptions,\n });\n }\n }, [localMessages.segments, port]);\n\n // Add data channel hook for function calls\n const { message: functionCallsCollected } = useDataChannel(\n \"function_calls_collected\",\n (msg) => {\n if (!port) return;\n\n const textDecoder = new TextDecoder();\n const messageString =\n msg.payload instanceof Uint8Array\n ? textDecoder.decode(msg.payload)\n : String(msg.payload);\n\n let functionCallInfo: FunctionCallInfo[];\n try {\n functionCallInfo = JSON.parse(messageString);\n\n // Send function calls to main hook via the port\n port.postMessage({\n type: \"function_calls_collected\",\n functionCalls: functionCallInfo,\n });\n } catch (e) {\n console.error(\"Failed to parse function call log:\", e);\n }\n }\n );\n\n // Add data channel hook for function call results\n const { message: functionCallsFinished } = useDataChannel(\n \"function_calls_finished\",\n (msg) => {\n if (!port) return;\n\n const textDecoder = new TextDecoder();\n const messageString =\n msg.payload instanceof Uint8Array\n ? textDecoder.decode(msg.payload)\n : String(msg.payload);\n\n let functionCallResult: FunctionCallResult[];\n try {\n functionCallResult = JSON.parse(messageString);\n\n // Send function results to main hook via the port\n port.postMessage({\n type: \"function_calls_finished\",\n functionResults: functionCallResult,\n });\n } catch (e) {\n console.error(\"Failed to parse function call result:\", e);\n }\n }\n );\n\n return null;\n}\n"],"names":["StateMonitor","_ref4","port","initialConfig","agent","state","useVoiceAssistant","send","sendChat","useChat","waveformConfig","setWaveformConfig","useState","speaker","barCount","updateInterval","agentAudioTrack","useParticipantTracks","Track","Source","Microphone","identity","agentTranscription","useTrackTranscription","agentAudioWaveform","useAudioWaveform","localParticipant","useLocalParticipant","localMessages","publication","microphoneTrack","source","participant","localAudioTrack","userAudioWaveform","useEffect","bars","postMessage","type","waveformData","handleMessage","event","data","config","text","console","error","publishDtmf","digit","start","addEventListener","removeEventListener","segments","length","transcriptions","map","segment","id","isFinal","final","timestamp","Date","now","useDataChannel","msg","textDecoder","TextDecoder","messageString","payload","Uint8Array","decode","String","functionCallInfo","JSON","parse","functionCalls","e","functionCallResult","functionResults","options","connectionDetail","setConnectionDetail","setState","transcriptMap","setTranscriptMap","Map","messages","setMessages","prevMessagesRef","useRef","processedMessageIdsRef","Set","portalRootRef","rootRef","channelRef","livekitComponentRef","waveformDataMap","setWaveformDataMap","user","waveformConfigRef","allMessages","Array","from","values","sort","a","b","messagesString","stringify","current","onMessage","filter","has","forEach","add","channel","MessageChannel","port1","onmessage","handleTranscriptionUpdate","prevMap","functionCallsId","newMap","set","name","toolCalls","functionResultsId","toolCallResults","_channelRef$current","_channelRef$current2","close","port2","useCallback","t","_prevMap$get","messageType","existingTimestamp","get","message","div","document","createElement","style","display","body","appendChild","createRoot","unmount","removeChild","connect","_ref","agentId","apiKey","dynamicVariables","metadata","Promise","resolve","fetch","method","headers","Authorization","agent_id","call_web","dynamic_variables","then","response","_temp2","_result2","json","onConnect","_temp","ok","errorText","Error","status","_catch","err","onError","reject","disconnect","onDisconnect","_ref2","messageId","undefined","warn","audioWaveform","_ref3","speakerData","slice","fill","_jsxs","jsxs","LiveKitRoom","serverUrl","token","participantToken","audio","video","onDisconnected","children","_jsx","jsx","RoomAudioRenderer","render","_Fragment"],"mappings":"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"}
1
+ {"version":3,"file":"lib.umd.js","sources":["../src/hooks/useVoxAI.tsx"],"sourcesContent":["import {\n LiveKitRoom,\n RoomAudioRenderer,\n useLocalParticipant,\n useParticipantTracks,\n useTrackTranscription,\n useVoiceAssistant,\n useChat,\n useAudioWaveform,\n useDataChannel,\n useTrackVolume,\n} from \"@livekit/components-react\";\nimport { KrispNoiseFilter } from \"@livekit/krisp-noise-filter\";\nimport { LocalAudioTrack, Track } from \"livekit-client\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { createRoot, Root } from \"react-dom/client\";\n\ntype VoxConnectionDetail = {\n serverUrl: string;\n roomName: string;\n participantName: string;\n participantToken: string;\n};\n\n/**\n * VoxAgentState\n * @description The state of the agent\n */\nexport type VoxAgentState =\n | \"disconnected\"\n | \"connecting\"\n | \"initializing\"\n | \"listening\"\n | \"thinking\"\n | \"speaking\";\n\n// API endpoint\nconst HTTPS_API_ORIGIN = \"https://www.tryvox.co/api/agent/sdk\";\n// const HTTPS_API_ORIGIN = \"http://localhost:3000/api/agent/sdk\";\n\n/**\n * Function call related types\n */\nexport interface FunctionCallInfo {\n tool_call_id: string;\n function_info: {\n name: string;\n description: string;\n arguments: Record<string, any>;\n };\n raw_arguments: string;\n arguments: Record<string, any>;\n}\n\nexport interface FunctionCallResult {\n call_info: {\n tool_call_id: string;\n function_info: {\n name: string;\n description: string;\n arguments: Record<string, any>;\n };\n raw_arguments: string;\n arguments: Record<string, any>;\n };\n result: Record<string, any> | null;\n exception: string | null;\n}\n\n/**\n * VoxMessage\n * @description The message type between the agent and the user\n */\nexport type VoxMessage = {\n id?: string;\n name: \"agent\" | \"user\" | \"tool\";\n message?: string;\n timestamp: number;\n isFinal?: boolean;\n toolCalls?: FunctionCallInfo[];\n toolCallResults?: FunctionCallResult[];\n};\n\n/**\n * VoxAIOptions\n * @description The callback functions for the useVoxAI hook\n */\nexport interface VoxAIOptions {\n onConnect?: () => void;\n onDisconnect?: () => void;\n onError?: (error: Error) => void;\n onMessage?: (message: VoxMessage) => void;\n enableNoiseCancellation?: boolean;\n}\n\n// Message channel event types\ntype MessageChannelEvent =\n | { type: \"state_update\"; state: VoxAgentState }\n | { type: \"transcription_update\"; transcriptions: TranscriptionSegment[] }\n | {\n type: \"waveform_update\";\n waveformData: number[];\n speaker: \"agent\" | \"user\";\n }\n | { type: \"function_calls_collected\"; functionCalls: FunctionCallInfo[] }\n | { type: \"function_calls_finished\"; functionResults: FunctionCallResult[] };\n\ntype TranscriptionSegment = {\n id: string;\n text: string;\n isFinal: boolean;\n timestamp: number;\n speaker: \"agent\" | \"user\";\n};\n\n/**\n * ConnectParams\n * @param agentId - The agent ID\n * @param apiKey - The API key\n * @param dynamicVariables - The dynamic variables\n * @param metadata - 이 메타데이터는 아웃바운드 웹훅, 통화 기록에 포함됩니다.\n */\nexport interface ConnectParams {\n agentId: string;\n apiKey: string;\n dynamicVariables?: Record<string, any>;\n metadata?: Record<string, any>;\n}\n\n/**\n * useVoxAI\n * @description The hook for integrating with vox.ai voice assistant\n * @param options - The options for the useVoxAI hook\n * @returns The useVoxAI hook\n * @example\n * const { connect, disconnect, state, messages, send } = useVoxAI({\n * onConnect: () => console.log(\"Connected\"),\n * onDisconnect: () => console.log(\"Disconnected\"),\n * onError: (error) => console.error(\"Error:\", error),\n * onMessage: (message) => console.log(\"Message:\", message),\n * });\n */\nexport function useVoxAI(options: VoxAIOptions = {}) {\n // Connection state\n const [connectionDetail, setConnectionDetail] =\n useState<VoxConnectionDetail | null>(null);\n const [state, setState] = useState<VoxAgentState>(\"disconnected\");\n\n // Message handling\n const [transcriptMap, setTranscriptMap] = useState<Map<string, VoxMessage>>(\n new Map()\n );\n const [messages, setMessages] = useState<VoxMessage[]>([]);\n const prevMessagesRef = useRef<string>(\"\");\n\n // Track which messages we've already sent to the onMessage callback\n const processedMessageIdsRef = useRef<Set<string>>(new Set());\n\n // DOM manipulation for LiveKit portal\n const portalRootRef = useRef<HTMLDivElement | null>(null);\n const rootRef = useRef<Root | null>(null);\n\n // Communication channel\n const channelRef = useRef<MessageChannel | null>(null);\n\n // Add this near the start of your useVoxAI hook\n const livekitComponentRef = useRef<React.ReactNode>(null);\n\n // Replace the single waveform state with a map for multiple speakers\n const [waveformDataMap, setWaveformDataMap] = useState<\n Record<string, number[]>\n >({\n agent: [],\n user: [],\n });\n\n // Add back the waveform config reference\n const waveformConfigRef = useRef<{\n speaker?: \"agent\" | \"user\";\n barCount: number;\n updateInterval: number;\n } | null>(null);\n\n // Add a new state to track microphone status\n const [isMicEnabled, setIsMicEnabled] = useState<boolean>(true);\n\n // Update messages whenever transcriptMap changes\n useEffect(() => {\n const allMessages = Array.from(transcriptMap.values()).sort(\n (a, b) => a.timestamp - b.timestamp\n );\n\n // Only update if the messages have actually changed\n const messagesString = JSON.stringify(allMessages);\n if (messagesString !== prevMessagesRef.current) {\n prevMessagesRef.current = messagesString;\n setMessages(allMessages);\n\n // Only trigger onMessage for new final messages that haven't been processed yet\n if (options.onMessage) {\n allMessages\n .filter(\n (msg) =>\n msg.isFinal &&\n msg.id &&\n !processedMessageIdsRef.current.has(msg.id)\n )\n .forEach((msg) => {\n if (msg.id) {\n // Mark this message as processed\n processedMessageIdsRef.current.add(msg.id);\n // Call the callback\n options.onMessage?.(msg);\n }\n });\n }\n }\n }, [transcriptMap, options.onMessage]);\n\n // Initialize message channel - ensure ports are properly connected\n useEffect(() => {\n const channel = new MessageChannel();\n\n channel.port1.onmessage = (e) => {\n const data = e.data as MessageChannelEvent;\n\n if (data.type === \"state_update\") {\n setState(data.state);\n } else if (data.type === \"transcription_update\") {\n handleTranscriptionUpdate(data.transcriptions);\n } else if (data.type === \"waveform_update\" && data.speaker) {\n // Store the waveform data for the specific speaker\n setWaveformDataMap((prevMap) => ({\n ...prevMap,\n [data.speaker]: data.waveformData,\n }));\n } else if (\n data.type === \"function_calls_collected\" &&\n data.functionCalls\n ) {\n // Handle function calls\n const functionCallsId = `function-calls-${Date.now()}`;\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n newMap.set(functionCallsId, {\n id: functionCallsId,\n name: \"tool\",\n toolCalls: data.functionCalls,\n timestamp: Date.now(),\n isFinal: true,\n });\n return newMap;\n });\n } else if (\n data.type === \"function_calls_finished\" &&\n data.functionResults\n ) {\n // Handle function results\n const functionResultsId = `function-results-${Date.now()}`;\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n newMap.set(functionResultsId, {\n id: functionResultsId,\n name: \"tool\",\n toolCallResults: data.functionResults,\n timestamp: Date.now(),\n isFinal: true,\n });\n return newMap;\n });\n }\n };\n\n // Start the port\n channel.port1.start();\n\n // Store the channel reference\n channelRef.current = channel;\n\n return () => {\n channelRef.current?.port1.close();\n channelRef.current?.port2.close();\n channelRef.current = null;\n };\n }, []);\n\n // Process incoming transcriptions\n const handleTranscriptionUpdate = useCallback(\n (transcriptions: TranscriptionSegment[]) => {\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n\n transcriptions.forEach((t) => {\n const messageType = t.speaker === \"agent\" ? \"agent\" : \"user\";\n // Use existing timestamp if we already have this segment\n const existingTimestamp = prevMap.get(t.id)?.timestamp || t.timestamp;\n\n newMap.set(t.id, {\n id: t.id,\n name: messageType,\n message: t.text,\n timestamp: existingTimestamp,\n isFinal: t.isFinal,\n });\n });\n\n return newMap;\n });\n },\n []\n );\n\n // Set up DOM portal for LiveKit\n useEffect(() => {\n const div = document.createElement(\"div\");\n div.style.display = \"none\";\n document.body.appendChild(div);\n portalRootRef.current = div;\n rootRef.current = createRoot(div);\n\n return () => {\n if (rootRef.current) {\n rootRef.current.unmount();\n }\n if (portalRootRef.current) {\n document.body.removeChild(portalRootRef.current);\n }\n };\n }, []);\n\n // Connect to VoxAI service - updated to include dynamicVariables\n /**\n * Initiates a connection to the VoxAI service\n * @param {ConnectParams} options - Connection parameters\n * @returns {Promise} - Resolves when the connection is successful, rejects if:\n * 1. The connection is already in progress (state is not \"disconnected\")\n * 2. The server returns an error\n * 3. Any other error occurs during the connection process\n */\n const connect = useCallback(\n async ({ agentId, apiKey, dynamicVariables, metadata }: ConnectParams) => {\n try {\n // Prevent connecting if already in a connection state\n if (state !== \"disconnected\") {\n const errorMessage = `Connection attempt rejected: Already in a connection state (${state})`;\n console.warn(errorMessage);\n\n // Call the onError callback if provided\n if (options.onError) {\n options.onError(new Error(errorMessage));\n }\n\n // Return a rejected promise\n return Promise.reject(new Error(errorMessage));\n }\n\n setState(\"connecting\");\n\n const response = await fetch(HTTPS_API_ORIGIN, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n agent_id: agentId,\n metadata: {\n call_web: {\n dynamic_variables: dynamicVariables || {},\n metadata: metadata || {},\n },\n },\n }),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `Connection failed (${response.status}): ${errorText}`\n );\n }\n\n const data = await response.json();\n setConnectionDetail(data);\n\n if (options.onConnect) {\n options.onConnect();\n }\n } catch (err) {\n // Reset state on error\n setConnectionDetail(null);\n setTranscriptMap(new Map());\n setMessages([]);\n setState(\"disconnected\");\n\n const error = err instanceof Error ? err : new Error(String(err));\n\n if (options.onError) {\n options.onError(error);\n }\n }\n },\n [options]\n );\n\n // Disconnect from VoxAI service\n const disconnect = useCallback(() => {\n setConnectionDetail(null);\n setTranscriptMap(new Map());\n setMessages([]);\n setState(\"disconnected\");\n\n if (options.onDisconnect) {\n options.onDisconnect();\n }\n }, [options]);\n\n // Update the send function with debugging and error checking\n const send = useCallback(\n ({ message, digit }: { message?: string; digit?: number }) => {\n if (state === \"disconnected\") {\n console.warn(\"Cannot send message: Not connected to a conversation\");\n return;\n }\n\n if (message) {\n // Add the message to our local transcript map for immediate feedback\n const messageId = `user-text-${Date.now()}`;\n setTranscriptMap((prevMap) => {\n const newMap = new Map(prevMap);\n newMap.set(messageId, {\n id: messageId,\n name: \"user\",\n message: message,\n timestamp: Date.now(),\n isFinal: true,\n });\n return newMap;\n });\n\n // Send message through the message channel to StateMonitor\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"send_text\",\n text: message,\n });\n } else {\n console.error(\"No message channel available to send message\");\n }\n }\n\n if (digit !== undefined) {\n // Send DTMF through the message channel to StateMonitor\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"send_dtmf\",\n digit: digit,\n });\n } else {\n console.error(\"No message channel available to send DTMF\");\n }\n }\n },\n [state]\n );\n\n // Update the audioWaveform function to return data for the requested speaker\n const audioWaveform = useCallback(\n ({\n speaker = \"agent\",\n barCount = 10,\n updateInterval = 20,\n }: {\n speaker?: \"agent\" | \"user\";\n barCount?: number;\n updateInterval?: number;\n }): number[] => {\n // Store the waveform configuration for StateMonitor to use\n waveformConfigRef.current = { speaker, barCount, updateInterval };\n\n // Send the configuration to StateMonitor if channel is available\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"waveform_config\",\n config: { speaker, barCount, updateInterval },\n });\n }\n\n // Get the waveform data for the requested speaker\n const speakerData = waveformDataMap[speaker] || [];\n\n // Return the current waveform data, or a default array if no data yet\n return speakerData.length > 0\n ? speakerData.slice(0, barCount) // Ensure we return only barCount items\n : Array(barCount).fill(0);\n },\n [waveformDataMap]\n );\n\n // Add toggleMic function that will be exposed in the hook's return value\n const toggleMic = useCallback(\n (value: boolean) => {\n setIsMicEnabled(value);\n\n // Send the command to the StateMonitor through the message channel\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"toggle_mic\",\n enabled: value,\n });\n } else {\n console.error(\"No message channel available to toggle microphone\");\n }\n },\n [options]\n );\n\n // Add setVolume function that will be exposed in the hook's return value\n const setVolume = useCallback((volume: number) => {\n // Validate volume (0-1 range)\n const validVolume = Math.min(Math.max(volume, 0), 1);\n\n // Send the command to the StateMonitor through the message channel\n if (channelRef.current) {\n channelRef.current.port1.postMessage({\n type: \"set_volume\",\n volume: validVolume,\n });\n } else {\n console.error(\"No message channel available to set volume\");\n }\n }, []);\n\n // Modify the useEffect hook that renders the LiveKit component\n useEffect(() => {\n if (!rootRef.current) return;\n\n if (connectionDetail) {\n // Only create a new LiveKit component if we don't have one or connection details changed\n if (!livekitComponentRef.current) {\n if (channelRef.current) {\n // Start port2 before passing it to StateMonitor\n channelRef.current.port2.start();\n }\n\n livekitComponentRef.current = (\n <LiveKitRoom\n serverUrl={connectionDetail.serverUrl}\n token={connectionDetail.participantToken}\n audio={true}\n video={false}\n connect={true}\n onDisconnected={disconnect}\n onError={(error) => {\n console.error(\"LiveKit connection error:\", error);\n disconnect();\n if (options.onError) {\n options.onError(\n new Error(`LiveKit connection error: ${error.message}`)\n );\n }\n }}\n >\n <RoomAudioRenderer />\n {channelRef.current && (\n <StateMonitor\n port={channelRef.current.port2}\n initialConfig={\n waveformConfigRef.current || {\n barCount: 10,\n updateInterval: 20,\n }\n }\n enableNoiseCancellation={\n options.enableNoiseCancellation ?? true\n }\n />\n )}\n </LiveKitRoom>\n );\n }\n\n rootRef.current.render(livekitComponentRef.current);\n } else {\n livekitComponentRef.current = null;\n rootRef.current.render(<></>);\n }\n }, [connectionDetail, disconnect, options.onError]);\n\n /**\n * Returns the VoxAI interface for controlling the conversation\n * @returns {Object} VoxAI interface\n * @property {Function} connect - Initiates a connection to the VoxAI service. Will reject with an error if already connected or in the process of connecting.\n * @property {Function} disconnect - Terminates the connection to the VoxAI service.\n * @property {VoxAgentState} state - The current state of the agent.\n * @property {VoxMessage[]} messages - An array of messages exchanged in the conversation.\n * @property {Function} send - Sends a message or DTMF digit to the agent.\n * @property {Function} audioWaveform - Returns audio waveform data for UI visualization.\n * @property {Function} toggleMic - Toggles the microphone on/off.\n * @property {Function} setVolume - Sets the volume of the agent's audio (0-1).\n */\n return {\n connect,\n disconnect,\n state,\n messages,\n send,\n audioWaveform,\n toggleMic,\n setVolume,\n };\n}\n\n/**\n * Component that monitors LiveKit state and communicates back to the main hook\n */\nfunction StateMonitor({\n port,\n initialConfig,\n enableNoiseCancellation = true,\n}: {\n port: MessagePort | undefined;\n initialConfig: {\n speaker?: \"agent\" | \"user\";\n barCount: number;\n updateInterval: number;\n };\n enableNoiseCancellation?: boolean;\n}) {\n const { agent, state } = useVoiceAssistant();\n const { send: sendChat } = useChat();\n\n // Initialize waveform config with the passed initial values, defaulting to \"agent\" if not specified\n const [waveformConfig, setWaveformConfig] = useState({\n speaker: initialConfig.speaker || \"agent\",\n barCount: initialConfig.barCount,\n updateInterval: initialConfig.updateInterval,\n });\n\n // Agent transcriptions\n const agentAudioTrack = useParticipantTracks(\n [Track.Source.Microphone],\n agent?.identity\n )[0];\n const agentTranscription = useTrackTranscription(agentAudioTrack);\n\n // Use the current config for the waveform, applying different settings based on speaker\n const agentAudioWaveform = useAudioWaveform(agentAudioTrack, {\n barCount:\n waveformConfig.speaker === \"agent\" ? waveformConfig.barCount : 120, // default if not the selected speaker\n updateInterval:\n waveformConfig.speaker === \"agent\" ? waveformConfig.updateInterval : 20,\n });\n\n // User transcriptions\n const localParticipant = useLocalParticipant();\n const localMessages = useTrackTranscription({\n publication: localParticipant.microphoneTrack,\n source: Track.Source.Microphone,\n participant: localParticipant.localParticipant,\n });\n const localAudioTrack = useParticipantTracks(\n [Track.Source.Microphone],\n localParticipant.localParticipant.identity\n )[0];\n const userAudioWaveform = useAudioWaveform(localAudioTrack, {\n barCount: waveformConfig.speaker === \"user\" ? waveformConfig.barCount : 120, // default if not the selected speaker\n updateInterval:\n waveformConfig.speaker === \"user\" ? waveformConfig.updateInterval : 20,\n });\n\n // Apply Krisp noise cancellation to the local microphone track\n useEffect(() => {\n const applyKrispNoiseFilter = async () => {\n const track = localParticipant.microphoneTrack?.track;\n if (track && track instanceof LocalAudioTrack) {\n try {\n // Only apply noise filter if enabled\n if (enableNoiseCancellation) {\n await track.setProcessor(KrispNoiseFilter());\n } else {\n // Remove any existing processor if noise cancellation is disabled\n await track.stopProcessor();\n }\n } catch (error) {\n console.error(\"Failed to set audio processor:\", error);\n }\n }\n };\n\n applyKrispNoiseFilter();\n }, [localParticipant.microphoneTrack, enableNoiseCancellation]);\n\n // Add separate effects to send agent and user waveform data\n useEffect(() => {\n if (!port || !agentAudioWaveform || !agentAudioWaveform.bars) return;\n\n // Send the agent waveform data\n port.postMessage({\n type: \"waveform_update\",\n waveformData: agentAudioWaveform.bars,\n speaker: \"agent\",\n });\n }, [port, agentAudioWaveform]);\n\n useEffect(() => {\n if (!port || !userAudioWaveform || !userAudioWaveform.bars) return;\n\n // Send the user waveform data\n port.postMessage({\n type: \"waveform_update\",\n waveformData: userAudioWaveform.bars,\n speaker: \"user\",\n });\n }, [port, userAudioWaveform]);\n\n // Listen for messages including config updates and mic toggle commands\n useEffect(() => {\n if (!port) return;\n\n const handleMessage = (event: MessageEvent) => {\n const data = event.data;\n\n if (data.type === \"waveform_config\" && data.config) {\n // Verify we have both required properties before updating\n if (\n typeof data.config.barCount === \"number\" &&\n typeof data.config.updateInterval === \"number\"\n ) {\n setWaveformConfig(data.config);\n }\n } else if (data.type === \"send_text\") {\n if (sendChat) {\n sendChat(data.text);\n } else {\n console.error(\"sendChat function is not available\");\n }\n } else if (data.type === \"send_dtmf\") {\n if (localParticipant.localParticipant) {\n // Use standard DTMF code (RFC 4733)\n const standardDtmfCode = 101; // Standard DTMF payload type\n localParticipant.localParticipant.publishDtmf(\n standardDtmfCode,\n data.digit\n );\n } else {\n console.error(\"Local participant is not available for DTMF\");\n }\n } else if (\n data.type === \"toggle_mic\" &&\n typeof data.enabled === \"boolean\"\n ) {\n // Handle microphone toggle\n if (localParticipant.localParticipant) {\n localParticipant.localParticipant\n .setMicrophoneEnabled(data.enabled)\n .catch((error) => {\n console.error(\"Failed to toggle microphone:\", error);\n });\n } else {\n console.error(\"Local participant is not available for mic toggle\");\n }\n } else if (\n data.type === \"set_volume\" &&\n typeof data.volume === \"number\"\n ) {\n // Handle volume control\n if (agent) {\n // The agent is a RemoteParticipant, so we can call setVolume directly\n try {\n agent.setVolume(data.volume);\n console.log(`Set agent volume to ${data.volume}`);\n } catch (error) {\n console.error(\"Failed to set agent volume:\", error);\n }\n } else {\n console.error(\"Agent is not available for volume control\");\n }\n }\n };\n\n // Make sure we start the port\n port.start();\n\n port.addEventListener(\"message\", handleMessage);\n\n return () => {\n port.removeEventListener(\"message\", handleMessage);\n };\n }, [port, sendChat, localParticipant, agent]);\n\n // Send agent state updates\n useEffect(() => {\n if (port) {\n port.postMessage({ type: \"state_update\", state });\n }\n }, [state, port]);\n\n // Send agent transcriptions\n useEffect(() => {\n if (port && agentTranscription.segments.length > 0) {\n const transcriptions = agentTranscription.segments.map((segment) => ({\n id: segment.id,\n text: segment.text,\n isFinal: segment.final,\n timestamp: Date.now(),\n speaker: \"agent\" as const,\n }));\n\n port.postMessage({\n type: \"transcription_update\",\n transcriptions,\n });\n }\n }, [agentTranscription.segments, port]);\n\n // Send user transcriptions\n useEffect(() => {\n if (port && localMessages.segments.length > 0) {\n const transcriptions = localMessages.segments.map((segment) => ({\n id: segment.id,\n text: segment.text,\n isFinal: segment.final,\n timestamp: Date.now(),\n speaker: \"user\" as const,\n }));\n\n port.postMessage({\n type: \"transcription_update\",\n transcriptions,\n });\n }\n }, [localMessages.segments, port]);\n\n // Add data channel hook for function calls\n const { message: functionCallsCollected } = useDataChannel(\n \"function_calls_collected\",\n (msg) => {\n if (!port) return;\n\n const textDecoder = new TextDecoder();\n const messageString =\n msg.payload instanceof Uint8Array\n ? textDecoder.decode(msg.payload)\n : String(msg.payload);\n\n let functionCallInfo: FunctionCallInfo[];\n try {\n functionCallInfo = JSON.parse(messageString);\n\n // Send function calls to main hook via the port\n port.postMessage({\n type: \"function_calls_collected\",\n functionCalls: functionCallInfo,\n });\n } catch (e) {\n console.error(\"Failed to parse function call log:\", e);\n }\n }\n );\n\n // Add data channel hook for function call results\n const { message: functionCallsFinished } = useDataChannel(\n \"function_calls_finished\",\n (msg) => {\n if (!port) return;\n\n const textDecoder = new TextDecoder();\n const messageString =\n msg.payload instanceof Uint8Array\n ? textDecoder.decode(msg.payload)\n : String(msg.payload);\n\n let functionCallResult: FunctionCallResult[];\n try {\n functionCallResult = JSON.parse(messageString);\n\n // Send function results to main hook via the port\n port.postMessage({\n type: \"function_calls_finished\",\n functionResults: functionCallResult,\n });\n } catch (e) {\n console.error(\"Failed to parse function call result:\", e);\n }\n }\n );\n\n return null;\n}\n"],"names":["_catch","body","recover","result","e","then","StateMonitor","_ref4","port","initialConfig","enableNoiseCancellation","agent","state","useVoiceAssistant","send","sendChat","useChat","waveformConfig","setWaveformConfig","useState","speaker","barCount","updateInterval","agentAudioTrack","useParticipantTracks","Track","Source","Microphone","identity","agentTranscription","useTrackTranscription","agentAudioWaveform","useAudioWaveform","localParticipant","useLocalParticipant","localMessages","publication","microphoneTrack","source","participant","localAudioTrack","userAudioWaveform","useEffect","applyKrispNoiseFilter","_localParticipant$mic","track","Promise","resolve","LocalAudioTrack","_temp3","setProcessor","KrispNoiseFilter","stopProcessor","error","console","reject","bars","postMessage","type","waveformData","handleMessage","event","data","config","text","publishDtmf","digit","enabled","setMicrophoneEnabled","catch","volume","setVolume","log","start","addEventListener","removeEventListener","segments","length","transcriptions","map","segment","id","isFinal","final","timestamp","Date","now","useDataChannel","msg","textDecoder","TextDecoder","messageString","payload","Uint8Array","decode","String","functionCallInfo","JSON","parse","functionCalls","functionCallResult","functionResults","options","connectionDetail","setConnectionDetail","setState","transcriptMap","setTranscriptMap","Map","messages","setMessages","prevMessagesRef","useRef","processedMessageIdsRef","Set","portalRootRef","rootRef","channelRef","livekitComponentRef","waveformDataMap","setWaveformDataMap","user","waveformConfigRef","isMicEnabled","setIsMicEnabled","allMessages","Array","from","values","sort","a","b","messagesString","stringify","current","onMessage","filter","has","forEach","add","channel","MessageChannel","port1","onmessage","handleTranscriptionUpdate","prevMap","functionCallsId","newMap","set","name","toolCalls","functionResultsId","toolCallResults","_channelRef$current","_channelRef$current2","close","port2","useCallback","t","_prevMap$get","messageType","existingTimestamp","get","message","div","document","createElement","style","display","appendChild","createRoot","unmount","removeChild","connect","_ref","agentId","apiKey","dynamicVariables","metadata","errorMessage","warn","onError","Error","fetch","method","headers","Authorization","agent_id","call_web","dynamic_variables","response","_temp2","_result2","_exit","json","onConnect","_temp","ok","errorText","status","err","disconnect","onDisconnect","_ref2","messageId","undefined","audioWaveform","_ref3","speakerData","slice","fill","toggleMic","value","validVolume","Math","min","max","_options$enableNoiseC","_jsxs","jsxs","LiveKitRoom","serverUrl","token","participantToken","audio","video","onDisconnected","children","_jsx","jsx","RoomAudioRenderer","render","_Fragment"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vox-ai/react",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "vox.ai React Library",
5
5
  "main": "./dist/lib.umd.js",
6
6
  "module": "./dist/lib.module.js",