@zeroweight/react 0.2.34 → 0.2.35

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.
@@ -1 +1 @@
1
- {"version":3,"file":"useAvatarSession.d.ts","sourceRoot":"","sources":["../src/useAvatarSession.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAgB7C,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,yDAAyD;IACzD,GAAG,CAAC,EAAE,aAAa,CAAC;IACpB,8EAA8E;IAC9E,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,0BAA0B;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gDAAgD;IAChD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,+CAA+C;IAC/C,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAmB;IAElC,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAGrD,QAAQ,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACpC,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;IAGhC,aAAa,EAAE,aAAa,CAAC;IAC7B,gBAAgB,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC3D,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC/C,gBAAgB,EAAE,OAAO,CAAC;IAC1B,aAAa,EAAE,OAAO,CAAC;IAGvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IAGnB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IAGxC,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,wBAAwB,EAAE,OAAO,CAAC;IAGlC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,WAAW,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,SAAS,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,iBAAiB,EAAE,MAAM,IAAI,CAAC;IAC9B,wCAAwC;IACxC,aAAa,EAAE,MAAM,IAAI,CAAC;IAG1B,mBAAmB,EAAE,CAAC,UAAU,EAAE,OAAO,KAAK,IAAI,CAAC;CACpD;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,mBAAmB,GAAG,mBAAmB,CAgXjF"}
1
+ {"version":3,"file":"useAvatarSession.d.ts","sourceRoot":"","sources":["../src/useAvatarSession.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAgB7C,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,yDAAyD;IACzD,GAAG,CAAC,EAAE,aAAa,CAAC;IACpB,8EAA8E;IAC9E,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,0BAA0B;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gDAAgD;IAChD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,+CAA+C;IAC/C,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAmB;IAElC,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAGrD,QAAQ,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACpC,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;IAGhC,aAAa,EAAE,aAAa,CAAC;IAC7B,gBAAgB,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC3D,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC/C,gBAAgB,EAAE,OAAO,CAAC;IAC1B,aAAa,EAAE,OAAO,CAAC;IAGvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IAGnB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IAGxC,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,wBAAwB,EAAE,OAAO,CAAC;IAGlC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,WAAW,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,SAAS,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,iBAAiB,EAAE,MAAM,IAAI,CAAC;IAC9B,wCAAwC;IACxC,aAAa,EAAE,MAAM,IAAI,CAAC;IAG1B,mBAAmB,EAAE,CAAC,UAAU,EAAE,OAAO,KAAK,IAAI,CAAC;CACpD;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,mBAAmB,GAAG,mBAAmB,CAoXjF"}
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react"),B=require("@zeroweight/renderer"),t=require("react/jsx-runtime"),D=require("@livekit/components-react");require("@livekit/components-styles");const E=require("lucide-react"),Se=3e4,ke=120,Ae="wss://prod-project-pazuyq69.livekit.cloud",G="https://api.zeroweight.ai/api/v1";function ne(m){const{avatarId:a,apiKey:n=null,api:u,turnOffMicWhenAISpeaking:d=!0,livekitUrl:y=Ae,sessionDuration:f=ke,inactivityTimeout:A=Se}=m,T=y,b=e.useMemo(()=>{if(u)return u;const r=n?{"X-ZW-Api-Key":n}:void 0;return{getBundle:async g=>{const v=await fetch(`${G}/avatars/bundle/${encodeURIComponent(g)}`,{headers:r});if(!v.ok)throw new Error(`Failed to fetch avatar bundle (${v.status} ${v.statusText})`);return v.json()},getLiveKitToken:async g=>{const v=await fetch(`${G}/livekit/getToken?avatar_id=${g}&name=anonymuous`,{headers:r});if(!v.ok)throw new Error(`Failed to fetch LiveKit token (${v.status} ${v.statusText})`);return v.json()}}},[u,n]),h=e.useRef(null),C=e.useRef(null),i=e.useRef(null),l=e.useRef(null),[x,w]=e.useState("idle"),[p,M]=e.useState(null),[c,z]=e.useState(new Set(["listening"])),[s,o]=e.useState({listening:{kind:"looped"},speaking:{kind:"looped"}}),[P,I]=e.useState(!1),[L,$]=e.useState(null),[O,K]=e.useState(!1),[U,H]=e.useState(!1),[oe,_]=e.useState(f),j=e.useRef(null),W=e.useRef(()=>{}),[ce,Q]=e.useState(!1),[le,J]=e.useState(.8),R=e.useRef(null),q=x==="ready",Z=e.useCallback(()=>{const r=h.current;if(!r)return null;let g=r.querySelector("canvas");return g||(g=document.createElement("canvas"),g.style.width="100%",g.style.height="100%",g.style.display="block",r.appendChild(g)),C.current=g,g},[]);e.useEffect(()=>{let r=!1;return(async()=>{const v=Z();if(!v)return;const S=new B.ZeroWeightRenderer;i.current=S;const V=new B.ActionQueue((k,F)=>{S.play(k,F)});l.current=V,S.on("stateChanged",k=>{r||w(k)}),S.on("dimensions",(k,F)=>{r||M({width:k,height:F})}),S.on("actionLoaded",k=>{r||z(F=>{const Y=new Set(F);return Y.add(k),Y})}),S.on("allActionsLoaded",()=>{r||I(!1)}),S.on("ready",()=>{r||(o(S.getActionMetadata()),V.setActionMetadata(S.getActionMetadata()))});try{I(!0);const k=await b.getBundle(a);if(r||(await S.init(v,{payload:k.payload}),r))return;o(S.getActionMetadata()),V.setActionMetadata(S.getActionMetadata())}catch(k){console.error("[useAvatarSession] Init failed:",k)}})(),()=>{r=!0,i.current?.destroy(),i.current=null,l.current=null}},[a,Z,b]);const X=e.useRef(!1);e.useEffect(()=>{q&&i.current&&c.has("wave_hand")&&!X.current&&(i.current.play("wave_hand","listening"),X.current=!0)},[q,c]);const ue=e.useCallback(async()=>{if(!(O||U)){K(!0);try{await navigator.mediaDevices.getUserMedia({audio:!0});const r=await b.getLiveKitToken(a);$(r.token)}catch(r){console.error("[useAvatarSession] Failed to connect:",r),K(!1)}}},[O,U,a,b]),N=e.useCallback(()=>{j.current&&(clearInterval(j.current),j.current=null),_(f),R.current&&(clearTimeout(R.current),R.current=null),$(null),H(!1),K(!1),l.current?.forceListening()},[f]);e.useEffect(()=>{W.current=N},[N]);const de=e.useCallback(()=>{H(!0),K(!1)},[]),fe=e.useCallback(()=>{j.current&&clearInterval(j.current),_(f),j.current=setInterval(()=>{_(r=>r<=1?(clearInterval(j.current),j.current=null,setTimeout(()=>W.current(),0),0):r-1)},1e3)},[f]),pe=e.useCallback(r=>{const g=Math.floor(r/60).toString().padStart(2,"0"),v=(r%60).toString().padStart(2,"0");return`${g}:${v}`},[]);e.useEffect(()=>()=>{j.current&&clearInterval(j.current)},[]);const ge=e.useCallback(r=>{if(!r){R.current&&(clearTimeout(R.current),R.current=null);return}R.current||(R.current=setTimeout(()=>{R.current=null,W.current()},A))},[A]),he=e.useCallback(()=>{Q(r=>!r)},[]),ve=e.useCallback(r=>{Q(r)},[]),me=e.useCallback(r=>{J(r)},[]),be=e.useCallback(()=>{J(r=>r>0?0:1)},[]),xe=e.useCallback(()=>{i.current?.interrupt()},[]),ye=e.useCallback(r=>{i.current?.play(r,"listening")},[]);return{containerRef:h,renderer:i.current,actionQueue:l.current,rendererState:x,avatarDimensions:p,loadedActions:c,actionMetadata:s,isLoadingActions:P,isEngineReady:q,token:L,isConnecting:O,isConnected:U,livekitUrl:T,timeRemaining:oe,formatTime:pe,micMuted:ce,volume:le,turnOffMicWhenAISpeaking:d,connect:ue,disconnect:N,toggleMic:he,setMicMuted:ve,setVolume:me,toggleVolume:be,interrupt:xe,runAction:ye,startSessionTimer:fe,markConnected:de,setInactivityActive:ge}}const re=({session:m,style:a,loadingContent:n})=>t.jsxs("div",{style:{position:"absolute",inset:0,width:"100%",height:"100%",zIndex:0,pointerEvents:"auto",display:"flex",justifyContent:"center",alignItems:"center",overflow:"hidden",...a},children:[t.jsx("style",{children:`
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react"),B=require("@zeroweight/renderer"),t=require("react/jsx-runtime"),D=require("@livekit/components-react");require("@livekit/components-styles");const M=require("lucide-react"),Se=3e4,ke=120,Ae="wss://prod-project-pazuyq69.livekit.cloud",Y="https://api.zeroweight.ai/api/v1",we=()=>{const u=["Happy","Swift","Bright","Cool","Smart"],a=["User","Guest","Visitor","Agent","Caller"],n=Math.floor(Math.random()*1e3);return`${u[Math.floor(Math.random()*u.length)]}${a[Math.floor(Math.random()*a.length)]}${n}`};function ne(u){const{avatarId:a,apiKey:n=null,api:f,turnOffMicWhenAISpeaking:m=!0,livekitUrl:y=Ae,sessionDuration:p=ke,inactivityTimeout:A=Se}=u,T=y,b=e.useMemo(()=>{if(f)return f;const r=n?{"X-ZW-Api-Key":n}:void 0;return{getBundle:async h=>{const S=await fetch(`${Y}/avatars/bundle/${encodeURIComponent(h)}`,{headers:r});if(!S.ok)throw new Error(`Failed to fetch avatar bundle (${S.status} ${S.statusText})`);return S.json()},getLiveKitToken:async h=>{const S=new URLSearchParams({avatar_id:h,name:we()}),l=await fetch(`${Y}/livekit/getToken?${S.toString()}`,{headers:r});if(!l.ok)throw new Error(`Failed to fetch LiveKit token (${l.status} ${l.statusText})`);return l.json()}}},[f,n]),v=e.useRef(null),C=e.useRef(null),i=e.useRef(null),d=e.useRef(null),[x,w]=e.useState("idle"),[g,E]=e.useState(null),[c,z]=e.useState(new Set(["listening"])),[s,o]=e.useState({listening:{kind:"looped"},speaking:{kind:"looped"}}),[F,I]=e.useState(!1),[L,P]=e.useState(null),[K,$]=e.useState(!1),[O,H]=e.useState(!1),[oe,_]=e.useState(p),j=e.useRef(null),N=e.useRef(()=>{}),[ce,Q]=e.useState(!1),[le,J]=e.useState(.8),R=e.useRef(null),W=x==="ready",Z=e.useCallback(()=>{const r=v.current;if(!r)return null;let h=r.querySelector("canvas");return h||(h=document.createElement("canvas"),h.style.width="100%",h.style.height="100%",h.style.display="block",r.appendChild(h)),C.current=h,h},[]);e.useEffect(()=>{let r=!1;return(async()=>{const S=Z();if(!S)return;const l=new B.ZeroWeightRenderer;i.current=l;const q=new B.ActionQueue((k,U)=>{l.play(k,U)});d.current=q,l.on("stateChanged",k=>{r||w(k)}),l.on("dimensions",(k,U)=>{r||E({width:k,height:U})}),l.on("actionLoaded",k=>{r||z(U=>{const X=new Set(U);return X.add(k),X})}),l.on("allActionsLoaded",()=>{r||I(!1)}),l.on("ready",()=>{r||(o(l.getActionMetadata()),q.setActionMetadata(l.getActionMetadata()))});try{I(!0);const k=await b.getBundle(a);if(r||(await l.init(S,{payload:k.payload}),r))return;o(l.getActionMetadata()),q.setActionMetadata(l.getActionMetadata())}catch(k){console.error("[useAvatarSession] Init failed:",k)}})(),()=>{r=!0,i.current?.destroy(),i.current=null,d.current=null}},[a,Z,b]);const G=e.useRef(!1);e.useEffect(()=>{W&&i.current&&c.has("wave_hand")&&!G.current&&(i.current.play("wave_hand","listening"),G.current=!0)},[W,c]);const ue=e.useCallback(async()=>{if(!(K||O)){$(!0);try{await navigator.mediaDevices.getUserMedia({audio:!0});const r=await b.getLiveKitToken(a);P(r.token)}catch(r){console.error("[useAvatarSession] Failed to connect:",r),$(!1)}}},[K,O,a,b]),V=e.useCallback(()=>{j.current&&(clearInterval(j.current),j.current=null),_(p),R.current&&(clearTimeout(R.current),R.current=null),P(null),H(!1),$(!1),d.current?.forceListening()},[p]);e.useEffect(()=>{N.current=V},[V]);const de=e.useCallback(()=>{H(!0),$(!1)},[]),fe=e.useCallback(()=>{j.current&&clearInterval(j.current),_(p),j.current=setInterval(()=>{_(r=>r<=1?(clearInterval(j.current),j.current=null,setTimeout(()=>N.current(),0),0):r-1)},1e3)},[p]),pe=e.useCallback(r=>{const h=Math.floor(r/60).toString().padStart(2,"0"),S=(r%60).toString().padStart(2,"0");return`${h}:${S}`},[]);e.useEffect(()=>()=>{j.current&&clearInterval(j.current)},[]);const ge=e.useCallback(r=>{if(!r){R.current&&(clearTimeout(R.current),R.current=null);return}R.current||(R.current=setTimeout(()=>{R.current=null,N.current()},A))},[A]),he=e.useCallback(()=>{Q(r=>!r)},[]),ve=e.useCallback(r=>{Q(r)},[]),me=e.useCallback(r=>{J(r)},[]),be=e.useCallback(()=>{J(r=>r>0?0:1)},[]),xe=e.useCallback(()=>{i.current?.interrupt()},[]),ye=e.useCallback(r=>{i.current?.play(r,"listening")},[]);return{containerRef:v,renderer:i.current,actionQueue:d.current,rendererState:x,avatarDimensions:g,loadedActions:c,actionMetadata:s,isLoadingActions:F,isEngineReady:W,token:L,isConnecting:K,isConnected:O,livekitUrl:T,timeRemaining:oe,formatTime:pe,micMuted:ce,volume:le,turnOffMicWhenAISpeaking:m,connect:ue,disconnect:V,toggleMic:he,setMicMuted:ve,setVolume:me,toggleVolume:be,interrupt:xe,runAction:ye,startSessionTimer:fe,markConnected:de,setInactivityActive:ge}}const re=({session:u,style:a,loadingContent:n})=>t.jsxs("div",{style:{position:"absolute",inset:0,width:"100%",height:"100%",zIndex:0,pointerEvents:"auto",display:"flex",justifyContent:"center",alignItems:"center",overflow:"hidden",...a},children:[t.jsx("style",{children:`
2
2
  @keyframes zwr-spin {
3
3
  from { transform: rotate(0deg); }
4
4
  to { transform: rotate(360deg); }
@@ -7,15 +7,15 @@
7
7
  0%, 100% { opacity: 1; }
8
8
  50% { opacity: 0.5; }
9
9
  }
10
- `}),t.jsx("div",{ref:m.containerRef,style:{position:"relative",width:"100%",height:"100%",transition:"all 0.5s ease-in-out"},children:!m.isEngineReady&&t.jsx("div",{style:{position:"absolute",inset:0,display:"flex",alignItems:"center",justifyContent:"center",background:"rgba(0,0,0,0.4)",backdropFilter:"blur(4px)",zIndex:10},children:n||t.jsxs("div",{style:{display:"flex",flexDirection:"column",alignItems:"center",gap:16},children:[t.jsx(E.Loader2,{style:{width:40,height:40,color:"rgba(255,255,255,0.5)",animation:"zwr-spin 1s linear infinite"}}),t.jsx("div",{style:{fontSize:14,color:"rgba(255,255,255,0.5)",fontWeight:500,letterSpacing:"0.1em",textTransform:"uppercase",animation:"zwr-pulse 2s ease-in-out infinite"},children:"Initializing Neural Engine..."})]})})})]}),ee={flexShrink:0,borderRadius:9999,padding:16,transition:"all 0.3s",border:"none",cursor:"pointer",display:"inline-flex",alignItems:"center",justifyContent:"center"},te={position:"relative",display:"inline-flex",alignItems:"center",justifyContent:"center",gap:12,borderRadius:9999,padding:"16px 24px",fontSize:16,fontWeight:600,color:"#fff",whiteSpace:"nowrap",transition:"all 0.3s",border:"none",cursor:"pointer"},se=({session:m,style:a})=>{const{micMuted:n,toggleMic:u,isConnected:d,isConnecting:y,isEngineReady:f,loadedActions:A,connect:T,disconnect:b}=m,[h,C]=e.useState(!1),[i,l]=e.useState(!1),x=f&&A.has("listening")&&A.has("speaking"),w=n?{...ee,background:"rgba(239,68,68,0.1)",color:"#f87171",boxShadow:"inset 0 0 0 1px rgba(239,68,68,0.3)",...h?{background:"rgba(239,68,68,0.2)"}:{}}:{...ee,background:h?"rgba(255,255,255,0.1)":"rgba(0,0,0,0.4)",color:"#fff",boxShadow:"inset 0 0 0 1px rgba(255,255,255,0.1)"},p=d?{...te,background:i?"rgba(239,68,68,0.22)":"rgba(239,68,68,0.14)",boxShadow:"inset 0 0 0 1px rgba(239,68,68,0.5), 0 0 20px rgba(239,68,68,0.2)"}:{...te,background:"linear-gradient(to right, #7c3aed, #db2777, #f97316)",boxShadow:"0 0 30px rgba(236,72,153,0.3)",opacity:i?.9:1};return(y||!x)&&(p.opacity=.5,p.cursor="not-allowed"),t.jsxs("div",{style:{width:"100%",marginTop:"auto",display:"flex",flexDirection:"column",alignItems:"center",gap:16,paddingBottom:16,...a},children:[t.jsx("style",{children:`
10
+ `}),t.jsx("div",{ref:u.containerRef,style:{position:"relative",width:"100%",height:"100%",transition:"all 0.5s ease-in-out"},children:!u.isEngineReady&&t.jsx("div",{style:{position:"absolute",inset:0,display:"flex",alignItems:"center",justifyContent:"center",background:"rgba(0,0,0,0.4)",backdropFilter:"blur(4px)",zIndex:10},children:n||t.jsxs("div",{style:{display:"flex",flexDirection:"column",alignItems:"center",gap:16},children:[t.jsx(M.Loader2,{style:{width:40,height:40,color:"rgba(255,255,255,0.5)",animation:"zwr-spin 1s linear infinite"}}),t.jsx("div",{style:{fontSize:14,color:"rgba(255,255,255,0.5)",fontWeight:500,letterSpacing:"0.1em",textTransform:"uppercase",animation:"zwr-pulse 2s ease-in-out infinite"},children:"Initializing Neural Engine..."})]})})})]}),ee={flexShrink:0,borderRadius:9999,padding:16,transition:"all 0.3s",border:"none",cursor:"pointer",display:"inline-flex",alignItems:"center",justifyContent:"center"},te={position:"relative",display:"inline-flex",alignItems:"center",justifyContent:"center",gap:12,borderRadius:9999,padding:"16px 24px",fontSize:16,fontWeight:600,color:"#fff",whiteSpace:"nowrap",transition:"all 0.3s",border:"none",cursor:"pointer"},se=({session:u,style:a})=>{const{micMuted:n,toggleMic:f,isConnected:m,isConnecting:y,isEngineReady:p,loadedActions:A,connect:T,disconnect:b}=u,[v,C]=e.useState(!1),[i,d]=e.useState(!1),x=p&&A.has("listening")&&A.has("speaking"),w=n?{...ee,background:"rgba(239,68,68,0.1)",color:"#f87171",boxShadow:"inset 0 0 0 1px rgba(239,68,68,0.3)",...v?{background:"rgba(239,68,68,0.2)"}:{}}:{...ee,background:v?"rgba(255,255,255,0.1)":"rgba(0,0,0,0.4)",color:"#fff",boxShadow:"inset 0 0 0 1px rgba(255,255,255,0.1)"},g=m?{...te,background:i?"rgba(239,68,68,0.22)":"rgba(239,68,68,0.14)",boxShadow:"inset 0 0 0 1px rgba(239,68,68,0.5), 0 0 20px rgba(239,68,68,0.2)"}:{...te,background:"linear-gradient(to right, #7c3aed, #db2777, #f97316)",boxShadow:"0 0 30px rgba(236,72,153,0.3)",opacity:i?.9:1};return(y||!x)&&(g.opacity=.5,g.cursor="not-allowed"),t.jsxs("div",{style:{width:"100%",marginTop:"auto",display:"flex",flexDirection:"column",alignItems:"center",gap:16,paddingBottom:16,...a},children:[t.jsx("style",{children:`
11
11
  @keyframes zwr-spin {
12
12
  from { transform: rotate(0deg); }
13
13
  to { transform: rotate(360deg); }
14
14
  }
15
- `}),t.jsxs("div",{style:{display:"flex",flexWrap:"wrap",alignItems:"center",justifyContent:"center",gap:16,width:"100%",pointerEvents:"auto"},children:[t.jsx("button",{type:"button",onClick:u,onMouseEnter:()=>C(!0),onMouseLeave:()=>C(!1),style:w,title:n?"Unmute mic":"Mute mic",children:n?t.jsx(E.MicOff,{size:24}):t.jsx(E.Mic,{size:24})}),t.jsx("button",{type:"button",onClick:d?b:T,onMouseEnter:()=>l(!0),onMouseLeave:()=>l(!1),disabled:!d&&(y||!x),style:p,children:y?t.jsxs(t.Fragment,{children:[t.jsx(E.Loader2,{size:20,style:{animation:"zwr-spin 1s linear infinite"}}),t.jsx("span",{children:"Connecting..."})]}):!d&&!x?t.jsxs(t.Fragment,{children:[t.jsx(E.Loader2,{size:20,style:{animation:"zwr-spin 1s linear infinite"}}),t.jsx("span",{children:"Loading Avatar..."})]}):d?t.jsxs(t.Fragment,{children:[t.jsx(E.Power,{size:20}),t.jsx("span",{children:"End Session"})]}):t.jsxs(t.Fragment,{children:[t.jsx(E.Activity,{size:20}),t.jsx("span",{children:"Start Session"})]})})]})]})},ie=({session:m})=>{const{isConnected:a,timeRemaining:n,formatTime:u}=m,d=a&&n<=30,y=a&&n>30&&n<=60,f={display:"flex",alignItems:"center",gap:8,backdropFilter:"blur(12px)",padding:"6px 12px",borderRadius:9999,border:"1px solid",pointerEvents:"auto",transition:"all 0.3s",...d?{background:"rgba(239,68,68,0.3)",borderColor:"rgba(239,68,68,0.4)",boxShadow:"0 0 15px rgba(239,68,68,0.3)",animation:"zwr-pulse 2s ease-in-out infinite"}:y?{background:"rgba(249,115,22,0.2)",borderColor:"rgba(249,115,22,0.3)",boxShadow:"0 4px 12px rgba(0,0,0,0.3)"}:{background:"rgba(0,0,0,0.4)",borderColor:"rgba(255,255,255,0.1)",boxShadow:"0 4px 12px rgba(0,0,0,0.3)"}};return t.jsxs(t.Fragment,{children:[t.jsx("style",{children:`
15
+ `}),t.jsxs("div",{style:{display:"flex",flexWrap:"wrap",alignItems:"center",justifyContent:"center",gap:16,width:"100%",pointerEvents:"auto"},children:[t.jsx("button",{type:"button",onClick:f,onMouseEnter:()=>C(!0),onMouseLeave:()=>C(!1),style:w,title:n?"Unmute mic":"Mute mic",children:n?t.jsx(M.MicOff,{size:24}):t.jsx(M.Mic,{size:24})}),t.jsx("button",{type:"button",onClick:m?b:T,onMouseEnter:()=>d(!0),onMouseLeave:()=>d(!1),disabled:!m&&(y||!x),style:g,children:y?t.jsxs(t.Fragment,{children:[t.jsx(M.Loader2,{size:20,style:{animation:"zwr-spin 1s linear infinite"}}),t.jsx("span",{children:"Connecting..."})]}):!m&&!x?t.jsxs(t.Fragment,{children:[t.jsx(M.Loader2,{size:20,style:{animation:"zwr-spin 1s linear infinite"}}),t.jsx("span",{children:"Loading Avatar..."})]}):m?t.jsxs(t.Fragment,{children:[t.jsx(M.Power,{size:20}),t.jsx("span",{children:"End Session"})]}):t.jsxs(t.Fragment,{children:[t.jsx(M.Activity,{size:20}),t.jsx("span",{children:"Start Session"})]})})]})]})},ie=({session:u})=>{const{isConnected:a,timeRemaining:n,formatTime:f}=u,m=a&&n<=30,y=a&&n>30&&n<=60,p={display:"flex",alignItems:"center",gap:8,backdropFilter:"blur(12px)",padding:"6px 12px",borderRadius:9999,border:"1px solid",pointerEvents:"auto",transition:"all 0.3s",...m?{background:"rgba(239,68,68,0.3)",borderColor:"rgba(239,68,68,0.4)",boxShadow:"0 0 15px rgba(239,68,68,0.3)",animation:"zwr-pulse 2s ease-in-out infinite"}:y?{background:"rgba(249,115,22,0.2)",borderColor:"rgba(249,115,22,0.3)",boxShadow:"0 4px 12px rgba(0,0,0,0.3)"}:{background:"rgba(0,0,0,0.4)",borderColor:"rgba(255,255,255,0.1)",boxShadow:"0 4px 12px rgba(0,0,0,0.3)"}};return t.jsxs(t.Fragment,{children:[t.jsx("style",{children:`
16
16
  @keyframes zwr-pulse {
17
17
  0%, 100% { opacity: 1; }
18
18
  50% { opacity: 0.5; }
19
19
  }
20
- `}),t.jsxs("div",{style:f,children:[t.jsx("div",{style:{height:8,width:8,borderRadius:"50%",...a?{background:"#22c55e",boxShadow:"0 0 10px rgba(34,197,94,0.5)"}:{background:"rgba(239,68,68,0.5)"}}}),t.jsx("span",{style:{fontSize:10,fontWeight:700,letterSpacing:"0.05em",color:"rgba(255,255,255,0.7)",textTransform:"uppercase"},children:a?`Online ${u(n)}`:"Offline"})]})]})},ae=({session:m})=>{const{renderer:a,actionQueue:n,micMuted:u,volume:d,setInactivityActive:y,loadedActions:f,token:A,isConnected:T}=m,{turnOffMicWhenAISpeaking:b,setMicMuted:h}=m,C=e.useRef(!1),{state:i,audioTrack:l}=D.useVoiceAssistant(),x=D.useLocalParticipant(),w=D.useIsSpeaking(x.localParticipant),p=e.useRef(null),M=e.useRef(f);e.useEffect(()=>{M.current=f},[f]),e.useEffect(()=>{const s=x.localParticipant;if(!a||!s)return;const o=new TextEncoder;return a.onOneshotComplete(I=>{const L=o.encode(JSON.stringify({type:"ACTION_FINISHED",action:I}));s.publishData(L,{reliable:!0}).catch($=>{console.error("[LiveKitAvatarProvider] Failed to publish action completion:",$)})})},[a,x.localParticipant]),e.useEffect(()=>{if(!n)return;const s=new B.VoiceActivityDetector({threshold:.008,analyseIntervalMs:30,speechStartFrames:1,speechPauseFrames:30,turnEndFrames:50});return p.current=s,s.on("speechStart",()=>{n.setTurnActive(!0),n.setSpeechState("speaking")}),s.on("turnEnd",()=>{n.setTurnActive(!1)}),()=>{s.stop(),p.current=null}},[n]),e.useEffect(()=>{const s=p.current;if(s)if(l?.publication?.track){const o=l.publication.track.mediaStreamTrack;o&&s.start(o)}else s.stop()},[l?.publication?.track]);const c=e.useRef(null),z=e.useRef(!1);return e.useEffect(()=>{if(!n)return;const s=i;if(!A||!T||s==="disconnected"){c.current&&(clearTimeout(c.current),c.current=null),p.current?.endTurn(),n.forceListening();return}s==="speaking"?(c.current&&(clearTimeout(c.current),c.current=null),n.setTurnActive(!0),n.setSpeechState("speaking")):s==="listening"||s==="idle"?(c.current&&(clearTimeout(c.current),c.current=null),p.current?.endTurn(),n.setTurnActive(!1)):s==="thinking"&&(c.current||(c.current=setTimeout(()=>{c.current=null,p.current?.endTurn(),n.setTurnActive(!1)},500)))},[n,T,i,A]),e.useEffect(()=>{const s=i==="speaking";if(!b){z.current=s;return}const o=z.current;!o&&s?u||h(!0):o&&!s&&u&&h(!1),z.current=s},[u,h,i,b]),e.useEffect(()=>{if(!b||!n)return;const s=o=>{o||h(!1)};return n.on("turnChanged",s),()=>{n.off("turnChanged",s)}},[n,h,b]),e.useEffect(()=>()=>{c.current&&clearTimeout(c.current)},[]),D.useDataChannel(s=>{try{const P=new TextDecoder().decode(s.payload),I=JSON.parse(P);if(I.type==="AVATAR_UPDATE"){const L=I.action;if(!M.current.has(L))return;n?.dispatch(L)}}catch(o){console.error("[LiveKitAvatarProvider] Failed to parse data message:",o)}}),e.useEffect(()=>{y(!(i==="speaking")&&!w)},[w,y,i]),e.useEffect(()=>{if(!n)return;const s=!!l;(!s||i==="disconnected")&&(p.current?.endTurn(),n.forceListening()),C.current=s},[l,i,n]),e.useEffect(()=>{const s=x.localParticipant;s&&s.setMicrophoneEnabled(!u).catch(o=>{console.error("[LiveKitAvatarProvider] Failed to set mic state:",o)})},[u,x.localParticipant]),t.jsx("div",{style:{position:"absolute",bottom:80,left:8,right:8,display:"flex",flexDirection:"column",gap:8},children:t.jsx(D.RoomAudioRenderer,{volume:d})})},we=({avatarId:m,apiKey:a,api:n,turnOffMicWhenAISpeaking:u,livekitUrl:d,sessionDuration:y,inactivityTimeout:f,style:A,className:T,loadingContent:b,customControls:h,customStatusBadge:C})=>{const i=ne({avatarId:m,apiKey:a,api:n,turnOffMicWhenAISpeaking:u,livekitUrl:d,sessionDuration:y,inactivityTimeout:f}),{token:l,isConnected:x,avatarDimensions:w,disconnect:p,startSessionTimer:M}=i;return t.jsxs("section",{className:T,style:{position:"relative",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",overflow:"hidden",borderRadius:16,border:"1px solid rgba(255,255,255,0.1)",boxShadow:"0 25px 50px -12px rgba(0,0,0,0.5)",height:"80vh",width:"auto",maxWidth:"100%",aspectRatio:w?`${w.width} / ${w.height}`:"3 / 4",...A},children:[t.jsx(re,{session:i,loadingContent:b}),t.jsxs("div",{style:{position:"absolute",inset:0,zIndex:20,pointerEvents:"none",display:"flex",flexDirection:"column",justifyContent:"space-between",padding:16},children:[t.jsxs("div",{style:{display:"flex",width:"100%",alignItems:"flex-start",justifyContent:"space-between"},children:[C?C(i):t.jsx(ie,{session:i}),t.jsx("div",{})]}),h?h(i):t.jsx(se,{session:i})]}),t.jsx("div",{style:{display:"none"},children:l&&d&&t.jsx(D.LiveKitRoom,{serverUrl:d,token:l,connect:!0,video:!1,audio:!0,onConnected:()=>{i.markConnected(),M()},onDisconnected:p,children:t.jsx(ae,{session:i})})})]})};exports.AvatarCanvas=re;exports.AvatarControls=se;exports.AvatarStatusBadge=ie;exports.LiveKitAvatarProvider=ae;exports.LiveKitAvatarSession=we;exports.useAvatarSession=ne;
20
+ `}),t.jsxs("div",{style:p,children:[t.jsx("div",{style:{height:8,width:8,borderRadius:"50%",...a?{background:"#22c55e",boxShadow:"0 0 10px rgba(34,197,94,0.5)"}:{background:"rgba(239,68,68,0.5)"}}}),t.jsx("span",{style:{fontSize:10,fontWeight:700,letterSpacing:"0.05em",color:"rgba(255,255,255,0.7)",textTransform:"uppercase"},children:a?`Online ${f(n)}`:"Offline"})]})]})},ae=({session:u})=>{const{renderer:a,actionQueue:n,micMuted:f,volume:m,setInactivityActive:y,loadedActions:p,token:A,isConnected:T}=u,{turnOffMicWhenAISpeaking:b,setMicMuted:v}=u,C=e.useRef(!1),{state:i,audioTrack:d}=D.useVoiceAssistant(),x=D.useLocalParticipant(),w=D.useIsSpeaking(x.localParticipant),g=e.useRef(null),E=e.useRef(p);e.useEffect(()=>{E.current=p},[p]),e.useEffect(()=>{const s=x.localParticipant;if(!a||!s)return;const o=new TextEncoder;return a.onOneshotComplete(I=>{const L=o.encode(JSON.stringify({type:"ACTION_FINISHED",action:I}));s.publishData(L,{reliable:!0}).catch(P=>{console.error("[LiveKitAvatarProvider] Failed to publish action completion:",P)})})},[a,x.localParticipant]),e.useEffect(()=>{if(!n)return;const s=new B.VoiceActivityDetector({threshold:.008,analyseIntervalMs:30,speechStartFrames:1,speechPauseFrames:30,turnEndFrames:50});return g.current=s,s.on("speechStart",()=>{n.setTurnActive(!0),n.setSpeechState("speaking")}),s.on("turnEnd",()=>{n.setTurnActive(!1)}),()=>{s.stop(),g.current=null}},[n]),e.useEffect(()=>{const s=g.current;if(s)if(d?.publication?.track){const o=d.publication.track.mediaStreamTrack;o&&s.start(o)}else s.stop()},[d?.publication?.track]);const c=e.useRef(null),z=e.useRef(!1);return e.useEffect(()=>{if(!n)return;const s=i;if(!A||!T||s==="disconnected"){c.current&&(clearTimeout(c.current),c.current=null),g.current?.endTurn(),n.forceListening();return}s==="speaking"?(c.current&&(clearTimeout(c.current),c.current=null),n.setTurnActive(!0),n.setSpeechState("speaking")):s==="listening"||s==="idle"?(c.current&&(clearTimeout(c.current),c.current=null),g.current?.endTurn(),n.setTurnActive(!1)):s==="thinking"&&(c.current||(c.current=setTimeout(()=>{c.current=null,g.current?.endTurn(),n.setTurnActive(!1)},500)))},[n,T,i,A]),e.useEffect(()=>{const s=i==="speaking";if(!b){z.current=s;return}const o=z.current;!o&&s?f||v(!0):o&&!s&&f&&v(!1),z.current=s},[f,v,i,b]),e.useEffect(()=>{if(!b||!n)return;const s=o=>{o||v(!1)};return n.on("turnChanged",s),()=>{n.off("turnChanged",s)}},[n,v,b]),e.useEffect(()=>()=>{c.current&&clearTimeout(c.current)},[]),D.useDataChannel(s=>{try{const F=new TextDecoder().decode(s.payload),I=JSON.parse(F);if(I.type==="AVATAR_UPDATE"){const L=I.action;if(!E.current.has(L))return;n?.dispatch(L)}}catch(o){console.error("[LiveKitAvatarProvider] Failed to parse data message:",o)}}),e.useEffect(()=>{y(!(i==="speaking")&&!w)},[w,y,i]),e.useEffect(()=>{if(!n)return;const s=!!d;(!s||i==="disconnected")&&(g.current?.endTurn(),n.forceListening()),C.current=s},[d,i,n]),e.useEffect(()=>{const s=x.localParticipant;s&&s.setMicrophoneEnabled(!f).catch(o=>{console.error("[LiveKitAvatarProvider] Failed to set mic state:",o)})},[f,x.localParticipant]),t.jsx("div",{style:{position:"absolute",bottom:80,left:8,right:8,display:"flex",flexDirection:"column",gap:8},children:t.jsx(D.RoomAudioRenderer,{volume:m})})},je=({avatarId:u,apiKey:a,api:n,turnOffMicWhenAISpeaking:f,livekitUrl:m,sessionDuration:y,inactivityTimeout:p,style:A,className:T,loadingContent:b,customControls:v,customStatusBadge:C})=>{const i=ne({avatarId:u,apiKey:a,api:n,turnOffMicWhenAISpeaking:f,livekitUrl:m,sessionDuration:y,inactivityTimeout:p}),{token:d,isConnected:x,avatarDimensions:w,disconnect:g,startSessionTimer:E}=i;return t.jsxs("section",{className:T,style:{position:"relative",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",overflow:"hidden",borderRadius:16,border:"1px solid rgba(255,255,255,0.1)",boxShadow:"0 25px 50px -12px rgba(0,0,0,0.5)",height:"80vh",width:"auto",maxWidth:"100%",aspectRatio:w?`${w.width} / ${w.height}`:"3 / 4",...A},children:[t.jsx(re,{session:i,loadingContent:b}),t.jsxs("div",{style:{position:"absolute",inset:0,zIndex:20,pointerEvents:"none",display:"flex",flexDirection:"column",justifyContent:"space-between",padding:16},children:[t.jsxs("div",{style:{display:"flex",width:"100%",alignItems:"flex-start",justifyContent:"space-between"},children:[C?C(i):t.jsx(ie,{session:i}),t.jsx("div",{})]}),v?v(i):t.jsx(se,{session:i})]}),t.jsx("div",{style:{display:"none"},children:d&&i.livekitUrl&&t.jsx(D.LiveKitRoom,{serverUrl:i.livekitUrl,token:d,connect:!0,video:!1,audio:!0,onConnected:()=>{i.markConnected(),E()},onDisconnected:g,children:t.jsx(ae,{session:i})})})]})};exports.AvatarCanvas=re;exports.AvatarControls=se;exports.AvatarStatusBadge=ie;exports.LiveKitAvatarProvider=ae;exports.LiveKitAvatarSession=je;exports.useAvatarSession=ne;
21
21
  //# sourceMappingURL=zeroweight-renderer-react.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"zeroweight-renderer-react.cjs.js","sources":["../src/useAvatarSession.ts","../src/AvatarCanvas.tsx","../src/AvatarControls.tsx","../src/AvatarStatusBadge.tsx","../src/LiveKitAvatarProvider.tsx","../src/LiveKitAvatarSession.tsx"],"sourcesContent":["/**\n * useAvatarSession — Main React hook for avatar rendering.\n *\n * Wraps ZeroWeightRenderer + ActionQueue in React lifecycle.\n * Returns reactive state and imperative methods.\n *\n * Usage:\n * const session = useAvatarSession({\n * avatarId: 'abc123',\n * apiKey: 'optional-api-key',\n * });\n */\n\nimport { useEffect, useRef, useState, useCallback, useMemo } from \"react\";\nimport { ZeroWeightRenderer, ActionQueue } from \"@zeroweight/renderer\";\nimport type { ActionMetadata, RendererState } from \"@zeroweight/renderer\";\nimport type { ZeroWeightApi } from \"./types\";\n\nconst INACTIVITY_TIMEOUT_MS = 30000;\nconst SESSION_DURATION_SECONDS = 120;\nconst DEFAULT_LIVEKIT_URL = \"wss://prod-project-pazuyq69.livekit.cloud\";\nconst DEFAULT_API_BASE_URL = \"https://api.zeroweight.ai/api/v1\";\n\nconst generateRandomName = () => {\n const adjectives = [\"Happy\", \"Swift\", \"Bright\", \"Cool\", \"Smart\"];\n const nouns = [\"User\", \"Guest\", \"Visitor\", \"Agent\", \"Caller\"];\n const randomNum = Math.floor(Math.random() * 1000);\n return `${adjectives[Math.floor(Math.random() * adjectives.length)]}${\n nouns[Math.floor(Math.random() * nouns.length)]\n }${randomNum}`;\n};\n\nexport interface AvatarSessionConfig {\n avatarId: string;\n /** Optional API key for the built-in ZeroWeight API integration. */\n apiKey?: string | null;\n /** Injectable API — provide your own fetch functions. */\n api?: ZeroWeightApi;\n /** Auto-mute when the assistant starts speaking, and unmute when it stops. */\n turnOffMicWhenAISpeaking?: boolean;\n /** LiveKit server URL. */\n livekitUrl?: string;\n /** Session duration in seconds. Default: 120 */\n sessionDuration?: number;\n /** Inactivity timeout in ms. Default: 30000 */\n inactivityTimeout?: number;\n}\n\nexport interface AvatarSessionReturn {\n // Refs\n containerRef: React.RefObject<HTMLDivElement | null>;\n\n // Renderer instance (for advanced use)\n renderer: ZeroWeightRenderer | null;\n actionQueue: ActionQueue | null;\n\n // Reactive state\n rendererState: RendererState;\n avatarDimensions: { width: number; height: number } | null;\n loadedActions: Set<string>;\n actionMetadata: Record<string, ActionMetadata>;\n isLoadingActions: boolean;\n isEngineReady: boolean;\n\n // Connection state\n token: string | null;\n isConnecting: boolean;\n isConnected: boolean;\n livekitUrl: string;\n\n // Session\n timeRemaining: number;\n formatTime: (seconds: number) => string;\n\n // Controls\n micMuted: boolean;\n volume: number;\n turnOffMicWhenAISpeaking: boolean;\n\n // Methods\n connect: () => Promise<void>;\n disconnect: () => void;\n toggleMic: () => void;\n setMicMuted: (muted: boolean) => void;\n setVolume: (v: number) => void;\n toggleVolume: () => void;\n interrupt: () => void;\n runAction: (actionId: string) => void;\n startSessionTimer: () => void;\n /** Called by LiveKitRoom onConnected */\n markConnected: () => void;\n\n // For LiveKitAvatarProvider internal use\n setInactivityActive: (isInactive: boolean) => void;\n}\n\nexport function useAvatarSession(config: AvatarSessionConfig): AvatarSessionReturn {\n const {\n avatarId,\n apiKey = null,\n api,\n turnOffMicWhenAISpeaking = true,\n livekitUrl: livekitUrlProp = DEFAULT_LIVEKIT_URL,\n sessionDuration = SESSION_DURATION_SECONDS,\n inactivityTimeout = INACTIVITY_TIMEOUT_MS,\n } = config;\n\n const livekitUrl = livekitUrlProp;\n const resolvedApi = useMemo<ZeroWeightApi>(() => {\n if (api) return api;\n\n const headers = apiKey ? { \"X-ZW-Api-Key\": apiKey } : undefined;\n\n return {\n getBundle: async (resolvedAvatarId: string) => {\n const response = await fetch(\n `${DEFAULT_API_BASE_URL}/avatars/bundle/${encodeURIComponent(\n resolvedAvatarId\n )}`,\n { headers }\n );\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch avatar bundle (${response.status} ${response.statusText})`\n );\n }\n\n return response.json();\n },\n getLiveKitToken: async (avatarId: string) => {\n const response = await fetch(\n `${DEFAULT_API_BASE_URL}/livekit/getToken?avatar_id=${avatarId}&name=anonymuous`,\n { headers }\n );\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch LiveKit token (${response.status} ${response.statusText})`\n );\n }\n\n return response.json();\n },\n };\n }, [api, apiKey]);\n\n // Refs\n const containerRef = useRef<HTMLDivElement | null>(null);\n const canvasRef = useRef<HTMLCanvasElement | null>(null);\n const rendererRef = useRef<ZeroWeightRenderer | null>(null);\n const actionQueueRef = useRef<ActionQueue | null>(null);\n\n // Engine state\n const [rendererState, setRendererState] = useState<RendererState>(\"idle\");\n const [avatarDimensions, setAvatarDimensions] = useState<{\n width: number;\n height: number;\n } | null>(null);\n const [loadedActions, setLoadedActions] = useState<Set<string>>(\n new Set([\"listening\"])\n );\n const [actionMetadata, setActionMetadata] = useState<\n Record<string, ActionMetadata>\n >({\n listening: { kind: \"looped\" },\n speaking: { kind: \"looped\" },\n });\n const [isLoadingActions, setIsLoadingActions] = useState(false);\n\n // Connection state\n const [token, setToken] = useState<string | null>(null);\n const [isConnecting, setIsConnecting] = useState(false);\n const [isConnected, setIsConnected] = useState(false);\n\n // Session timer\n const [timeRemaining, setTimeRemaining] = useState(sessionDuration);\n const sessionTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);\n const handleDisconnectRef = useRef<() => void>(() => {});\n\n // Controls\n const [micMuted, setMicMuted] = useState(false);\n const [volume, setVolumeState] = useState(0.8);\n\n // Inactivity\n const inactivityTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const isEngineReady = rendererState === \"ready\";\n\n // ─── Ensure Canvas ──────────────────────────────────────────────\n\n const ensureCanvas = useCallback(() => {\n const container = containerRef.current;\n if (!container) return null;\n\n let canvas = container.querySelector(\"canvas\") as HTMLCanvasElement | null;\n if (!canvas) {\n canvas = document.createElement(\"canvas\");\n canvas.style.width = \"100%\";\n canvas.style.height = \"100%\";\n canvas.style.display = \"block\";\n container.appendChild(canvas);\n }\n canvasRef.current = canvas;\n return canvas;\n }, []);\n\n // ─── Init Renderer ──────────────────────────────────────────────\n\n useEffect(() => {\n let cancelled = false;\n\n const initRenderer = async () => {\n const canvas = ensureCanvas();\n if (!canvas) return;\n\n // Create renderer\n const renderer = new ZeroWeightRenderer();\n rendererRef.current = renderer;\n\n // Create action queue\n const queue = new ActionQueue((actionId, fallback) => {\n renderer.play(actionId, fallback);\n });\n actionQueueRef.current = queue;\n\n // Wire up renderer events\n renderer.on(\"stateChanged\", (state) => {\n if (!cancelled) setRendererState(state);\n });\n\n renderer.on(\"dimensions\", (w, h) => {\n if (!cancelled) setAvatarDimensions({ width: w, height: h });\n });\n\n renderer.on(\"actionLoaded\", (actionId) => {\n if (!cancelled) {\n setLoadedActions((prev) => {\n const next = new Set(prev);\n next.add(actionId);\n return next;\n });\n }\n });\n\n renderer.on(\"allActionsLoaded\", () => {\n if (!cancelled) setIsLoadingActions(false);\n });\n\n renderer.on(\"ready\", () => {\n // Update action metadata from renderer\n if (!cancelled) {\n setActionMetadata(renderer.getActionMetadata());\n queue.setActionMetadata(renderer.getActionMetadata());\n }\n });\n\n // Fetch bundle and init\n try {\n setIsLoadingActions(true);\n const data = await resolvedApi.getBundle(avatarId);\n if (cancelled) return;\n\n await renderer.init(canvas, { payload: data.payload });\n if (cancelled) return;\n\n // After init, update metadata again with all loaded data\n setActionMetadata(renderer.getActionMetadata());\n queue.setActionMetadata(renderer.getActionMetadata());\n } catch (e) {\n console.error(\"[useAvatarSession] Init failed:\", e);\n }\n };\n\n initRenderer();\n\n return () => {\n cancelled = true;\n rendererRef.current?.destroy();\n rendererRef.current = null;\n actionQueueRef.current = null;\n };\n }, [avatarId, ensureCanvas, resolvedApi]);\n\n // ─── Auto-wave on load ──────────────────────────────────────────\n\n const hasWavedRef = useRef(false);\n useEffect(() => {\n if (\n isEngineReady &&\n rendererRef.current &&\n loadedActions.has(\"wave_hand\") &&\n !hasWavedRef.current\n ) {\n rendererRef.current.play(\"wave_hand\", \"listening\");\n hasWavedRef.current = true;\n }\n }, [isEngineReady, loadedActions]);\n\n // ─── Connection ─────────────────────────────────────────────────\n\n const connect = useCallback(async () => {\n if (isConnecting || isConnected) return;\n\n setIsConnecting(true);\n try {\n await navigator.mediaDevices.getUserMedia({ audio: true });\n const data = await resolvedApi.getLiveKitToken(avatarId);\n setToken(data.token);\n } catch (error) {\n console.error(\"[useAvatarSession] Failed to connect:\", error);\n setIsConnecting(false);\n }\n }, [isConnecting, isConnected, avatarId, resolvedApi]);\n\n const disconnect = useCallback(() => {\n // Clear session timer\n if (sessionTimerRef.current) {\n clearInterval(sessionTimerRef.current);\n sessionTimerRef.current = null;\n }\n setTimeRemaining(sessionDuration);\n\n // Clear inactivity timer\n if (inactivityTimeoutRef.current) {\n clearTimeout(inactivityTimeoutRef.current);\n inactivityTimeoutRef.current = null;\n }\n\n setToken(null);\n setIsConnected(false);\n setIsConnecting(false);\n\n // Reset avatar to listening\n actionQueueRef.current?.forceListening();\n }, [sessionDuration]);\n\n // Keep disconnect ref current for timer\n useEffect(() => {\n handleDisconnectRef.current = disconnect;\n }, [disconnect]);\n\n const markConnected = useCallback(() => {\n setIsConnected(true);\n setIsConnecting(false);\n }, []);\n\n // ─── Session Timer ──────────────────────────────────────────────\n\n const startSessionTimer = useCallback(() => {\n if (sessionTimerRef.current) clearInterval(sessionTimerRef.current);\n setTimeRemaining(sessionDuration);\n\n sessionTimerRef.current = setInterval(() => {\n setTimeRemaining((prev) => {\n if (prev <= 1) {\n clearInterval(sessionTimerRef.current!);\n sessionTimerRef.current = null;\n setTimeout(() => handleDisconnectRef.current(), 0);\n return 0;\n }\n return prev - 1;\n });\n }, 1000);\n }, [sessionDuration]);\n\n const formatTime = useCallback((seconds: number) => {\n const m = Math.floor(seconds / 60)\n .toString()\n .padStart(2, \"0\");\n const s = (seconds % 60).toString().padStart(2, \"0\");\n return `${m}:${s}`;\n }, []);\n\n // Cleanup timer on unmount\n useEffect(() => {\n return () => {\n if (sessionTimerRef.current) clearInterval(sessionTimerRef.current);\n };\n }, []);\n\n // ─── Inactivity ─────────────────────────────────────────────────\n\n const setInactivityActive = useCallback(\n (isInactive: boolean) => {\n if (!isInactive) {\n if (inactivityTimeoutRef.current) {\n clearTimeout(inactivityTimeoutRef.current);\n inactivityTimeoutRef.current = null;\n }\n return;\n }\n\n if (inactivityTimeoutRef.current) return;\n\n inactivityTimeoutRef.current = setTimeout(() => {\n inactivityTimeoutRef.current = null;\n handleDisconnectRef.current();\n }, inactivityTimeout);\n },\n [inactivityTimeout]\n );\n\n // ─── Controls ───────────────────────────────────────────────────\n\n const toggleMic = useCallback(() => {\n setMicMuted((v) => !v);\n }, []);\n\n const setMicMutedValue = useCallback((muted: boolean) => {\n setMicMuted(muted);\n }, []);\n\n const setVolume = useCallback((v: number) => {\n setVolumeState(v);\n }, []);\n\n const toggleVolume = useCallback(() => {\n setVolumeState((v) => (v > 0 ? 0 : 1));\n }, []);\n\n const interrupt = useCallback(() => {\n rendererRef.current?.interrupt();\n }, []);\n\n const runAction = useCallback((actionId: string) => {\n // if (actionQueueRef.current) {\n // actionQueueRef.current.dispatch(actionId);\n // } else {\n // }\n rendererRef.current?.play(actionId, \"listening\");\n }, []);\n\n return {\n containerRef,\n renderer: rendererRef.current,\n actionQueue: actionQueueRef.current,\n rendererState,\n avatarDimensions,\n loadedActions,\n actionMetadata,\n isLoadingActions,\n isEngineReady,\n token,\n isConnecting,\n isConnected,\n livekitUrl,\n timeRemaining,\n formatTime,\n micMuted,\n volume,\n turnOffMicWhenAISpeaking,\n connect,\n disconnect,\n toggleMic,\n setMicMuted: setMicMutedValue,\n setVolume,\n toggleVolume,\n interrupt,\n runAction,\n startSessionTimer,\n markConnected,\n setInactivityActive,\n };\n}\n","/**\n * AvatarCanvas — Canvas container for the avatar renderer.\n *\n * Provides the div container where the renderer creates and manages a <canvas>.\n * Shows loading overlay when the engine is initializing.\n * Uses inline styles only — no CSS framework dependency.\n */\n\nimport React from \"react\";\nimport { Loader2 } from \"lucide-react\";\nimport type { AvatarSessionReturn } from \"./useAvatarSession\";\n\ninterface AvatarCanvasProps {\n session: AvatarSessionReturn;\n /** Optional style overrides for the outer container. */\n style?: React.CSSProperties;\n /** Custom loading component. */\n loadingContent?: React.ReactNode;\n}\n\nexport const AvatarCanvas: React.FC<AvatarCanvasProps> = ({\n session,\n style,\n loadingContent,\n}) => {\n return (\n <div\n style={{\n position: \"absolute\",\n inset: 0,\n width: \"100%\",\n height: \"100%\",\n zIndex: 0,\n pointerEvents: \"auto\",\n display: \"flex\",\n justifyContent: \"center\",\n alignItems: \"center\",\n overflow: \"hidden\",\n ...style,\n }}\n >\n <style>{`\n @keyframes zwr-spin {\n from { transform: rotate(0deg); }\n to { transform: rotate(360deg); }\n }\n @keyframes zwr-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.5; }\n }\n `}</style>\n <div\n ref={session.containerRef}\n style={{\n position: \"relative\",\n width: \"100%\",\n height: \"100%\",\n transition: \"all 0.5s ease-in-out\",\n }}\n >\n {/* Canvas is injected here by the renderer */}\n {!session.isEngineReady && (\n <div\n style={{\n position: \"absolute\",\n inset: 0,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n background: \"rgba(0,0,0,0.4)\",\n backdropFilter: \"blur(4px)\",\n zIndex: 10,\n }}\n >\n {loadingContent || (\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: 16,\n }}\n >\n <Loader2\n style={{\n width: 40,\n height: 40,\n color: \"rgba(255,255,255,0.5)\",\n animation: \"zwr-spin 1s linear infinite\",\n }}\n />\n <div\n style={{\n fontSize: 14,\n color: \"rgba(255,255,255,0.5)\",\n fontWeight: 500,\n letterSpacing: \"0.1em\",\n textTransform: \"uppercase\",\n animation: \"zwr-pulse 2s ease-in-out infinite\",\n }}\n >\n Initializing Neural Engine...\n </div>\n </div>\n )}\n </div>\n )}\n </div>\n </div>\n );\n};\n","/**\n * AvatarControls — Default control bar for the avatar session.\n *\n * Mic toggle, connect/disconnect button.\n * Can be replaced entirely by a custom UI.\n * Uses inline styles only — no CSS framework dependency.\n */\n\nimport React, { useState } from \"react\";\nimport {\n Mic,\n MicOff,\n Power,\n Activity,\n Loader2,\n} from \"lucide-react\";\nimport type { AvatarSessionReturn } from \"./useAvatarSession\";\n\ninterface AvatarControlsProps {\n session: AvatarSessionReturn;\n /** Optional style overrides for the wrapper. */\n style?: React.CSSProperties;\n}\n\nconst btnBase: React.CSSProperties = {\n flexShrink: 0,\n borderRadius: 9999,\n padding: 16,\n transition: \"all 0.3s\",\n border: \"none\",\n cursor: \"pointer\",\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n};\n\nconst connectBtnBase: React.CSSProperties = {\n position: \"relative\",\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: 12,\n borderRadius: 9999,\n padding: \"16px 24px\",\n fontSize: 16,\n fontWeight: 600,\n color: \"#fff\",\n whiteSpace: \"nowrap\",\n transition: \"all 0.3s\",\n border: \"none\",\n cursor: \"pointer\",\n};\n\nexport const AvatarControls: React.FC<AvatarControlsProps> = ({\n session,\n style,\n}) => {\n const {\n micMuted,\n toggleMic,\n isConnected,\n isConnecting,\n isEngineReady,\n loadedActions,\n connect,\n disconnect,\n } = session;\n\n const [micHover, setMicHover] = useState(false);\n const [connectHover, setConnectHover] = useState(false);\n\n const isSessionReady =\n isEngineReady &&\n loadedActions.has(\"listening\") &&\n loadedActions.has(\"speaking\");\n\n const micStyle: React.CSSProperties = micMuted\n ? {\n ...btnBase,\n background: \"rgba(239,68,68,0.1)\",\n color: \"#f87171\",\n boxShadow: \"inset 0 0 0 1px rgba(239,68,68,0.3)\",\n ...(micHover ? { background: \"rgba(239,68,68,0.2)\" } : {}),\n }\n : {\n ...btnBase,\n background: micHover ? \"rgba(255,255,255,0.1)\" : \"rgba(0,0,0,0.4)\",\n color: \"#fff\",\n boxShadow: \"inset 0 0 0 1px rgba(255,255,255,0.1)\",\n };\n\n const connectStyle: React.CSSProperties = isConnected\n ? {\n ...connectBtnBase,\n background: connectHover ? \"rgba(239,68,68,0.22)\" : \"rgba(239,68,68,0.14)\",\n boxShadow: \"inset 0 0 0 1px rgba(239,68,68,0.5), 0 0 20px rgba(239,68,68,0.2)\",\n }\n : {\n ...connectBtnBase,\n background: \"linear-gradient(to right, #7c3aed, #db2777, #f97316)\",\n boxShadow: \"0 0 30px rgba(236,72,153,0.3)\",\n opacity: connectHover ? 0.9 : 1,\n };\n\n if (isConnecting || !isSessionReady) {\n connectStyle.opacity = 0.5;\n connectStyle.cursor = \"not-allowed\";\n }\n\n return (\n <div\n style={{\n width: \"100%\",\n marginTop: \"auto\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: 16,\n paddingBottom: 16,\n ...style,\n }}\n >\n <style>{`\n @keyframes zwr-spin {\n from { transform: rotate(0deg); }\n to { transform: rotate(360deg); }\n }\n `}</style>\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: 16,\n width: \"100%\",\n pointerEvents: \"auto\",\n }}\n >\n {/* Mic Toggle */}\n <button\n type=\"button\"\n onClick={toggleMic}\n onMouseEnter={() => setMicHover(true)}\n onMouseLeave={() => setMicHover(false)}\n style={micStyle}\n title={micMuted ? \"Unmute mic\" : \"Mute mic\"}\n >\n {micMuted ? <MicOff size={24} /> : <Mic size={24} />}\n </button>\n\n {/* Main Connect Button */}\n <button\n type=\"button\"\n onClick={isConnected ? disconnect : connect}\n onMouseEnter={() => setConnectHover(true)}\n onMouseLeave={() => setConnectHover(false)}\n disabled={!isConnected && (isConnecting || !isSessionReady)}\n style={connectStyle}\n >\n {isConnecting ? (\n <>\n <Loader2 size={20} style={{ animation: \"zwr-spin 1s linear infinite\" }} />\n <span>Connecting...</span>\n </>\n ) : !isConnected && !isSessionReady ? (\n <>\n <Loader2 size={20} style={{ animation: \"zwr-spin 1s linear infinite\" }} />\n <span>Loading Avatar...</span>\n </>\n ) : isConnected ? (\n <>\n <Power size={20} />\n <span>End Session</span>\n </>\n ) : (\n <>\n <Activity size={20} />\n <span>Start Session</span>\n </>\n )}\n </button>\n </div>\n </div>\n );\n};\n","/**\n * AvatarStatusBadge — Connection status indicator with session timer.\n * Uses inline styles only — no CSS framework dependency.\n */\n\nimport React from \"react\";\nimport type { AvatarSessionReturn } from \"./useAvatarSession\";\n\ninterface AvatarStatusBadgeProps {\n session: AvatarSessionReturn;\n}\n\nexport const AvatarStatusBadge: React.FC<AvatarStatusBadgeProps> = ({\n session,\n}) => {\n const { isConnected, timeRemaining, formatTime } = session;\n\n const isUrgent = isConnected && timeRemaining <= 30;\n const isWarning = isConnected && timeRemaining > 30 && timeRemaining <= 60;\n\n const wrapperStyle: React.CSSProperties = {\n display: \"flex\",\n alignItems: \"center\",\n gap: 8,\n backdropFilter: \"blur(12px)\",\n padding: \"6px 12px\",\n borderRadius: 9999,\n border: \"1px solid\",\n pointerEvents: \"auto\",\n transition: \"all 0.3s\",\n ...(isUrgent\n ? {\n background: \"rgba(239,68,68,0.3)\",\n borderColor: \"rgba(239,68,68,0.4)\",\n boxShadow: \"0 0 15px rgba(239,68,68,0.3)\",\n animation: \"zwr-pulse 2s ease-in-out infinite\",\n }\n : isWarning\n ? {\n background: \"rgba(249,115,22,0.2)\",\n borderColor: \"rgba(249,115,22,0.3)\",\n boxShadow: \"0 4px 12px rgba(0,0,0,0.3)\",\n }\n : {\n background: \"rgba(0,0,0,0.4)\",\n borderColor: \"rgba(255,255,255,0.1)\",\n boxShadow: \"0 4px 12px rgba(0,0,0,0.3)\",\n }),\n };\n\n return (\n <>\n <style>{`\n @keyframes zwr-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.5; }\n }\n `}</style>\n <div style={wrapperStyle}>\n <div\n style={{\n height: 8,\n width: 8,\n borderRadius: \"50%\",\n ...(isConnected\n ? {\n background: \"#22c55e\",\n boxShadow: \"0 0 10px rgba(34,197,94,0.5)\",\n }\n : {\n background: \"rgba(239,68,68,0.5)\",\n }),\n }}\n />\n <span\n style={{\n fontSize: 10,\n fontWeight: 700,\n letterSpacing: \"0.05em\",\n color: \"rgba(255,255,255,0.7)\",\n textTransform: \"uppercase\",\n }}\n >\n {isConnected ? `Online ${formatTime(timeRemaining)}` : \"Offline\"}\n </span>\n </div>\n </>\n );\n};\n","/**\n * LiveKitAvatarProvider — Bridge between LiveKit hooks and the ActionQueue/VAD.\n * Uses inline styles only — no CSS framework dependency.\n *\n * This component MUST be rendered inside a <LiveKitRoom>.\n */\n\nimport React, { useEffect, useRef } from \"react\";\nimport {\n useVoiceAssistant,\n RoomAudioRenderer,\n useDataChannel,\n useIsSpeaking,\n useLocalParticipant,\n} from \"@livekit/components-react\";\nimport { VoiceActivityDetector } from \"@zeroweight/renderer\";\nimport type { AvatarSessionReturn } from \"./useAvatarSession\";\n\ninterface LiveKitAvatarProviderProps {\n session: AvatarSessionReturn;\n}\n\nexport const LiveKitAvatarProvider: React.FC<LiveKitAvatarProviderProps> = ({\n session,\n}) => {\n const {\n renderer,\n actionQueue,\n micMuted,\n volume,\n setInactivityActive,\n loadedActions,\n token,\n isConnected,\n } = session;\n const { turnOffMicWhenAISpeaking, setMicMuted } = session;\n\n const prevAudioTrackRef = useRef<boolean>(false);\n const { state, audioTrack } = useVoiceAssistant();\n const localParticipant = useLocalParticipant();\n const isUserSpeaking = useIsSpeaking(localParticipant.localParticipant);\n\n // VAD instance\n const vadRef = useRef<VoiceActivityDetector | null>(null);\n\n // Ref to latest loadedActions so data channel callback doesn't go stale\n const loadedActionsRef = useRef(loadedActions);\n useEffect(() => {\n loadedActionsRef.current = loadedActions;\n }, [loadedActions]);\n\n useEffect(() => {\n const participant = localParticipant.localParticipant;\n if (!renderer || !participant) return;\n\n const encoder = new TextEncoder();\n const unsubscribe = renderer.onOneshotComplete((actionId) => {\n const payload = encoder.encode(\n JSON.stringify({\n type: \"ACTION_FINISHED\",\n action: actionId,\n })\n );\n\n participant.publishData(payload, {\n reliable: true,\n }).catch((err) => {\n console.error(\"[LiveKitAvatarProvider] Failed to publish action completion:\", err);\n });\n });\n\n return unsubscribe;\n }, [renderer, localParticipant.localParticipant]);\n\n // ─── VAD Setup ──────────────────────────────────────────────────\n\n useEffect(() => {\n if (!actionQueue) return;\n\n const vad = new VoiceActivityDetector({\n threshold: 0.008,\n analyseIntervalMs: 30,\n speechStartFrames: 1,\n speechPauseFrames: 30,\n turnEndFrames: 50,\n });\n vadRef.current = vad;\n\n vad.on(\"speechStart\", () => {\n actionQueue.setTurnActive(true);\n actionQueue.setSpeechState(\"speaking\");\n });\n\n vad.on(\"turnEnd\", () => {\n actionQueue.setTurnActive(false);\n });\n\n return () => {\n vad.stop();\n vadRef.current = null;\n };\n }, [actionQueue]);\n\n // ─── Connect/disconnect VAD to audio track ──────────────────────\n\n useEffect(() => {\n const vad = vadRef.current;\n if (!vad) return;\n\n if (audioTrack?.publication?.track) {\n const mediaTrack = audioTrack.publication.track.mediaStreamTrack;\n if (mediaTrack) {\n vad.start(mediaTrack);\n }\n } else {\n vad.stop();\n }\n }, [audioTrack?.publication?.track]);\n\n // ─── LiveKit state → turn management ─────────────────────────────\n\n const turnEndTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const wasAssistantSpeakingRef = useRef(false);\n\n useEffect(() => {\n if (!actionQueue) return;\n\n const stateStr = state as string;\n const isSessionInactive = !token || !isConnected || stateStr === \"disconnected\";\n\n if (isSessionInactive) {\n if (turnEndTimerRef.current) {\n clearTimeout(turnEndTimerRef.current);\n turnEndTimerRef.current = null;\n }\n vadRef.current?.endTurn();\n actionQueue.forceListening();\n return;\n }\n\n if (stateStr === \"speaking\") {\n if (turnEndTimerRef.current) {\n clearTimeout(turnEndTimerRef.current);\n turnEndTimerRef.current = null;\n }\n actionQueue.setTurnActive(true);\n actionQueue.setSpeechState(\"speaking\");\n } else if (stateStr === \"listening\" || stateStr === \"idle\") {\n if (turnEndTimerRef.current) {\n clearTimeout(turnEndTimerRef.current);\n turnEndTimerRef.current = null;\n }\n vadRef.current?.endTurn();\n actionQueue.setTurnActive(false);\n } else if (stateStr === \"thinking\") {\n if (!turnEndTimerRef.current) {\n turnEndTimerRef.current = setTimeout(() => {\n turnEndTimerRef.current = null;\n vadRef.current?.endTurn();\n actionQueue.setTurnActive(false);\n }, 500);\n }\n }\n }, [actionQueue, isConnected, state, token]);\n\n useEffect(() => {\n const isAssistantSpeaking = state === \"speaking\";\n\n if (!turnOffMicWhenAISpeaking) {\n wasAssistantSpeakingRef.current = isAssistantSpeaking;\n return;\n }\n\n const wasAssistantSpeaking = wasAssistantSpeakingRef.current;\n\n if (!wasAssistantSpeaking && isAssistantSpeaking) {\n if (!micMuted) {\n setMicMuted(true);\n }\n } else if (wasAssistantSpeaking && !isAssistantSpeaking) {\n if (micMuted) {\n setMicMuted(false);\n }\n }\n\n wasAssistantSpeakingRef.current = isAssistantSpeaking;\n }, [micMuted, setMicMuted, state, turnOffMicWhenAISpeaking]);\n\n useEffect(() => {\n if (!turnOffMicWhenAISpeaking || !actionQueue) return;\n\n const handleTurnChanged = (active: boolean) => {\n if (!active) {\n setMicMuted(false);\n }\n };\n\n actionQueue.on(\"turnChanged\", handleTurnChanged);\n\n return () => {\n actionQueue.off(\"turnChanged\", handleTurnChanged);\n };\n }, [actionQueue, setMicMuted, turnOffMicWhenAISpeaking]);\n\n // Cleanup turn-end timer on unmount\n useEffect(() => {\n return () => {\n if (turnEndTimerRef.current) {\n clearTimeout(turnEndTimerRef.current);\n }\n };\n }, []);\n\n // ─── Data channel (backend-triggered actions) ───────────────────\n\n useDataChannel((msg) => {\n try {\n const decoder = new TextDecoder();\n const strData = decoder.decode(msg.payload);\n const data = JSON.parse(strData);\n\n if (data.type === \"AVATAR_UPDATE\") {\n const actionId = data.action;\n if (!loadedActionsRef.current.has(actionId)) {\n return;\n }\n actionQueue?.dispatch(actionId);\n }\n } catch (err) {\n console.error(\"[LiveKitAvatarProvider] Failed to parse data message:\", err);\n }\n });\n\n // ─── Inactivity tracking: only count when both AI and user are silent ───\n\n useEffect(() => {\n const isAssistantSpeaking = state === \"speaking\";\n setInactivityActive(!isAssistantSpeaking && !isUserSpeaking);\n }, [isUserSpeaking, setInactivityActive, state]);\n\n // ─── Audio loss / disconnect fallback ───────────────────────────\n\n useEffect(() => {\n if (!actionQueue) return;\n\n const hasAudio = !!audioTrack;\n const isDisconnectedState = state === \"disconnected\";\n\n if (!hasAudio || isDisconnectedState) {\n vadRef.current?.endTurn();\n actionQueue.forceListening();\n }\n prevAudioTrackRef.current = hasAudio;\n }, [audioTrack, state, actionQueue]);\n\n // ─── Mic mute sync ─────────────────────────────────────────────\n\n useEffect(() => {\n const participant = localParticipant.localParticipant;\n if (!participant) return;\n\n participant.setMicrophoneEnabled(!micMuted).catch((err) => {\n console.error(\"[LiveKitAvatarProvider] Failed to set mic state:\", err);\n });\n }, [micMuted, localParticipant.localParticipant]);\n\n return (\n <div style={{ position: \"absolute\", bottom: 80, left: 8, right: 8, display: \"flex\", flexDirection: \"column\", gap: 8 }}>\n <RoomAudioRenderer volume={volume} />\n </div>\n );\n};\n","/**\n * LiveKitAvatarSession — Full drop-in avatar component.\n * Uses inline styles only — no CSS framework dependency.\n *\n * Simplest usage:\n * import { LiveKitAvatarSession } from \"@zeroweight/react\";\n * <LiveKitAvatarSession\n * avatarId=\"abc123\"\n * apiKey=\"optional-api-key\"\n * />\n */\n\nimport React from \"react\";\nimport { LiveKitRoom } from \"@livekit/components-react\";\nimport \"@livekit/components-styles\";\n\nimport { useAvatarSession } from \"./useAvatarSession\";\nimport type { AvatarSessionReturn } from \"./useAvatarSession\";\nimport { AvatarCanvas } from \"./AvatarCanvas\";\nimport { AvatarControls } from \"./AvatarControls\";\nimport { AvatarStatusBadge } from \"./AvatarStatusBadge\";\nimport { LiveKitAvatarProvider } from \"./LiveKitAvatarProvider\";\nimport type { ZeroWeightApi } from \"./types\";\n\ninterface LiveKitAvatarSessionProps {\n avatarId: string;\n /** Optional API key for the built-in ZeroWeight API integration. */\n apiKey?: string | null;\n /** Injectable API — provide your own fetch functions. */\n api?: ZeroWeightApi;\n /** Auto-mute when the assistant starts speaking, and unmute when it stops. */\n turnOffMicWhenAISpeaking?: boolean;\n /** LiveKit server URL (e.g. \"wss://your-livekit.example.com\"). */\n livekitUrl?: string;\n /** Session duration in seconds. Default: 120 */\n sessionDuration?: number;\n /** Inactivity timeout in ms. Default: 30000 */\n inactivityTimeout?: number;\n /** Optional style overrides for the outer section. */\n style?: React.CSSProperties;\n /** Optional class name overrides for the outer section. */\n className?: string;\n /** Custom loading UI for the canvas. */\n loadingContent?: React.ReactNode;\n /** Custom controls component. If provided, replaces the default controls. */\n customControls?: (session: AvatarSessionReturn) => React.ReactNode;\n /** Custom status badge. If provided, replaces the default badge. */\n customStatusBadge?: (session: AvatarSessionReturn) => React.ReactNode;\n}\n\nexport const LiveKitAvatarSession: React.FC<LiveKitAvatarSessionProps> = ({\n avatarId,\n apiKey,\n api,\n turnOffMicWhenAISpeaking,\n livekitUrl,\n sessionDuration,\n inactivityTimeout,\n style,\n className,\n loadingContent,\n customControls,\n customStatusBadge,\n}) => {\n const session = useAvatarSession({\n avatarId,\n apiKey,\n api,\n turnOffMicWhenAISpeaking,\n livekitUrl,\n sessionDuration,\n inactivityTimeout,\n });\n\n const {\n token,\n isConnected,\n avatarDimensions,\n disconnect,\n startSessionTimer,\n } = session;\n\n return (\n <section\n className={className}\n style={{\n position: \"relative\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n overflow: \"hidden\",\n borderRadius: 16,\n border: \"1px solid rgba(255,255,255,0.1)\",\n boxShadow: \"0 25px 50px -12px rgba(0,0,0,0.5)\",\n height: \"80vh\",\n width: \"auto\",\n maxWidth: \"100%\",\n aspectRatio: avatarDimensions\n ? `${avatarDimensions.width} / ${avatarDimensions.height}`\n : \"3 / 4\",\n ...style,\n }}\n >\n {/* 1. Canvas Layer */}\n <AvatarCanvas session={session} loadingContent={loadingContent} />\n\n {/* 2. UI Overlay Layer */}\n <div\n style={{\n position: \"absolute\",\n inset: 0,\n zIndex: 20,\n pointerEvents: \"none\",\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"space-between\",\n padding: 16,\n }}\n >\n {/* Top Header / Status */}\n <div\n style={{\n display: \"flex\",\n width: \"100%\",\n alignItems: \"flex-start\",\n justifyContent: \"space-between\",\n }}\n >\n {customStatusBadge ? (\n customStatusBadge(session)\n ) : (\n <AvatarStatusBadge session={session} />\n )}\n <div />\n </div>\n\n {/* Bottom Controls */}\n {customControls ? (\n customControls(session)\n ) : (\n <AvatarControls session={session} />\n )}\n </div>\n\n {/* 3. LiveKit Room (hidden, audio-only) */}\n <div style={{ display: \"none\" }}>\n {token && livekitUrl && (\n <LiveKitRoom\n serverUrl={livekitUrl}\n token={token}\n connect={true}\n video={false}\n audio={true}\n onConnected={() => {\n session.markConnected();\n startSessionTimer();\n }}\n onDisconnected={disconnect}\n >\n <LiveKitAvatarProvider session={session} />\n </LiveKitRoom>\n )}\n </div>\n </section>\n );\n};\n"],"names":["INACTIVITY_TIMEOUT_MS","SESSION_DURATION_SECONDS","DEFAULT_LIVEKIT_URL","DEFAULT_API_BASE_URL","useAvatarSession","config","avatarId","apiKey","api","turnOffMicWhenAISpeaking","livekitUrlProp","sessionDuration","inactivityTimeout","livekitUrl","resolvedApi","useMemo","headers","resolvedAvatarId","response","containerRef","useRef","canvasRef","rendererRef","actionQueueRef","rendererState","setRendererState","useState","avatarDimensions","setAvatarDimensions","loadedActions","setLoadedActions","actionMetadata","setActionMetadata","isLoadingActions","setIsLoadingActions","token","setToken","isConnecting","setIsConnecting","isConnected","setIsConnected","timeRemaining","setTimeRemaining","sessionTimerRef","handleDisconnectRef","micMuted","setMicMuted","volume","setVolumeState","inactivityTimeoutRef","isEngineReady","ensureCanvas","useCallback","container","canvas","useEffect","cancelled","renderer","ZeroWeightRenderer","queue","ActionQueue","actionId","fallback","state","w","h","prev","next","data","e","hasWavedRef","connect","error","disconnect","markConnected","startSessionTimer","formatTime","seconds","m","s","setInactivityActive","isInactive","toggleMic","v","setMicMutedValue","muted","setVolume","toggleVolume","interrupt","runAction","AvatarCanvas","session","style","loadingContent","jsxs","jsx","Loader2","btnBase","connectBtnBase","AvatarControls","micHover","setMicHover","connectHover","setConnectHover","isSessionReady","micStyle","connectStyle","MicOff","Mic","Fragment","Power","Activity","AvatarStatusBadge","isUrgent","isWarning","wrapperStyle","LiveKitAvatarProvider","actionQueue","prevAudioTrackRef","audioTrack","useVoiceAssistant","localParticipant","useLocalParticipant","isUserSpeaking","useIsSpeaking","vadRef","loadedActionsRef","participant","encoder","payload","err","vad","VoiceActivityDetector","mediaTrack","turnEndTimerRef","wasAssistantSpeakingRef","stateStr","isAssistantSpeaking","wasAssistantSpeaking","handleTurnChanged","active","useDataChannel","msg","strData","hasAudio","RoomAudioRenderer","LiveKitAvatarSession","className","customControls","customStatusBadge","LiveKitRoom"],"mappings":"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"}
1
+ {"version":3,"file":"zeroweight-renderer-react.cjs.js","sources":["../src/useAvatarSession.ts","../src/AvatarCanvas.tsx","../src/AvatarControls.tsx","../src/AvatarStatusBadge.tsx","../src/LiveKitAvatarProvider.tsx","../src/LiveKitAvatarSession.tsx"],"sourcesContent":["/**\n * useAvatarSession — Main React hook for avatar rendering.\n *\n * Wraps ZeroWeightRenderer + ActionQueue in React lifecycle.\n * Returns reactive state and imperative methods.\n *\n * Usage:\n * const session = useAvatarSession({\n * avatarId: 'abc123',\n * apiKey: 'optional-api-key',\n * });\n */\n\nimport { useEffect, useRef, useState, useCallback, useMemo } from \"react\";\nimport { ZeroWeightRenderer, ActionQueue } from \"@zeroweight/renderer\";\nimport type { ActionMetadata, RendererState } from \"@zeroweight/renderer\";\nimport type { ZeroWeightApi } from \"./types\";\n\nconst INACTIVITY_TIMEOUT_MS = 30000;\nconst SESSION_DURATION_SECONDS = 120;\nconst DEFAULT_LIVEKIT_URL = \"wss://prod-project-pazuyq69.livekit.cloud\";\nconst DEFAULT_API_BASE_URL = \"https://api.zeroweight.ai/api/v1\";\n\nconst generateRandomName = () => {\n const adjectives = [\"Happy\", \"Swift\", \"Bright\", \"Cool\", \"Smart\"];\n const nouns = [\"User\", \"Guest\", \"Visitor\", \"Agent\", \"Caller\"];\n const randomNum = Math.floor(Math.random() * 1000);\n return `${adjectives[Math.floor(Math.random() * adjectives.length)]}${\n nouns[Math.floor(Math.random() * nouns.length)]\n }${randomNum}`;\n};\n\nexport interface AvatarSessionConfig {\n avatarId: string;\n /** Optional API key for the built-in ZeroWeight API integration. */\n apiKey?: string | null;\n /** Injectable API — provide your own fetch functions. */\n api?: ZeroWeightApi;\n /** Auto-mute when the assistant starts speaking, and unmute when it stops. */\n turnOffMicWhenAISpeaking?: boolean;\n /** LiveKit server URL. */\n livekitUrl?: string;\n /** Session duration in seconds. Default: 120 */\n sessionDuration?: number;\n /** Inactivity timeout in ms. Default: 30000 */\n inactivityTimeout?: number;\n}\n\nexport interface AvatarSessionReturn {\n // Refs\n containerRef: React.RefObject<HTMLDivElement | null>;\n\n // Renderer instance (for advanced use)\n renderer: ZeroWeightRenderer | null;\n actionQueue: ActionQueue | null;\n\n // Reactive state\n rendererState: RendererState;\n avatarDimensions: { width: number; height: number } | null;\n loadedActions: Set<string>;\n actionMetadata: Record<string, ActionMetadata>;\n isLoadingActions: boolean;\n isEngineReady: boolean;\n\n // Connection state\n token: string | null;\n isConnecting: boolean;\n isConnected: boolean;\n livekitUrl: string;\n\n // Session\n timeRemaining: number;\n formatTime: (seconds: number) => string;\n\n // Controls\n micMuted: boolean;\n volume: number;\n turnOffMicWhenAISpeaking: boolean;\n\n // Methods\n connect: () => Promise<void>;\n disconnect: () => void;\n toggleMic: () => void;\n setMicMuted: (muted: boolean) => void;\n setVolume: (v: number) => void;\n toggleVolume: () => void;\n interrupt: () => void;\n runAction: (actionId: string) => void;\n startSessionTimer: () => void;\n /** Called by LiveKitRoom onConnected */\n markConnected: () => void;\n\n // For LiveKitAvatarProvider internal use\n setInactivityActive: (isInactive: boolean) => void;\n}\n\nexport function useAvatarSession(config: AvatarSessionConfig): AvatarSessionReturn {\n const {\n avatarId,\n apiKey = null,\n api,\n turnOffMicWhenAISpeaking = true,\n livekitUrl: livekitUrlProp = DEFAULT_LIVEKIT_URL,\n sessionDuration = SESSION_DURATION_SECONDS,\n inactivityTimeout = INACTIVITY_TIMEOUT_MS,\n } = config;\n\n const livekitUrl = livekitUrlProp;\n const resolvedApi = useMemo<ZeroWeightApi>(() => {\n if (api) return api;\n\n const headers = apiKey ? { \"X-ZW-Api-Key\": apiKey } : undefined;\n\n return {\n getBundle: async (resolvedAvatarId: string) => {\n const response = await fetch(\n `${DEFAULT_API_BASE_URL}/avatars/bundle/${encodeURIComponent(\n resolvedAvatarId\n )}`,\n { headers }\n );\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch avatar bundle (${response.status} ${response.statusText})`\n );\n }\n\n return response.json();\n },\n getLiveKitToken: async (resolvedAvatarId: string) => {\n const params = new URLSearchParams({\n avatar_id: resolvedAvatarId,\n name: generateRandomName(),\n });\n const response = await fetch(\n `${DEFAULT_API_BASE_URL}/livekit/getToken?${params.toString()}`,\n { headers }\n );\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch LiveKit token (${response.status} ${response.statusText})`\n );\n }\n\n return response.json();\n },\n };\n }, [api, apiKey]);\n\n // Refs\n const containerRef = useRef<HTMLDivElement | null>(null);\n const canvasRef = useRef<HTMLCanvasElement | null>(null);\n const rendererRef = useRef<ZeroWeightRenderer | null>(null);\n const actionQueueRef = useRef<ActionQueue | null>(null);\n\n // Engine state\n const [rendererState, setRendererState] = useState<RendererState>(\"idle\");\n const [avatarDimensions, setAvatarDimensions] = useState<{\n width: number;\n height: number;\n } | null>(null);\n const [loadedActions, setLoadedActions] = useState<Set<string>>(\n new Set([\"listening\"])\n );\n const [actionMetadata, setActionMetadata] = useState<\n Record<string, ActionMetadata>\n >({\n listening: { kind: \"looped\" },\n speaking: { kind: \"looped\" },\n });\n const [isLoadingActions, setIsLoadingActions] = useState(false);\n\n // Connection state\n const [token, setToken] = useState<string | null>(null);\n const [isConnecting, setIsConnecting] = useState(false);\n const [isConnected, setIsConnected] = useState(false);\n\n // Session timer\n const [timeRemaining, setTimeRemaining] = useState(sessionDuration);\n const sessionTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);\n const handleDisconnectRef = useRef<() => void>(() => {});\n\n // Controls\n const [micMuted, setMicMuted] = useState(false);\n const [volume, setVolumeState] = useState(0.8);\n\n // Inactivity\n const inactivityTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const isEngineReady = rendererState === \"ready\";\n\n // ─── Ensure Canvas ──────────────────────────────────────────────\n\n const ensureCanvas = useCallback(() => {\n const container = containerRef.current;\n if (!container) return null;\n\n let canvas = container.querySelector(\"canvas\") as HTMLCanvasElement | null;\n if (!canvas) {\n canvas = document.createElement(\"canvas\");\n canvas.style.width = \"100%\";\n canvas.style.height = \"100%\";\n canvas.style.display = \"block\";\n container.appendChild(canvas);\n }\n canvasRef.current = canvas;\n return canvas;\n }, []);\n\n // ─── Init Renderer ──────────────────────────────────────────────\n\n useEffect(() => {\n let cancelled = false;\n\n const initRenderer = async () => {\n const canvas = ensureCanvas();\n if (!canvas) return;\n\n // Create renderer\n const renderer = new ZeroWeightRenderer();\n rendererRef.current = renderer;\n\n // Create action queue\n const queue = new ActionQueue((actionId, fallback) => {\n renderer.play(actionId, fallback);\n });\n actionQueueRef.current = queue;\n\n // Wire up renderer events\n renderer.on(\"stateChanged\", (state) => {\n if (!cancelled) setRendererState(state);\n });\n\n renderer.on(\"dimensions\", (w, h) => {\n if (!cancelled) setAvatarDimensions({ width: w, height: h });\n });\n\n renderer.on(\"actionLoaded\", (actionId) => {\n if (!cancelled) {\n setLoadedActions((prev) => {\n const next = new Set(prev);\n next.add(actionId);\n return next;\n });\n }\n });\n\n renderer.on(\"allActionsLoaded\", () => {\n if (!cancelled) setIsLoadingActions(false);\n });\n\n renderer.on(\"ready\", () => {\n // Update action metadata from renderer\n if (!cancelled) {\n setActionMetadata(renderer.getActionMetadata());\n queue.setActionMetadata(renderer.getActionMetadata());\n }\n });\n\n // Fetch bundle and init\n try {\n setIsLoadingActions(true);\n const data = await resolvedApi.getBundle(avatarId);\n if (cancelled) return;\n\n await renderer.init(canvas, { payload: data.payload });\n if (cancelled) return;\n\n // After init, update metadata again with all loaded data\n setActionMetadata(renderer.getActionMetadata());\n queue.setActionMetadata(renderer.getActionMetadata());\n } catch (e) {\n console.error(\"[useAvatarSession] Init failed:\", e);\n }\n };\n\n initRenderer();\n\n return () => {\n cancelled = true;\n rendererRef.current?.destroy();\n rendererRef.current = null;\n actionQueueRef.current = null;\n };\n }, [avatarId, ensureCanvas, resolvedApi]);\n\n // ─── Auto-wave on load ──────────────────────────────────────────\n\n const hasWavedRef = useRef(false);\n useEffect(() => {\n if (\n isEngineReady &&\n rendererRef.current &&\n loadedActions.has(\"wave_hand\") &&\n !hasWavedRef.current\n ) {\n rendererRef.current.play(\"wave_hand\", \"listening\");\n hasWavedRef.current = true;\n }\n }, [isEngineReady, loadedActions]);\n\n // ─── Connection ─────────────────────────────────────────────────\n\n const connect = useCallback(async () => {\n if (isConnecting || isConnected) return;\n\n setIsConnecting(true);\n try {\n await navigator.mediaDevices.getUserMedia({ audio: true });\n const data = await resolvedApi.getLiveKitToken(avatarId);\n setToken(data.token);\n } catch (error) {\n console.error(\"[useAvatarSession] Failed to connect:\", error);\n setIsConnecting(false);\n }\n }, [isConnecting, isConnected, avatarId, resolvedApi]);\n\n const disconnect = useCallback(() => {\n // Clear session timer\n if (sessionTimerRef.current) {\n clearInterval(sessionTimerRef.current);\n sessionTimerRef.current = null;\n }\n setTimeRemaining(sessionDuration);\n\n // Clear inactivity timer\n if (inactivityTimeoutRef.current) {\n clearTimeout(inactivityTimeoutRef.current);\n inactivityTimeoutRef.current = null;\n }\n\n setToken(null);\n setIsConnected(false);\n setIsConnecting(false);\n\n // Reset avatar to listening\n actionQueueRef.current?.forceListening();\n }, [sessionDuration]);\n\n // Keep disconnect ref current for timer\n useEffect(() => {\n handleDisconnectRef.current = disconnect;\n }, [disconnect]);\n\n const markConnected = useCallback(() => {\n setIsConnected(true);\n setIsConnecting(false);\n }, []);\n\n // ─── Session Timer ──────────────────────────────────────────────\n\n const startSessionTimer = useCallback(() => {\n if (sessionTimerRef.current) clearInterval(sessionTimerRef.current);\n setTimeRemaining(sessionDuration);\n\n sessionTimerRef.current = setInterval(() => {\n setTimeRemaining((prev) => {\n if (prev <= 1) {\n clearInterval(sessionTimerRef.current!);\n sessionTimerRef.current = null;\n setTimeout(() => handleDisconnectRef.current(), 0);\n return 0;\n }\n return prev - 1;\n });\n }, 1000);\n }, [sessionDuration]);\n\n const formatTime = useCallback((seconds: number) => {\n const m = Math.floor(seconds / 60)\n .toString()\n .padStart(2, \"0\");\n const s = (seconds % 60).toString().padStart(2, \"0\");\n return `${m}:${s}`;\n }, []);\n\n // Cleanup timer on unmount\n useEffect(() => {\n return () => {\n if (sessionTimerRef.current) clearInterval(sessionTimerRef.current);\n };\n }, []);\n\n // ─── Inactivity ─────────────────────────────────────────────────\n\n const setInactivityActive = useCallback(\n (isInactive: boolean) => {\n if (!isInactive) {\n if (inactivityTimeoutRef.current) {\n clearTimeout(inactivityTimeoutRef.current);\n inactivityTimeoutRef.current = null;\n }\n return;\n }\n\n if (inactivityTimeoutRef.current) return;\n\n inactivityTimeoutRef.current = setTimeout(() => {\n inactivityTimeoutRef.current = null;\n handleDisconnectRef.current();\n }, inactivityTimeout);\n },\n [inactivityTimeout]\n );\n\n // ─── Controls ───────────────────────────────────────────────────\n\n const toggleMic = useCallback(() => {\n setMicMuted((v) => !v);\n }, []);\n\n const setMicMutedValue = useCallback((muted: boolean) => {\n setMicMuted(muted);\n }, []);\n\n const setVolume = useCallback((v: number) => {\n setVolumeState(v);\n }, []);\n\n const toggleVolume = useCallback(() => {\n setVolumeState((v) => (v > 0 ? 0 : 1));\n }, []);\n\n const interrupt = useCallback(() => {\n rendererRef.current?.interrupt();\n }, []);\n\n const runAction = useCallback((actionId: string) => {\n // if (actionQueueRef.current) {\n // actionQueueRef.current.dispatch(actionId);\n // } else {\n // }\n rendererRef.current?.play(actionId, \"listening\");\n }, []);\n\n return {\n containerRef,\n renderer: rendererRef.current,\n actionQueue: actionQueueRef.current,\n rendererState,\n avatarDimensions,\n loadedActions,\n actionMetadata,\n isLoadingActions,\n isEngineReady,\n token,\n isConnecting,\n isConnected,\n livekitUrl,\n timeRemaining,\n formatTime,\n micMuted,\n volume,\n turnOffMicWhenAISpeaking,\n connect,\n disconnect,\n toggleMic,\n setMicMuted: setMicMutedValue,\n setVolume,\n toggleVolume,\n interrupt,\n runAction,\n startSessionTimer,\n markConnected,\n setInactivityActive,\n };\n}\n","/**\n * AvatarCanvas — Canvas container for the avatar renderer.\n *\n * Provides the div container where the renderer creates and manages a <canvas>.\n * Shows loading overlay when the engine is initializing.\n * Uses inline styles only — no CSS framework dependency.\n */\n\nimport React from \"react\";\nimport { Loader2 } from \"lucide-react\";\nimport type { AvatarSessionReturn } from \"./useAvatarSession\";\n\ninterface AvatarCanvasProps {\n session: AvatarSessionReturn;\n /** Optional style overrides for the outer container. */\n style?: React.CSSProperties;\n /** Custom loading component. */\n loadingContent?: React.ReactNode;\n}\n\nexport const AvatarCanvas: React.FC<AvatarCanvasProps> = ({\n session,\n style,\n loadingContent,\n}) => {\n return (\n <div\n style={{\n position: \"absolute\",\n inset: 0,\n width: \"100%\",\n height: \"100%\",\n zIndex: 0,\n pointerEvents: \"auto\",\n display: \"flex\",\n justifyContent: \"center\",\n alignItems: \"center\",\n overflow: \"hidden\",\n ...style,\n }}\n >\n <style>{`\n @keyframes zwr-spin {\n from { transform: rotate(0deg); }\n to { transform: rotate(360deg); }\n }\n @keyframes zwr-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.5; }\n }\n `}</style>\n <div\n ref={session.containerRef}\n style={{\n position: \"relative\",\n width: \"100%\",\n height: \"100%\",\n transition: \"all 0.5s ease-in-out\",\n }}\n >\n {/* Canvas is injected here by the renderer */}\n {!session.isEngineReady && (\n <div\n style={{\n position: \"absolute\",\n inset: 0,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n background: \"rgba(0,0,0,0.4)\",\n backdropFilter: \"blur(4px)\",\n zIndex: 10,\n }}\n >\n {loadingContent || (\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: 16,\n }}\n >\n <Loader2\n style={{\n width: 40,\n height: 40,\n color: \"rgba(255,255,255,0.5)\",\n animation: \"zwr-spin 1s linear infinite\",\n }}\n />\n <div\n style={{\n fontSize: 14,\n color: \"rgba(255,255,255,0.5)\",\n fontWeight: 500,\n letterSpacing: \"0.1em\",\n textTransform: \"uppercase\",\n animation: \"zwr-pulse 2s ease-in-out infinite\",\n }}\n >\n Initializing Neural Engine...\n </div>\n </div>\n )}\n </div>\n )}\n </div>\n </div>\n );\n};\n","/**\n * AvatarControls — Default control bar for the avatar session.\n *\n * Mic toggle, connect/disconnect button.\n * Can be replaced entirely by a custom UI.\n * Uses inline styles only — no CSS framework dependency.\n */\n\nimport React, { useState } from \"react\";\nimport {\n Mic,\n MicOff,\n Power,\n Activity,\n Loader2,\n} from \"lucide-react\";\nimport type { AvatarSessionReturn } from \"./useAvatarSession\";\n\ninterface AvatarControlsProps {\n session: AvatarSessionReturn;\n /** Optional style overrides for the wrapper. */\n style?: React.CSSProperties;\n}\n\nconst btnBase: React.CSSProperties = {\n flexShrink: 0,\n borderRadius: 9999,\n padding: 16,\n transition: \"all 0.3s\",\n border: \"none\",\n cursor: \"pointer\",\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n};\n\nconst connectBtnBase: React.CSSProperties = {\n position: \"relative\",\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: 12,\n borderRadius: 9999,\n padding: \"16px 24px\",\n fontSize: 16,\n fontWeight: 600,\n color: \"#fff\",\n whiteSpace: \"nowrap\",\n transition: \"all 0.3s\",\n border: \"none\",\n cursor: \"pointer\",\n};\n\nexport const AvatarControls: React.FC<AvatarControlsProps> = ({\n session,\n style,\n}) => {\n const {\n micMuted,\n toggleMic,\n isConnected,\n isConnecting,\n isEngineReady,\n loadedActions,\n connect,\n disconnect,\n } = session;\n\n const [micHover, setMicHover] = useState(false);\n const [connectHover, setConnectHover] = useState(false);\n\n const isSessionReady =\n isEngineReady &&\n loadedActions.has(\"listening\") &&\n loadedActions.has(\"speaking\");\n\n const micStyle: React.CSSProperties = micMuted\n ? {\n ...btnBase,\n background: \"rgba(239,68,68,0.1)\",\n color: \"#f87171\",\n boxShadow: \"inset 0 0 0 1px rgba(239,68,68,0.3)\",\n ...(micHover ? { background: \"rgba(239,68,68,0.2)\" } : {}),\n }\n : {\n ...btnBase,\n background: micHover ? \"rgba(255,255,255,0.1)\" : \"rgba(0,0,0,0.4)\",\n color: \"#fff\",\n boxShadow: \"inset 0 0 0 1px rgba(255,255,255,0.1)\",\n };\n\n const connectStyle: React.CSSProperties = isConnected\n ? {\n ...connectBtnBase,\n background: connectHover ? \"rgba(239,68,68,0.22)\" : \"rgba(239,68,68,0.14)\",\n boxShadow: \"inset 0 0 0 1px rgba(239,68,68,0.5), 0 0 20px rgba(239,68,68,0.2)\",\n }\n : {\n ...connectBtnBase,\n background: \"linear-gradient(to right, #7c3aed, #db2777, #f97316)\",\n boxShadow: \"0 0 30px rgba(236,72,153,0.3)\",\n opacity: connectHover ? 0.9 : 1,\n };\n\n if (isConnecting || !isSessionReady) {\n connectStyle.opacity = 0.5;\n connectStyle.cursor = \"not-allowed\";\n }\n\n return (\n <div\n style={{\n width: \"100%\",\n marginTop: \"auto\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: 16,\n paddingBottom: 16,\n ...style,\n }}\n >\n <style>{`\n @keyframes zwr-spin {\n from { transform: rotate(0deg); }\n to { transform: rotate(360deg); }\n }\n `}</style>\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: 16,\n width: \"100%\",\n pointerEvents: \"auto\",\n }}\n >\n {/* Mic Toggle */}\n <button\n type=\"button\"\n onClick={toggleMic}\n onMouseEnter={() => setMicHover(true)}\n onMouseLeave={() => setMicHover(false)}\n style={micStyle}\n title={micMuted ? \"Unmute mic\" : \"Mute mic\"}\n >\n {micMuted ? <MicOff size={24} /> : <Mic size={24} />}\n </button>\n\n {/* Main Connect Button */}\n <button\n type=\"button\"\n onClick={isConnected ? disconnect : connect}\n onMouseEnter={() => setConnectHover(true)}\n onMouseLeave={() => setConnectHover(false)}\n disabled={!isConnected && (isConnecting || !isSessionReady)}\n style={connectStyle}\n >\n {isConnecting ? (\n <>\n <Loader2 size={20} style={{ animation: \"zwr-spin 1s linear infinite\" }} />\n <span>Connecting...</span>\n </>\n ) : !isConnected && !isSessionReady ? (\n <>\n <Loader2 size={20} style={{ animation: \"zwr-spin 1s linear infinite\" }} />\n <span>Loading Avatar...</span>\n </>\n ) : isConnected ? (\n <>\n <Power size={20} />\n <span>End Session</span>\n </>\n ) : (\n <>\n <Activity size={20} />\n <span>Start Session</span>\n </>\n )}\n </button>\n </div>\n </div>\n );\n};\n","/**\n * AvatarStatusBadge — Connection status indicator with session timer.\n * Uses inline styles only — no CSS framework dependency.\n */\n\nimport React from \"react\";\nimport type { AvatarSessionReturn } from \"./useAvatarSession\";\n\ninterface AvatarStatusBadgeProps {\n session: AvatarSessionReturn;\n}\n\nexport const AvatarStatusBadge: React.FC<AvatarStatusBadgeProps> = ({\n session,\n}) => {\n const { isConnected, timeRemaining, formatTime } = session;\n\n const isUrgent = isConnected && timeRemaining <= 30;\n const isWarning = isConnected && timeRemaining > 30 && timeRemaining <= 60;\n\n const wrapperStyle: React.CSSProperties = {\n display: \"flex\",\n alignItems: \"center\",\n gap: 8,\n backdropFilter: \"blur(12px)\",\n padding: \"6px 12px\",\n borderRadius: 9999,\n border: \"1px solid\",\n pointerEvents: \"auto\",\n transition: \"all 0.3s\",\n ...(isUrgent\n ? {\n background: \"rgba(239,68,68,0.3)\",\n borderColor: \"rgba(239,68,68,0.4)\",\n boxShadow: \"0 0 15px rgba(239,68,68,0.3)\",\n animation: \"zwr-pulse 2s ease-in-out infinite\",\n }\n : isWarning\n ? {\n background: \"rgba(249,115,22,0.2)\",\n borderColor: \"rgba(249,115,22,0.3)\",\n boxShadow: \"0 4px 12px rgba(0,0,0,0.3)\",\n }\n : {\n background: \"rgba(0,0,0,0.4)\",\n borderColor: \"rgba(255,255,255,0.1)\",\n boxShadow: \"0 4px 12px rgba(0,0,0,0.3)\",\n }),\n };\n\n return (\n <>\n <style>{`\n @keyframes zwr-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.5; }\n }\n `}</style>\n <div style={wrapperStyle}>\n <div\n style={{\n height: 8,\n width: 8,\n borderRadius: \"50%\",\n ...(isConnected\n ? {\n background: \"#22c55e\",\n boxShadow: \"0 0 10px rgba(34,197,94,0.5)\",\n }\n : {\n background: \"rgba(239,68,68,0.5)\",\n }),\n }}\n />\n <span\n style={{\n fontSize: 10,\n fontWeight: 700,\n letterSpacing: \"0.05em\",\n color: \"rgba(255,255,255,0.7)\",\n textTransform: \"uppercase\",\n }}\n >\n {isConnected ? `Online ${formatTime(timeRemaining)}` : \"Offline\"}\n </span>\n </div>\n </>\n );\n};\n","/**\n * LiveKitAvatarProvider — Bridge between LiveKit hooks and the ActionQueue/VAD.\n * Uses inline styles only — no CSS framework dependency.\n *\n * This component MUST be rendered inside a <LiveKitRoom>.\n */\n\nimport React, { useEffect, useRef } from \"react\";\nimport {\n useVoiceAssistant,\n RoomAudioRenderer,\n useDataChannel,\n useIsSpeaking,\n useLocalParticipant,\n} from \"@livekit/components-react\";\nimport { VoiceActivityDetector } from \"@zeroweight/renderer\";\nimport type { AvatarSessionReturn } from \"./useAvatarSession\";\n\ninterface LiveKitAvatarProviderProps {\n session: AvatarSessionReturn;\n}\n\nexport const LiveKitAvatarProvider: React.FC<LiveKitAvatarProviderProps> = ({\n session,\n}) => {\n const {\n renderer,\n actionQueue,\n micMuted,\n volume,\n setInactivityActive,\n loadedActions,\n token,\n isConnected,\n } = session;\n const { turnOffMicWhenAISpeaking, setMicMuted } = session;\n\n const prevAudioTrackRef = useRef<boolean>(false);\n const { state, audioTrack } = useVoiceAssistant();\n const localParticipant = useLocalParticipant();\n const isUserSpeaking = useIsSpeaking(localParticipant.localParticipant);\n\n // VAD instance\n const vadRef = useRef<VoiceActivityDetector | null>(null);\n\n // Ref to latest loadedActions so data channel callback doesn't go stale\n const loadedActionsRef = useRef(loadedActions);\n useEffect(() => {\n loadedActionsRef.current = loadedActions;\n }, [loadedActions]);\n\n useEffect(() => {\n const participant = localParticipant.localParticipant;\n if (!renderer || !participant) return;\n\n const encoder = new TextEncoder();\n const unsubscribe = renderer.onOneshotComplete((actionId) => {\n const payload = encoder.encode(\n JSON.stringify({\n type: \"ACTION_FINISHED\",\n action: actionId,\n })\n );\n\n participant.publishData(payload, {\n reliable: true,\n }).catch((err) => {\n console.error(\"[LiveKitAvatarProvider] Failed to publish action completion:\", err);\n });\n });\n\n return unsubscribe;\n }, [renderer, localParticipant.localParticipant]);\n\n // ─── VAD Setup ──────────────────────────────────────────────────\n\n useEffect(() => {\n if (!actionQueue) return;\n\n const vad = new VoiceActivityDetector({\n threshold: 0.008,\n analyseIntervalMs: 30,\n speechStartFrames: 1,\n speechPauseFrames: 30,\n turnEndFrames: 50,\n });\n vadRef.current = vad;\n\n vad.on(\"speechStart\", () => {\n actionQueue.setTurnActive(true);\n actionQueue.setSpeechState(\"speaking\");\n });\n\n vad.on(\"turnEnd\", () => {\n actionQueue.setTurnActive(false);\n });\n\n return () => {\n vad.stop();\n vadRef.current = null;\n };\n }, [actionQueue]);\n\n // ─── Connect/disconnect VAD to audio track ──────────────────────\n\n useEffect(() => {\n const vad = vadRef.current;\n if (!vad) return;\n\n if (audioTrack?.publication?.track) {\n const mediaTrack = audioTrack.publication.track.mediaStreamTrack;\n if (mediaTrack) {\n vad.start(mediaTrack);\n }\n } else {\n vad.stop();\n }\n }, [audioTrack?.publication?.track]);\n\n // ─── LiveKit state → turn management ─────────────────────────────\n\n const turnEndTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const wasAssistantSpeakingRef = useRef(false);\n\n useEffect(() => {\n if (!actionQueue) return;\n\n const stateStr = state as string;\n const isSessionInactive = !token || !isConnected || stateStr === \"disconnected\";\n\n if (isSessionInactive) {\n if (turnEndTimerRef.current) {\n clearTimeout(turnEndTimerRef.current);\n turnEndTimerRef.current = null;\n }\n vadRef.current?.endTurn();\n actionQueue.forceListening();\n return;\n }\n\n if (stateStr === \"speaking\") {\n if (turnEndTimerRef.current) {\n clearTimeout(turnEndTimerRef.current);\n turnEndTimerRef.current = null;\n }\n actionQueue.setTurnActive(true);\n actionQueue.setSpeechState(\"speaking\");\n } else if (stateStr === \"listening\" || stateStr === \"idle\") {\n if (turnEndTimerRef.current) {\n clearTimeout(turnEndTimerRef.current);\n turnEndTimerRef.current = null;\n }\n vadRef.current?.endTurn();\n actionQueue.setTurnActive(false);\n } else if (stateStr === \"thinking\") {\n if (!turnEndTimerRef.current) {\n turnEndTimerRef.current = setTimeout(() => {\n turnEndTimerRef.current = null;\n vadRef.current?.endTurn();\n actionQueue.setTurnActive(false);\n }, 500);\n }\n }\n }, [actionQueue, isConnected, state, token]);\n\n useEffect(() => {\n const isAssistantSpeaking = state === \"speaking\";\n\n if (!turnOffMicWhenAISpeaking) {\n wasAssistantSpeakingRef.current = isAssistantSpeaking;\n return;\n }\n\n const wasAssistantSpeaking = wasAssistantSpeakingRef.current;\n\n if (!wasAssistantSpeaking && isAssistantSpeaking) {\n if (!micMuted) {\n setMicMuted(true);\n }\n } else if (wasAssistantSpeaking && !isAssistantSpeaking) {\n if (micMuted) {\n setMicMuted(false);\n }\n }\n\n wasAssistantSpeakingRef.current = isAssistantSpeaking;\n }, [micMuted, setMicMuted, state, turnOffMicWhenAISpeaking]);\n\n useEffect(() => {\n if (!turnOffMicWhenAISpeaking || !actionQueue) return;\n\n const handleTurnChanged = (active: boolean) => {\n if (!active) {\n setMicMuted(false);\n }\n };\n\n actionQueue.on(\"turnChanged\", handleTurnChanged);\n\n return () => {\n actionQueue.off(\"turnChanged\", handleTurnChanged);\n };\n }, [actionQueue, setMicMuted, turnOffMicWhenAISpeaking]);\n\n // Cleanup turn-end timer on unmount\n useEffect(() => {\n return () => {\n if (turnEndTimerRef.current) {\n clearTimeout(turnEndTimerRef.current);\n }\n };\n }, []);\n\n // ─── Data channel (backend-triggered actions) ───────────────────\n\n useDataChannel((msg) => {\n try {\n const decoder = new TextDecoder();\n const strData = decoder.decode(msg.payload);\n const data = JSON.parse(strData);\n\n if (data.type === \"AVATAR_UPDATE\") {\n const actionId = data.action;\n if (!loadedActionsRef.current.has(actionId)) {\n return;\n }\n actionQueue?.dispatch(actionId);\n }\n } catch (err) {\n console.error(\"[LiveKitAvatarProvider] Failed to parse data message:\", err);\n }\n });\n\n // ─── Inactivity tracking: only count when both AI and user are silent ───\n\n useEffect(() => {\n const isAssistantSpeaking = state === \"speaking\";\n setInactivityActive(!isAssistantSpeaking && !isUserSpeaking);\n }, [isUserSpeaking, setInactivityActive, state]);\n\n // ─── Audio loss / disconnect fallback ───────────────────────────\n\n useEffect(() => {\n if (!actionQueue) return;\n\n const hasAudio = !!audioTrack;\n const isDisconnectedState = state === \"disconnected\";\n\n if (!hasAudio || isDisconnectedState) {\n vadRef.current?.endTurn();\n actionQueue.forceListening();\n }\n prevAudioTrackRef.current = hasAudio;\n }, [audioTrack, state, actionQueue]);\n\n // ─── Mic mute sync ─────────────────────────────────────────────\n\n useEffect(() => {\n const participant = localParticipant.localParticipant;\n if (!participant) return;\n\n participant.setMicrophoneEnabled(!micMuted).catch((err) => {\n console.error(\"[LiveKitAvatarProvider] Failed to set mic state:\", err);\n });\n }, [micMuted, localParticipant.localParticipant]);\n\n return (\n <div style={{ position: \"absolute\", bottom: 80, left: 8, right: 8, display: \"flex\", flexDirection: \"column\", gap: 8 }}>\n <RoomAudioRenderer volume={volume} />\n </div>\n );\n};\n","/**\n * LiveKitAvatarSession — Full drop-in avatar component.\n * Uses inline styles only — no CSS framework dependency.\n *\n * Simplest usage:\n * import { LiveKitAvatarSession } from \"@zeroweight/react\";\n * <LiveKitAvatarSession\n * avatarId=\"abc123\"\n * apiKey=\"optional-api-key\"\n * />\n */\n\nimport React from \"react\";\nimport { LiveKitRoom } from \"@livekit/components-react\";\nimport \"@livekit/components-styles\";\n\nimport { useAvatarSession } from \"./useAvatarSession\";\nimport type { AvatarSessionReturn } from \"./useAvatarSession\";\nimport { AvatarCanvas } from \"./AvatarCanvas\";\nimport { AvatarControls } from \"./AvatarControls\";\nimport { AvatarStatusBadge } from \"./AvatarStatusBadge\";\nimport { LiveKitAvatarProvider } from \"./LiveKitAvatarProvider\";\nimport type { ZeroWeightApi } from \"./types\";\n\ninterface LiveKitAvatarSessionProps {\n avatarId: string;\n /** Optional API key for the built-in ZeroWeight API integration. */\n apiKey?: string | null;\n /** Injectable API — provide your own fetch functions. */\n api?: ZeroWeightApi;\n /** Auto-mute when the assistant starts speaking, and unmute when it stops. */\n turnOffMicWhenAISpeaking?: boolean;\n /** LiveKit server URL (e.g. \"wss://your-livekit.example.com\"). */\n livekitUrl?: string;\n /** Session duration in seconds. Default: 120 */\n sessionDuration?: number;\n /** Inactivity timeout in ms. Default: 30000 */\n inactivityTimeout?: number;\n /** Optional style overrides for the outer section. */\n style?: React.CSSProperties;\n /** Optional class name overrides for the outer section. */\n className?: string;\n /** Custom loading UI for the canvas. */\n loadingContent?: React.ReactNode;\n /** Custom controls component. If provided, replaces the default controls. */\n customControls?: (session: AvatarSessionReturn) => React.ReactNode;\n /** Custom status badge. If provided, replaces the default badge. */\n customStatusBadge?: (session: AvatarSessionReturn) => React.ReactNode;\n}\n\nexport const LiveKitAvatarSession: React.FC<LiveKitAvatarSessionProps> = ({\n avatarId,\n apiKey,\n api,\n turnOffMicWhenAISpeaking,\n livekitUrl,\n sessionDuration,\n inactivityTimeout,\n style,\n className,\n loadingContent,\n customControls,\n customStatusBadge,\n}) => {\n const session = useAvatarSession({\n avatarId,\n apiKey,\n api,\n turnOffMicWhenAISpeaking,\n livekitUrl,\n sessionDuration,\n inactivityTimeout,\n });\n\n const {\n token,\n isConnected,\n avatarDimensions,\n disconnect,\n startSessionTimer,\n } = session;\n\n return (\n <section\n className={className}\n style={{\n position: \"relative\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n overflow: \"hidden\",\n borderRadius: 16,\n border: \"1px solid rgba(255,255,255,0.1)\",\n boxShadow: \"0 25px 50px -12px rgba(0,0,0,0.5)\",\n height: \"80vh\",\n width: \"auto\",\n maxWidth: \"100%\",\n aspectRatio: avatarDimensions\n ? `${avatarDimensions.width} / ${avatarDimensions.height}`\n : \"3 / 4\",\n ...style,\n }}\n >\n {/* 1. Canvas Layer */}\n <AvatarCanvas session={session} loadingContent={loadingContent} />\n\n {/* 2. UI Overlay Layer */}\n <div\n style={{\n position: \"absolute\",\n inset: 0,\n zIndex: 20,\n pointerEvents: \"none\",\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"space-between\",\n padding: 16,\n }}\n >\n {/* Top Header / Status */}\n <div\n style={{\n display: \"flex\",\n width: \"100%\",\n alignItems: \"flex-start\",\n justifyContent: \"space-between\",\n }}\n >\n {customStatusBadge ? (\n customStatusBadge(session)\n ) : (\n <AvatarStatusBadge session={session} />\n )}\n <div />\n </div>\n\n {/* Bottom Controls */}\n {customControls ? (\n customControls(session)\n ) : (\n <AvatarControls session={session} />\n )}\n </div>\n\n {/* 3. LiveKit Room (hidden, audio-only) */}\n <div style={{ display: \"none\" }}>\n {token && session.livekitUrl && (\n <LiveKitRoom\n serverUrl={session.livekitUrl}\n token={token}\n connect={true}\n video={false}\n audio={true}\n onConnected={() => {\n session.markConnected();\n startSessionTimer();\n }}\n onDisconnected={disconnect}\n >\n <LiveKitAvatarProvider session={session} />\n </LiveKitRoom>\n )}\n </div>\n </section>\n );\n};\n"],"names":["INACTIVITY_TIMEOUT_MS","SESSION_DURATION_SECONDS","DEFAULT_LIVEKIT_URL","DEFAULT_API_BASE_URL","generateRandomName","adjectives","nouns","randomNum","useAvatarSession","config","avatarId","apiKey","api","turnOffMicWhenAISpeaking","livekitUrlProp","sessionDuration","inactivityTimeout","livekitUrl","resolvedApi","useMemo","headers","resolvedAvatarId","response","params","containerRef","useRef","canvasRef","rendererRef","actionQueueRef","rendererState","setRendererState","useState","avatarDimensions","setAvatarDimensions","loadedActions","setLoadedActions","actionMetadata","setActionMetadata","isLoadingActions","setIsLoadingActions","token","setToken","isConnecting","setIsConnecting","isConnected","setIsConnected","timeRemaining","setTimeRemaining","sessionTimerRef","handleDisconnectRef","micMuted","setMicMuted","volume","setVolumeState","inactivityTimeoutRef","isEngineReady","ensureCanvas","useCallback","container","canvas","useEffect","cancelled","renderer","ZeroWeightRenderer","queue","ActionQueue","actionId","fallback","state","w","h","prev","next","data","e","hasWavedRef","connect","error","disconnect","markConnected","startSessionTimer","formatTime","seconds","m","s","setInactivityActive","isInactive","toggleMic","v","setMicMutedValue","muted","setVolume","toggleVolume","interrupt","runAction","AvatarCanvas","session","style","loadingContent","jsxs","jsx","Loader2","btnBase","connectBtnBase","AvatarControls","micHover","setMicHover","connectHover","setConnectHover","isSessionReady","micStyle","connectStyle","MicOff","Mic","Fragment","Power","Activity","AvatarStatusBadge","isUrgent","isWarning","wrapperStyle","LiveKitAvatarProvider","actionQueue","prevAudioTrackRef","audioTrack","useVoiceAssistant","localParticipant","useLocalParticipant","isUserSpeaking","useIsSpeaking","vadRef","loadedActionsRef","participant","encoder","payload","err","vad","VoiceActivityDetector","mediaTrack","turnEndTimerRef","wasAssistantSpeakingRef","stateStr","isAssistantSpeaking","wasAssistantSpeaking","handleTurnChanged","active","useDataChannel","msg","strData","hasAudio","RoomAudioRenderer","LiveKitAvatarSession","className","customControls","customStatusBadge","LiveKitRoom"],"mappings":"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"}