@streamoji/avatar-widget 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/avatar-widget.cjs +8 -0
- package/dist/avatar-widget.cjs.map +1 -0
- package/dist/{style.css → avatar-widget.css} +1 -1
- package/dist/avatar-widget.js +1125 -2385
- package/dist/avatar-widget.js.map +1 -1
- package/dist/avatar-widget.umd.css +1 -1
- package/dist/avatar-widget.umd.js +158 -158
- package/dist/avatar-widget.umd.js.map +1 -1
- package/package.json +2 -2
- package/dist/avatar-widget.umd.cjs +0 -9
- package/dist/avatar-widget.umd.cjs.map +0 -1
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("react/jsx-runtime"),We=require("@react-three/drei"),Xe=require("@react-three/fiber"),t=require("react"),kt=require("three"),At=require("three/examples/jsm/loaders/GLTFLoader.js");function Lt(c){const w=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(c){for(const m in c)if(m!=="default"){const h=Object.getOwnPropertyDescriptor(c,m);Object.defineProperty(w,m,h.get?h:{enumerable:!0,get:()=>c[m]})}}return w.default=c,Object.freeze(w)}const Ce=Lt(kt),W=(...c)=>{},Et=(...c)=>{},ot=({analyser:c})=>{const w=t.useRef(null),m=t.useRef(null);return t.useEffect(()=>{const h=w.current;if(!h)return;const C=h.getContext("2d",{alpha:!0});if(!C)return;let E,A=null;c&&(c.fftSize=128,A=new Uint8Array(c.frequencyBinCount));const le=()=>{E=requestAnimationFrame(le),(h.width!==h.offsetWidth||h.height!==h.offsetHeight)&&(h.width=h.offsetWidth,h.height=h.offsetHeight);const ee=h.width,O=h.height;if(ee===0||O===0)return;const y=O/2;C.clearRect(0,0,ee,O),C.fillStyle="#1e293b";const X=2,L=X+2,ie=ee*.95,b=Math.floor(ie/L);(!m.current||m.current.length!==b)&&(m.current=new Float32Array(b).fill(2));const xe=b*L,K=(ee-xe)/2;c&&A&&c.getByteFrequencyData(A);const de=A?A.length:0,H=Math.floor(de*.7)/b,M=new Float32Array(b);for(let g=0;g<b;g++){let V=0;if(A&&H>0){const _=Math.floor(g*H),Y=Math.floor((g+1)*H);for(let q=_;q<Y;q++){const oe=A[q]||0;oe>V&&(V=oe)}}V<10&&(V=0);const R=V/255,x=V>0?Math.max(2,Math.pow(R,1.4)*O*.25):2;M[g]=x}for(let g=0;g<b;g++){const V=b-1-g,R=Math.max(M[g],M[V]),x=m.current[g]+(R-m.current[g])*.3;m.current[g]=x;const _=K+g*L,Y=y-x/2;C.beginPath(),C.roundRect?C.roundRect(_,Y,X,x,4):C.fillRect(_,Y,X,x),C.fill()}};return le(),()=>{cancelAnimationFrame(E)}},[c]),o.jsx("canvas",{ref:w,style:{width:"100%",height:"100%",display:"block"}})},at="https://ai.streamoji.com",Tt="https://pub-48df6f7d60d6440bbd01676ea5d90e55.r2.dev",ct="https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/default-models/fullbodyavatarmale.glb";async function Mt(c){const w=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(c));return Array.from(new Uint8Array(w)).map(m=>m.toString(16).padStart(2,"0")).join("")}function It(c){const[w,m]=t.useState(null);return t.useEffect(()=>{if(!c){m(null);return}let h=!1;return Mt(c).then(C=>{if(h)return;const E=`${Tt}/${C}.glb`;fetch(E,{method:"HEAD"}).then(A=>{h||m(A.ok?E:ct)}).catch(()=>{h||m(ct)})}),()=>{h=!0}},[c]),w}const Ct=["https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/idle/M_Standing_Idle_Variations_001.glb","https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/idle/M_Standing_Idle_Variations_002.glb","https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/idle/M_Standing_Idle_002.glb","https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/idle/M_Standing_Idle_Variations_005.glb"],Ot=[{id:"m_expr_01",name:"Friendly Wave",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_001.glb"},{id:"m_expr_02",name:"You There",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_002.glb"},{id:"m_expr_04",name:"Awkward Agreement",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_004.glb"},{id:"m_expr_05",name:"What's Going On?",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_005.glb"},{id:"m_expr_06",name:"Tired Stretch",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_006.glb"},{id:"m_expr_07",name:"Conceilied Laughter",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_007.glb"},{id:"m_expr_08",name:"You Come Here",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_008.glb"},{id:"m_expr_09",name:"Come Here Kid",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_009.glb"},{id:"m_expr_10",name:"Come Here Everyone",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_010.glb"},{id:"m_expr_11",name:"No Freaking Way",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_011.glb"},{id:"m_expr_12",name:"Cheerful Approval",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_012.glb"},{id:"m_expr_13",name:"Waving Hello",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_013.glb"},{id:"m_expr_14",name:"Checking Surroundings",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_014.glb"},{id:"m_expr_15",name:"Referee Warning",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_015.glb"},{id:"m_expr_16",name:"You Thumbs Down",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_016.glb"},{id:"m_expr_17",name:"Side Thumbs Down",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_017.glb"},{id:"m_expr_18",name:"You're Finished",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_018.glb"},{id:"m_talk_01",name:"Oh God, Why Me?",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Talking_Variations_001.glb"},{id:"m_talk_02",name:"What Are You Doing?",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Talking_Variations_002.glb"},{id:"m_talk_03",name:"What Am I doing?",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Talking_Variations_003.glb"},{id:"m_talk_04",name:"No Way",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Talking_Variations_004.glb"},{id:"m_talk_05",name:"What's Going On?",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Talking_Variations_005.glb"},{id:"m_talk_06",name:"I have no idea",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Talking_Variations_006.glb"},{id:"m_talk_07",name:"What's going on here?",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Talking_Variations_007.glb"},{id:"m_talk_08",name:"Let's stop",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Talking_Variations_008.glb"},{id:"m_talk_09",name:"Fed Up Moment",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Talking_Variations_009.glb"},{id:"m_talk_10",name:"What's This? Hold On",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Talking_Variations_010.glb"},{id:"f_talk_01",name:"Great Job Clap",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/F_Talking_Variations_001.glb"},{id:"f_talk_02",name:"Chill Stretch",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/F_Talking_Variations_002.glb"},{id:"f_talk_03",name:"This Is Me",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/F_Talking_Variations_003.glb"},{id:"f_talk_04",name:"Empathize",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/F_Talking_Variations_004.glb"},{id:"f_talk_05",name:"Loose Hands Stretch",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/F_Talking_Variations_005.glb"},{id:"f_talk_06",name:"Take It Easy",url:"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/F_Talking_Variations_006.glb"}],Dt=["https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Talking_Variations_005.glb","https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Talking_Variations_007.glb"],Ft={zoom:.85,position:[.15,-.8,0],scale:1.5,rotation:[.15,.02,0]},Ut=[-.45,1.9,.1],Nt={browInnerUp:.2},Ye=.18,jt=1,Pe={neutral:{eyeLookDownLeft:.1,eyeLookDownRight:.1},happy:{mouthSmileLeft:.2,mouthSmileRight:.2,eyeLookDownLeft:.1,eyeLookDownRight:.1},sad:{eyeLookDownLeft:.2,eyeLookDownRight:.2,browDownRight:.1,browInnerUp:.6,browOuterUpRight:.2,eyeSquintLeft:.7,eyeSquintRight:.7,mouthFrownLeft:.8,mouthFrownRight:.8,mouthLeft:.2,mouthPucker:.5,mouthRollLower:.2,mouthRollUpper:.2,mouthShrugLower:.2,mouthShrugUpper:.2,mouthStretchLeft:.4},angry:{eyeLookDownLeft:.1,eyeLookDownRight:.1,browDownLeft:.6,browDownRight:.6,jawForward:.3,mouthFrownLeft:.7,mouthFrownRight:.7,mouthRollLower:.2,mouthShrugLower:.3},fear:{browInnerUp:.7,eyeSquintLeft:.5,eyeSquintRight:.5,eyeWideLeft:.6,eyeWideRight:.6,mouthClose:.1,mouthFunnel:.3,mouthShrugLower:.5,mouthShrugUpper:.5},disgust:{browDownLeft:.7,browDownRight:.1,browInnerUp:.3,eyeSquintLeft:1,eyeSquintRight:1,eyeWideLeft:.5,eyeWideRight:.5,mouthLeft:.4,mouthPressLeft:.3,mouthRollLower:.3,mouthShrugLower:.3,mouthShrugUpper:.8,mouthUpperUpLeft:.3,noseSneerLeft:1,noseSneerRight:.7},love:{browInnerUp:.4,browOuterUpLeft:.2,browOuterUpRight:.2,mouthSmileLeft:.2,mouthSmileRight:.2,eyeBlinkLeft:.6,eyeBlinkRight:.6,eyeWideLeft:.7,eyeWideRight:.7,mouthDimpleLeft:.1,mouthDimpleRight:.1,mouthPressLeft:.2,mouthShrugUpper:.2,mouthUpperUpLeft:.1,mouthUpperUpRight:.1}},Pt=[{key:"viseme_aa",mix:{jawOpen:.6}},{key:"viseme_E",mix:{mouthPressLeft:.8,mouthPressRight:.8,mouthDimpleLeft:1,mouthDimpleRight:1,jawOpen:.3}},{key:"viseme_I",mix:{mouthPressLeft:.6,mouthPressRight:.6,mouthDimpleLeft:.6,mouthDimpleRight:.6,jawOpen:.2}},{key:"viseme_O",mix:{mouthPucker:1,jawForward:.6,jawOpen:.2}},{key:"viseme_U",mix:{mouthFunnel:1}},{key:"viseme_PP",mix:{mouthRollLower:.3,mouthRollUpper:.3,mouthUpperUpLeft:.3,mouthUpperUpRight:.3}},{key:"viseme_FF",mix:{mouthPucker:1,mouthShrugUpper:1,mouthLowerDownLeft:.2,mouthLowerDownRight:.2,mouthDimpleLeft:1,mouthDimpleRight:1,mouthRollLower:.3}},{key:"viseme_DD",mix:{mouthPressLeft:.8,mouthPressRight:.8,mouthFunnel:.5,jawOpen:.2}},{key:"viseme_SS",mix:{mouthPressLeft:.8,mouthPressRight:.8,mouthLowerDownLeft:.5,mouthLowerDownRight:.5,jawOpen:.1}},{key:"viseme_TH",mix:{mouthRollUpper:.3,jawOpen:.2,tongueOut:.4}},{key:"viseme_CH",mix:{mouthPucker:.5,jawOpen:.2}},{key:"viseme_RR",mix:{mouthPucker:.5,jawOpen:.2}},{key:"viseme_kk",mix:{mouthLowerDownLeft:.4,mouthLowerDownRight:.4,mouthDimpleLeft:.3,mouthDimpleRight:.3,mouthFunnel:.3,mouthPucker:.3,jawOpen:.15}},{key:"viseme_nn",mix:{mouthLowerDownLeft:.4,mouthLowerDownRight:.4,mouthDimpleLeft:.3,mouthDimpleRight:.3,mouthFunnel:.3,mouthPucker:.3,jawOpen:.15,tongueOut:.2}},{key:"viseme_sil",mix:{}}],Wt={aei:[{v:"E",w:.8},{v:"I",w:.2}],ee:[{v:"I",w:1}],oo:[{v:"O",w:1}],u:[{v:"U",w:1}],aa:[{v:"aa",w:1}],ah:[{v:"aa",w:.7},{v:"O",w:.3}],bmp:[{v:"PP",w:1}],fv:[{v:"FF",w:1}],th:[{v:"TH",w:1}],l:[{v:"nn",w:1}],r:[{v:"RR",w:1}],qw:[{v:"U",w:.6},{v:"O",w:.4}],chjsh:[{v:"CH",w:1}],cdgknstxyz:[{v:"DD",w:.6},{v:"SS",w:.4}],sil:[{v:"sil",w:1}]};function Vt(c){if(!c)return[{v:"sil",w:1}];const w=c.toLowerCase();return Wt[w]??[{v:"sil",w:1}]}function Bt({target:c}){const{camera:w}=Xe.useThree();return t.useEffect(()=>{w.lookAt(...c)},[w,c]),null}function J(c,w,m){if(!c||!c.morphTargetDictionary)return;const h=c,C=h.morphTargetDictionary,E=h.morphTargetInfluences;if(E)for(const A in C)A.toLowerCase()===w.toLowerCase()&&(E[C[A]]=m)}function ut(c,w=.97){if(!c)return;const m=c;if(m.morphTargetInfluences)for(let h=0;h<m.morphTargetInfluences.length;h++)m.morphTargetInfluences[h]*=w}const $t=t.memo(({avatarUrl:c,isPlayingRef:w,visemeQueueRef:m,audioContextRef:h,responseAudioStartTimeRef:C,adjustments:E,mood:A,expression:le,agentResponse:ee,isSpeaking:O,nextStartTimeRef:y,stopPlayback:X,setIsSpeaking:F,expressionUrl:L,onExpressionFinished:ie})=>{const{scene:b}=We.useGLTF(c),xe=We.useGLTF(Ct),K=t.useMemo(()=>xe.flatMap(f=>f.animations),[xe]),de=We.useGLTF(Dt),te=t.useMemo(()=>de.flatMap(f=>f.animations),[de]),H=t.useRef(null),M=t.useRef(null),g=t.useRef(null),V=t.useRef([]),[R]=t.useState(()=>new Ce.AnimationMixer(b)),x=t.useRef({}),_=t.useRef(null),Y=t.useRef(0),q=t.useRef(!1),oe=t.useRef(0),Q=t.useRef(null);t.useEffect(()=>{if(!(!K||!b)){if(K.forEach((f,l)=>{const i=`idle_${l}`;if(!x.current[i]){const r=R.clipAction(f,b);r.name=i,r.setLoop(Ce.LoopOnce,1),r.clampWhenFinished=!0,x.current[i]=r}}),te.forEach((f,l)=>{const i=`talk_${l}`;if(!x.current[i]){const r=R.clipAction(f,b);r.name=i,r.setLoop(Ce.LoopOnce,1),r.clampWhenFinished=!0,x.current[i]=r}}),K.length>0){const f=x.current.idle_0,l=_.current&&R.existingAction(_.current.getClip());f&&!l&&(f.reset().fadeIn(.5).play(),_.current=f)}return()=>{R.stopAllAction(),x.current={},_.current=null}}},[K,te,b,R]);const ve=t.useRef("");t.useEffect(()=>{if(!L||!b||L===ve.current)return;ve.current=L,Q.current=L,new At.GLTFLoader().load(L,l=>{if(l.animations&&l.animations.length>0){const i=l.animations[0],r=R.clipAction(i,b);if(r.name=`EXPR_${L}`,r.setLoop(Ce.LoopOnce,1),r.clampWhenFinished=!0,x.current[`EXPR_${L}`]=r,O&&Q.current===L){const p=_.current;r.reset().fadeIn(.3).play(),p&&p!==r&&p.crossFadeTo(r,.3,!0),_.current=r,Q.current=null}}},void 0,l=>{console.error(`[ANIMATION] Failed to load ${L}`,l)})},[L,b,R,O]),t.useEffect(()=>{const f=l=>{const i=l.action,r=i.name||"";if(r.startsWith("idle_")){const B=(parseInt(r.split("_")[1])+1)%K.length,G=x.current[`idle_${B}`];G&&(G.reset().fadeIn(.5).play(),i.crossFadeTo(G,.5,!0),_.current=G)}else if(r.startsWith("EXPR_")){if(O){const p=x.current.talk_0;p&&(p.reset().fadeIn(.5).play(),i.crossFadeTo(p,.5,!0),_.current=p)}else{const p=x.current.idle_0;p&&(p.reset().fadeIn(.5).play(),i.crossFadeTo(p,.5,!0),_.current=p)}ie&&ie()}else if(r.startsWith("talk_"))if(O){const B=(parseInt(r.split("_")[1])+1)%te.length,G=x.current[`talk_${B}`];G&&(G.reset().fadeIn(.3).play(),i.crossFadeTo(G,.3,!0),_.current=G)}else{const p=x.current.idle_0;p&&(p.reset().fadeIn(.5).play(),i.crossFadeTo(p,.5,!0),_.current=p)}};return R.addEventListener("finished",f),()=>R.removeEventListener("finished",f)},[R,K,te,O,ie]),t.useEffect(()=>{if(O&&b){const f=_.current,l=f?.name||"";if(l.startsWith("idle_")||l.startsWith("talk_")||l===""){const i=Q.current;if(i){const r=x.current[`EXPR_${i}`];if(r){r.reset().fadeIn(.3).play(),f&&f!==r&&f.crossFadeTo(r,.3,!0),_.current=r,Q.current=null;return}}if(l.startsWith("idle_")||l===""){const r=x.current.talk_0;r&&(r.reset().fadeIn(.5).play(),f&&f.crossFadeTo(r,.5,!0),_.current=r)}}}else if(!O&&b){const f=_.current,l=f?.name||"";if(l.startsWith("talk_")||l.startsWith("EXPR_")){const i=x.current.idle_0;i&&(i.reset().fadeIn(.5).play(),f&&f.crossFadeTo(i,.5,!0),_.current=i)}}},[O,b,L]),t.useEffect(()=>{if(!b)return;b.traverse(i=>{if(i.isMesh&&i.morphTargetDictionary){const r=i.name.toLowerCase();(r.includes("head")||r.includes("avatar"))&&(M.current=i,W(`[ANIMATION] Found head mesh: ${i.name}`)),r.includes("teeth")&&(g.current=i,W(`[ANIMATION] Found teeth mesh: ${i.name}`))}});const f=M.current?.morphTargetDictionary;f&&Object.keys(f).filter(i=>i.toLowerCase().includes("brow"));const l=[];b.traverse(i=>{if(i.isMesh){const p=i.morphTargetDictionary;p&&Object.keys(p).some(B=>B.toLowerCase().includes("brow"))&&l.push(i)}}),V.current=l,l.length>0&&W("[ANIMATION] Meshes with brow morphs:",l.length)},[b]);const Oe=(f,l=1)=>{const i=`viseme_${f}`.toLowerCase(),r=Pt.find(p=>p.key.toLowerCase()===i);if(r)for(const p in r.mix){const B=r.mix[p]*l;J(M.current,p,B),J(g.current,p,B)}},Te=f=>{const l=Pe[f]??Pe.neutral;for(const i in l)J(M.current,i,l[i]),J(g.current,i,l[i])};return Xe.useFrame((f,l)=>{const i=Math.pow(.88,60*l);ut(M.current,i),ut(g.current,i),Te(A);const r=f.clock.elapsedTime;let p=0;if(Math.floor(r)%5===0&&Math.floor((r-l)%5)!==0){let $=null;b.traverse(N=>{N.name.toLowerCase().includes("hips")&&($=N)});const ne=$?`Hips Y: ${$.position.y.toFixed(4)}`:"Hips not found";W(`[ANIMATION] Mixer Time: ${R.time.toFixed(2)}, ${ne}`)}if(R.update(l),r>Y.current&&!q.current&&(q.current=!0,oe.current=r),q.current){const ne=(r-oe.current)/.3;if(ne>=1)q.current=!1,Y.current=r+2+Math.random()*5;else{const N=ne<.5?ne*2:(1-ne)*2;J(M.current,"eyeBlinkLeft",N),J(M.current,"eyeBlinkRight",N),J(g.current,"eyeBlinkLeft",N),J(g.current,"eyeBlinkRight",N),p=N*Nt.browInnerUp}}const B=Pe[A]??Pe.neutral,G=B.browInnerUp??0,pe=B.browOuterUpLeft??0,fe=B.browOuterUpRight??0,ye=r*jt,he=Ye*Math.sin(ye),me=Ye*.7*Math.sin(ye+.7),Me=Ye*.7*Math.sin(ye+1.3),ge=$=>Math.max(0,Math.min(1,$)),Ie=ge(G+he),Re=ge(pe+me),Ve=ge(fe+Me),De=ge(Ie+p);if(J(M.current,"browInnerUp",De),J(g.current,"browInnerUp",De),J(M.current,"browOuterUpLeft",Re),J(g.current,"browOuterUpLeft",Re),J(M.current,"browOuterUpRight",Ve),J(g.current,"browOuterUpRight",Ve),H.current){const $=w.current?0:E.rotation[1];H.current.rotation.y=Ce.MathUtils.lerp(H.current.rotation.y,$,.1),H.current.position.set(...E.position),H.current.scale.setScalar(E.scale),H.current.rotation.x=E.rotation[0],H.current.rotation.z=E.rotation[2]}if(w.current&&h.current){const $=h.current.currentTime,be=($-C.current)*1e3- -150;for(let ke=0;ke<m.current.length;ke++){const _e=m.current[ke];be>=_e.vtime&&be<_e.vtime+_e.vduration&&Oe(_e.viseme,_e.weight??1)}$>y.current+.5&&(X(),F(!1))}}),o.jsx("group",{ref:H,children:o.jsx("primitive",{object:b})})});function Ht(c){return c?c.charAt(0).toUpperCase()+c.slice(1).toLowerCase():""}const qt=({token:c,agentToken:w,onNavigationRequested:m}={})=>{const h=c??w??"",C=It(h||void 0),[E,A]=t.useState(""),[le,ee]=t.useState(""),[O,y]=t.useState("Ready"),[X,F]=t.useState(!1),[L,ie]=t.useState(!1),[b,xe]=t.useState(()=>typeof window<"u"?window.matchMedia("(max-width: 480px)").matches:!1);t.useEffect(()=>{const e=window.matchMedia("(max-width: 480px)"),n=()=>xe(e.matches);return e.addEventListener("change",n),()=>e.removeEventListener("change",n)},[]);const K=b?80:600,de=t.useRef(!1),te=t.useRef([]),H=t.useRef(0),M=t.useRef(!1),g=t.useRef([]),V=t.useRef(null),R=t.useRef([]);t.useRef([]);const x=t.useRef([]),_=t.useRef(0),Y=t.useRef(0),q=t.useRef(0),oe=t.useRef(0),Q=t.useRef(!1),[ve,Oe]=t.useState(null),Te=t.useRef(null),[f,l]=t.useState("neutral"),[i,r]=t.useState(""),[p,B]=t.useState(""),[G,pe]=t.useState("Chat with us"),[fe,ye]=t.useState(null),[he,me]=t.useState("hidden"),[Me,ge]=t.useState(""),Ie=t.useRef(null),Re=t.useRef(he);Re.current=he;const[Ve,De]=t.useState(null),$=t.useRef(null),ne=t.useRef(null),[N,be]=t.useState("hidden"),[ke,_e]=t.useState(""),Be=t.useRef(N);Be.current=N;const ae=t.useRef(""),Ae=t.useRef(!1),Le=t.useRef(""),Ee=t.useMemo(()=>O==="Thinking..."||O==="Processing Voice..."?O:fe!=null&&fe!==""&&fe!=="none"&&fe!=="<none>"?`Enter ${Ht(fe)}`:null,[O,fe]),Fe=Ee!=null&&Ee!=="";t.useEffect(()=>{const e=Re.current;if(!(e==="exiting"||e==="entering")){if(e==="hidden"){Fe&&(ge(Ee??""),me("entering"));return}e==="visible"&&(!Fe||Ee!==Me)&&(Ie.current=Fe?Ee:null,me("exiting"))}},[Fe,Ee,he,Me]);const lt=t.useCallback(()=>{const e=Re.current;if(e==="exiting"){me("hidden");const n=Ie.current;Ie.current=null,n!=null&&n!==""&&(ge(n),me("entering"))}else e==="entering"&&me("visible")},[]),dt=e=>{if(!e)return;if(e.mood!=null){const s=String(e.mood).toLowerCase();l(s)}if(e.expression!=null){const s=String(e.expression).trim();r(s);const d=Ot.find(S=>S.name.toLowerCase()===s.toLowerCase());W(`[STREAM] Animation match for "${s}": ${d?d.name:"NONE"}`),B(d?.url??"")}if(e.navigation!=null){const s=String(e.navigation).trim();s!==""&&(m?m(s):window.open(s,"_blank"))}const n=e.ask_for||e.lead_capture?.ask_for,a=n?String(n).trim().toLowerCase():"",u=a==="none"||a==="<none>";if(n&&!u){const s=a;ye(s||null),pe(s==="email"?"Enter your email":s==="name"?"Enter your name":s==="phone"?"Enter your phone number":"Chat with us")}else(u||e.ask_for===null||e.lead_capture&&e.lead_capture.ask_for===null||e.ask_for==="none")&&(ye(null),G!=="Chat with us"&&pe("Chat with us"));e.collected,e.valid},ft=e=>{const n=e.trim();if(!n)return null;if(n.startsWith("{"))try{return JSON.parse(n)}catch{return null}if(n.includes(":")){const a=n.split(":"),u=a[0].trim().toLowerCase(),s=a.slice(1).join(":").trim();return{[u]:s}}return null},ht=t.useCallback(()=>{},[]),Ke=e=>{if(Ae.current)e==="§"?Ae.current=!1:(Le.current+=e,ee(n=>n+e));else if(e==="§"){Ae.current=!0;return}else for(ae.current+=e;ae.current.includes(`
|
|
2
|
+
`);){const n=ae.current.indexOf(`
|
|
3
|
+
`),a=ae.current.slice(0,n).trim();ae.current=ae.current.slice(n+1);const u=ft(a);u&&dt(u)}};t.useEffect(()=>{(async()=>{if(!sessionStorage.getItem("STREAMOJI_LEADS_SESSION_LEAD_ID")){const a="secret",u=Math.floor(Date.now()/1e3).toString();try{const s=new TextEncoder,d=await crypto.subtle.importKey("raw",s.encode(a),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),S=await crypto.subtle.sign("HMAC",d,s.encode(u)),j=Array.from(new Uint8Array(S)).map(v=>v.toString(16).padStart(2,"0")).join("");sessionStorage.setItem("STREAMOJI_LEADS_SESSION_LEAD_ID",j),W("[SESSION] New HMAC UID generated and saved:",j)}catch(s){console.error("[SESSION] HMAC generation failed:",s);const d=Math.random().toString(36)+Date.now().toString();sessionStorage.setItem("STREAMOJI_LEADS_SESSION_LEAD_ID",d)}}sessionStorage.getItem("STREAMOJI_LEADS_SESSION_MESSAGES")||sessionStorage.setItem("STREAMOJI_LEADS_SESSION_MESSAGES",JSON.stringify([]))})()},[]);const Qe=()=>{try{return JSON.parse(sessionStorage.getItem("STREAMOJI_LEADS_SESSION_MESSAGES")||"[]")}catch{return[]}},pt=e=>{sessionStorage.setItem("STREAMOJI_LEADS_SESSION_MESSAGES",JSON.stringify(e))},[$e,He]=t.useState(!1),[Ze,qe]=t.useState(0),re=t.useRef(null),Ue=t.useRef([]),et=t.useRef(0),[tt,mt]=t.useState(null),[gt,Je]=t.useState(null),ce=t.useRef(null),we=t.useRef(null),Ne=t.useCallback((e=!1)=>{e&&(Q.current=!0,F(!1),y("Ready")),g.current=[],te.current=[],M.current=!1,ie(!1),_.current=0,Y.current=0,q.current=0,R.current.forEach(n=>{try{n.stop()}catch{}}),$.current&&(clearTimeout($.current),$.current=null),De(null),B(""),R.current=[]},[]),bt=async()=>{try{const e=await navigator.mediaDevices.getUserMedia({audio:!0}),n=window.AudioContext||window.webkitAudioContext,a=new n,u=a.createMediaStreamSource(e),s=a.createAnalyser();s.fftSize=64,u.connect(s),ce.current=a,we.current=u,Je(s);const d=new MediaRecorder(e);re.current=d,Ue.current=[],d.ondataavailable=S=>{S.data.size>0&&Ue.current.push(S.data)},d.onstop=async()=>{const S=Date.now()-et.current;if(Je(null),we.current&&(we.current.disconnect(),we.current=null),ce.current&&ce.current.state!=="closed"&&(ce.current.close(),ce.current=null),S<1e3){y("Recording too short. Hold or click longer."),F(!1);return}const k=new Blob(Ue.current,{type:"audio/wav"});await xt(k)},et.current=Date.now(),d.start(100),He(!0),y("Listening...")}catch(e){console.error("Error accessing microphone:",e),y("Mic Access Error")}},_t=()=>{re.current&&re.current.state!=="inactive"&&(re.current.stop(),re.current.stream.getTracks().forEach(e=>e.stop()),He(!1))},wt=()=>{re.current&&re.current.state!=="inactive"&&(re.current.onstop=null,re.current.stop(),re.current.stream.getTracks().forEach(e=>e.stop()),Je(null),we.current&&(we.current.disconnect(),we.current=null),ce.current&&ce.current.state!=="closed"&&(ce.current.close(),ce.current=null),He(!1),Ue.current=[],y("Ready"))};t.useEffect(()=>{if(!L)return;const e=()=>{const n=q.current;if(n<=0)return;const a=V.current,u=_.current;if(!a)return;const s=a.currentTime-u,d=Math.min(Math.max(0,s),n),S=le.trim().length;if(S<=0)return;const k=Math.min(Math.round(d/n*S),S);Oe(k)};return clearInterval(Te.current??void 0),Te.current=setInterval(e,90),()=>clearInterval(Te.current??void 0)},[L,le,q.current]),t.useEffect(()=>{let e;return $e?(qe(0),e=window.setInterval(()=>{qe(n=>n+1)},1e3)):qe(0),()=>clearInterval(e)},[$e]);const St=e=>{const n=e.numberOfChannels,a=e.length*n*2+44,u=new ArrayBuffer(a),s=new DataView(u);let d=0;const S=D=>{s.setUint16(d,D,!0),d+=2},k=D=>{s.setUint32(d,D,!0),d+=4};k(1179011410),k(a-8),k(1163280727),k(544501094),k(16),S(1),S(n),k(e.sampleRate),k(e.sampleRate*2*n),S(n*2),S(16),k(1635017060),k(a-d-4);const j=[];for(let D=0;D<n;D++)j.push(e.getChannelData(D));let v=0;for(;d<a;){for(let D=0;D<n;D++){let U=Math.max(-1,Math.min(1,j[D][v]));U=U<0?U*32768:U*32767,s.setInt16(d,U,!0),d+=2}v++}return new Blob([u],{type:"audio/wav"})},Ge=async(e,n,a=!1)=>{if(!Q.current){if(de.current){te.current.push({audio:e,visemes:n,isNewSegment:a});return}de.current=!0;try{const u=window.AudioContext||window.webkitAudioContext,s=V.current??new u;s.state==="suspended"&&await s.resume(),V.current=s;const d=window.atob(e),S=new Uint8Array(d.length);for(let T=0;T<d.length;T++)S[T]=d.charCodeAt(T);const k=await s.decodeAudioData(S.buffer.slice(0));q.current+=k.duration;const j=s.currentTime;let v=Y.current;const D=!M.current;v<j&&(v=j+.1),Y.current=v+k.duration;const U=s.createBufferSource();U.buffer=k;let P=tt;if((!P||P.context!==s)&&(P=s.createAnalyser(),P.fftSize=64,P.connect(s.destination),mt(P)),U.connect(P),R.current.push(U),Q.current){R.current=R.current.filter(T=>T!==U);return}if(D){M.current=!0,ie(!0),W(`[AUDIO] setIsSpeaking(true) - First chunk starting at ${v.toFixed(3)}`),_.current=v;const T=(v-j)*1e3;H.current=performance.now()+T,W(`[AUDIO] Response started. Initial startTime: ${v.toFixed(3)}, CT: ${j.toFixed(3)}`)}U.start(v);const z=(v-_.current)*1e3;a&&(oe.current=z,W(`[AUDIO] New segment detected at +${z.toFixed(0)}ms. Resetting segment offset.`)),n.forEach((T,ue)=>{const I=T.symbol??"";if(I){const Z=Vt(I),se=Math.round(T.start*1e3),Se=Math.round((T.duration??0)*1e3),st=oe.current+se;ue<3&&W(`[AUDIO] Viseme "${I}": segment_relative=${se}ms, segment_offset=${oe.current.toFixed(0)}ms => vtime=${st}ms`),Z.forEach(it=>{g.current.push({viseme:it.v,weight:it.w,vtime:st,vduration:Se})})}}),y("Speaking...")}finally{if(de.current=!1,te.current.length>0){const u=te.current.shift();u&&Ge(u.audio,u.visemes,u.isNewSegment)}}}},xt=async e=>{try{F(!0),Le.current="",ee(""),y("Processing Voice...");const n=await e.arrayBuffer(),u=await new(window.AudioContext||window.webkitAudioContext)().decodeAudioData(n),s=St(u),d=new FileReader;d.readAsDataURL(s),d.onloadend=async()=>{const S=d.result.split(",")[1];Ne(),A(""),ae.current="",Ae.current=!1;const k=`${at}/stt?token=${encodeURIComponent(h)}`,j=await fetch(k,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({audio_base64:S,audio_format:"wav"})});if(!j.ok){const ue=await j.text();let I="STT Failed";try{I=JSON.parse(ue).error||I}catch{ue&&(I=ue.slice(0,200))}throw new Error(I)}const v=j.body;if(W("this is body"+v),!v){y("STT Failed"),F(!1);return}const D=v.getReader();Q.current=!1;const U=new TextDecoder;let P="",z=!1;const T=async(ue,I)=>{switch(ue){case"transcript":I.transcript!=null&&A(String(I.transcript));break;case"text":{const Z=I.delta??I.text??"";Z&&Ke(Z);break}case"audio":{const Z=I.chunk,se=I.visemes??[],Se=!!I.is_new_segment;Z&&await Ge(Z,se,Se);break}case"done":{z=!0,y("Ready"),F(!1);break}case"error":{z=!0,y("STT Failed"),F(!1);break}default:break}};for(;;){const{done:ue,value:I}=await D.read();I&&(P+=U.decode(I,{stream:!0}));const Z=P.split(`
|
|
4
|
+
|
|
5
|
+
`);P=Z.pop()??"";for(const se of Z){const Se=je(se);Se&&await T(Se.event,Se.data)}if(ue){if(P.trim()){const se=je(P.trim());se&&await T(se.event,se.data)}z||(y("Ready"),F(!1));break}}}}catch(n){console.error("Audio Submission Error:",n),y("STT Failed"),F(!1)}},vt=async e=>{e&&e.preventDefault(),Le.current="",ee(""),!(!E||X)&&await yt(E)},je=e=>{const n=e.split(/\r?\n/);let a="",u="";for(const d of n)d.startsWith("event:")?a=d.slice(6).trim():d.startsWith("data:")&&(u=d.slice(5).trim());if(!a)return null;let s={};if(u)try{s=JSON.parse(u)}catch{s={raw:u}}return{event:a,data:s}},nt=(e,n)=>{switch(e){case"connected":ae.current="",Ae.current=!1;break;case"text":{const a=n.delta??"";a&&Ke(a);break}case"audio":{const a=n.chunk,u=n.visemes??[];a&&Ge(a,u);break}case"done":{const a=Qe(),u=Le.current.trim(),s=[...a,{role:"user",content:E||"..."},{role:"assistant",content:u}];pt(s),x.current=[...g.current],y("Ready"),F(!1),A("");break}case"error":{const a=n.message??"Unknown error";Le.current=a,ee(a),y("Agent Failed"),F(!1);break}}},yt=async e=>{F(!0),y("Thinking..."),Le.current="",ae.current="",Ae.current=!1,Ne(),q.current=0,Oe(0);const n=`${at}/agent/chat?token=${encodeURIComponent(h)}`;try{const a=Qe();let u=sessionStorage.getItem("STREAMOJI_LEADS_SESSION_LEAD_ID");u||(Et("[CHAT] Session UID missing at send time! Generating emergency backup."),u="emergency-"+Math.random().toString(36).substring(7),sessionStorage.setItem("STREAMOJI_LEADS_SESSION_LEAD_ID",u));const s={history:a,question:e,lead_id:u};W("[CHAT] Sending payload:",s);const d=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s),cache:"default"});if(!d.ok)throw new Error("Agent request failed");const S=d.body;if(!S){y("Agent Failed"),F(!1);return}const k=S.getReader();Q.current=!1;const j=new TextDecoder;let v="";for(;;){const{done:D,value:U}=await k.read();W(`[SSE] Chunk received. done=${D}, length=${U?.length||0}`),U&&(v+=j.decode(U,{stream:!0}));const P=v.split(`
|
|
6
|
+
|
|
7
|
+
`);v=P.pop()??"";for(const z of P){W(`[SSE] Processing block: ${z.slice(0,50)}...`);const T=je(z);T&&(W(`[SSE] Event: ${T.event}`),nt(T.event,T.data))}if(D){if(W("[SSE] Stream finished"),v.trim()){const z=je(v.trim());z&&nt(z.event,z.data)}y("Ready"),F(!1),A("");break}}}catch(a){console.error("Chat Error:",a),y("Agent Failed"),F(!1)}},rt=le.trim(),ze=rt&&L?rt.slice(0,ve!=null&&ve>0?ve:0):"";t.useEffect(()=>{const e=Be.current;e!=="exiting"&&(ze?(_e(ze),e==="hidden"&&be("entering")):(e==="visible"||e==="entering")&&be("exiting"))},[ze,N]);const Rt=t.useCallback(()=>{const e=Be.current;e==="entering"?be("visible"):e==="exiting"&&be("hidden")},[]);return t.useLayoutEffect(()=>{const e=ne.current;e&&(e.scrollTop=e.scrollHeight)},[ke]),o.jsxs("div",{className:"avatar-widget-container",children:[o.jsxs("div",{className:"avatar-input-area",children:[he!=="hidden"?o.jsx("div",{className:`avatar-thinking-tab${he==="exiting"?" avatar-thinking-tab--exiting":he==="entering"?" avatar-thinking-tab--entering":""}`,onAnimationEnd:lt,children:Me}):null,o.jsx("div",{className:"avatar-input-container",children:o.jsx("div",{style:{display:"flex",alignItems:"center",width:"100%",height:"100%"},children:$e?o.jsxs("div",{className:"avatar-input-recording",children:[o.jsx("button",{type:"button",className:"avatar-recording-cancel",onClick:wt,title:"Cancel",children:o.jsxs("svg",{width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.5",strokeLinecap:"round",strokeLinejoin:"round",style:{display:"block"},children:[o.jsx("line",{x1:"18",y1:"6",x2:"6",y2:"18"}),o.jsx("line",{x1:"6",y1:"6",x2:"18",y2:"18"})]})}),o.jsxs("div",{style:{flex:1,height:"100%",position:"relative",display:"flex",alignItems:"center",minWidth:0},children:[o.jsx("div",{style:{flex:1,height:"100%"},children:o.jsx(ot,{analyser:gt})}),o.jsxs("span",{style:{fontSize:"0.75rem",color:"#64748b",fontWeight:500,marginLeft:"4px",minWidth:"32px",textAlign:"right",fontVariantNumeric:"tabular-nums"},children:[Math.floor(Ze/60),":",String(Ze%60).padStart(2,"0")]})]}),o.jsx("button",{type:"button",className:"avatar-recording-confirm",onClick:_t,title:"Send",children:o.jsx("svg",{width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.5",strokeLinecap:"round",strokeLinejoin:"round",style:{display:"block"},children:o.jsx("polyline",{points:"20 6 9 17 4 12"})})})]}):L?o.jsxs("div",{className:"avatar-input-speaking",children:[o.jsx("div",{style:{flex:1,height:"100%",display:"flex",alignItems:"center",paddingRight:"8px"},children:o.jsx(ot,{analyser:tt})}),o.jsx("button",{type:"button",className:"avatar-speaking-stop",onClick:()=>Ne(!0),title:"Stop",children:o.jsx("span",{className:"avatar-speaking-stop__icon","aria-hidden":!0})})]}):X?o.jsx("div",{style:{flex:1,height:"100%",display:"flex",alignItems:"center",justifyContent:"center"},children:o.jsx("div",{className:"avatar-input-loader"})}):o.jsxs("form",{onSubmit:vt,style:{flex:1,display:"flex",height:"100%",alignItems:"center"},children:[o.jsx("input",{id:"avatar-text-input",type:"text",value:E,onChange:e=>A(e.target.value),placeholder:"Ask me anything",disabled:X,autoComplete:"off",style:{width:"100%",height:"100%"}}),E.trim()===""?o.jsx("button",{type:"button",className:"mic-button",onClick:bt,disabled:X,style:{backgroundColor:"#1e4a5e"},children:o.jsxs("svg",{width:"28",height:"28",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",children:[o.jsx("path",{d:"M12 14C13.66 14 15 12.66 15 11V5C15 3.34 13.66 2 12 2C10.34 2 9 3.34 9 5V11C9 12.66 10.34 14 12 14Z",fill:"white"}),o.jsx("path",{d:"M17 11C17 13.76 14.76 16 12 16C9.24 16 7 13.76 7 11H5C5 14.53 7.61 17.43 11 17.93V21H13V17.93C16.39 17.43 19 14.53 19 11H17Z",fill:"white"})]})}):o.jsx("button",{type:"submit",className:"mic-button",disabled:X,style:{backgroundColor:"#1e4a5e"},title:"Send",children:o.jsx("img",{src:"/assets/leadmoji-logo-send.png",alt:"Send",width:24,height:24})})]})})})]}),o.jsx("div",{className:"avatar-wrapper",children:o.jsxs("div",{className:"avatar-scene-wrapper",children:[N!=="hidden"&&o.jsx("div",{className:`avatar-bubble${N==="entering"?" avatar-bubble--entering":N==="exiting"?" avatar-bubble--exiting":""}`,onAnimationEnd:Rt,children:o.jsx("div",{ref:ne,className:"avatar-bubble__content",children:ke})}),o.jsx("div",{className:"avatar-canvas-layer",style:{width:K,height:K},children:o.jsxs(Xe.Canvas,{shadows:!0,camera:{position:[.2,1.4,3],fov:42},gl:{alpha:!0},dpr:1.8,style:{pointerEvents:"none",width:"100%",height:"100%"},children:[o.jsx(Bt,{target:Ut}),o.jsx("ambientLight",{intensity:.7}),o.jsx("directionalLight",{position:[0,2,2],intensity:1}),o.jsx(We.Environment,{preset:"city"}),o.jsx(t.Suspense,{fallback:null,children:C!==null&&o.jsx($t,{avatarUrl:C,isPlayingRef:M,visemeQueueRef:g,audioContextRef:V,responseAudioStartTimeRef:_,adjustments:Ft,mood:f,expression:i,agentResponse:le,isSpeaking:L,nextStartTimeRef:Y,stopPlayback:Ne,setIsSpeaking:ie,expressionUrl:p,onExpressionFinished:ht})})]})})]})})]})},Jt=({token:c,onNavigationRequested:w})=>o.jsx(qt,{token:c,onNavigationRequested:w});exports.AvatarWidget=Jt;
|
|
8
|
+
//# sourceMappingURL=avatar-widget.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"avatar-widget.cjs","sources":["../../../src/components/AvatarWidget.tsx","../src/AvatarWidget.tsx"],"sourcesContent":["import { Environment, useGLTF } from \"@react-three/drei\";\nimport { Canvas, useFrame, useThree } from \"@react-three/fiber\";\nimport {\n memo,\n Suspense,\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport * as THREE from \"three\";\nimport { GLTFLoader } from \"three/examples/jsm/loaders/GLTFLoader.js\";\n// import \"./AvatarWidget.css\"; // REMOVE THIS LINE WHEN USING THE PACKAGE!\n\nconst isDev = import.meta.env.DEV;\nconst devLog = (...args: unknown[]) => {\n if (isDev) console.log(...args);\n};\nconst devWarn = (...args: unknown[]) => {\n if (isDev) console.warn(...args);\n};\n\nconst VoiceVisualizer = ({ analyser }: { analyser: AnalyserNode | null }) => {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const prevDataRef = useRef<Float32Array | null>(null);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n const ctx = canvas.getContext(\"2d\", { alpha: true });\n if (!ctx) return;\n\n let animationId: number;\n let dataArray: Uint8Array<ArrayBuffer> | null = null;\n\n if (analyser) {\n analyser.fftSize = 128;\n dataArray = new Uint8Array(analyser.frequencyBinCount);\n }\n\n const draw = () => {\n animationId = requestAnimationFrame(draw);\n\n if (\n canvas.width !== canvas.offsetWidth ||\n canvas.height !== canvas.offsetHeight\n ) {\n canvas.width = canvas.offsetWidth;\n canvas.height = canvas.offsetHeight;\n }\n\n const w = canvas.width;\n const h = canvas.height;\n if (w === 0 || h === 0) return;\n\n const centerY = h / 2;\n ctx.clearRect(0, 0, w, h);\n\n ctx.fillStyle = \"#1e293b\"; // slate-800\n\n // Config for \"continuous\" look\n const barWidth = 2;\n const gap = 2;\n const totalBarSpace = barWidth + gap;\n // Use full width or nearly full width\n const availableWidth = w * 0.95;\n const maxBars = Math.floor(availableWidth / totalBarSpace);\n\n if (!prevDataRef.current || prevDataRef.current.length !== maxBars) {\n prevDataRef.current = new Float32Array(maxBars).fill(2); // Start with min height\n }\n\n const visualWidth = maxBars * totalBarSpace;\n const startX = (w - visualWidth) / 2;\n\n // Prepare data\n if (analyser && dataArray) {\n analyser.getByteFrequencyData(dataArray);\n }\n\n const bufferLength = dataArray ? dataArray.length : 0;\n const relevantBins = Math.floor(bufferLength * 0.7);\n const step = relevantBins / maxBars;\n\n // First pass: compute raw target height for each bar from frequency data\n const targetHeights = new Float32Array(maxBars);\n for (let i = 0; i < maxBars; i++) {\n let maxVal = 0;\n if (dataArray && step > 0) {\n const chunkStart = Math.floor(i * step);\n const chunkEnd = Math.floor((i + 1) * step);\n for (let j = chunkStart; j < chunkEnd; j++) {\n const val = dataArray[j] || 0;\n if (val > maxVal) maxVal = val;\n }\n }\n\n if (maxVal < 10) maxVal = 0;\n const percent = maxVal / 255;\n\n // Min height 2px creates the \"dotted line\" effect\n const rawHeight =\n maxVal > 0 ? Math.max(2, Math.pow(percent, 1.4) * h * 0.25) : 2;\n targetHeights[i] = rawHeight;\n }\n\n // Second pass: mirror so vibration is centered (symmetric around center)\n for (let i = 0; i < maxBars; i++) {\n const mirrorI = maxBars - 1 - i;\n const targetHeight = Math.max(targetHeights[i], targetHeights[mirrorI]);\n\n // Smooth transition\n const currentHeight =\n prevDataRef.current[i] +\n (targetHeight - prevDataRef.current[i]) * 0.3;\n prevDataRef.current[i] = currentHeight;\n\n const x = startX + i * totalBarSpace;\n const y = centerY - currentHeight / 2;\n\n ctx.beginPath();\n // Small radius for small bars\n if (ctx.roundRect) {\n ctx.roundRect(x, y, barWidth, currentHeight, 4);\n } else {\n ctx.fillRect(x, y, barWidth, currentHeight);\n }\n ctx.fill();\n }\n };\n\n draw();\n\n return () => {\n cancelAnimationFrame(animationId);\n };\n }, [analyser]);\n\n return (\n <canvas\n ref={canvasRef}\n style={{ width: \"100%\", height: \"100%\", display: \"block\" }}\n />\n );\n};\n\nconst API_BASE = \"https://ai.streamoji.com\";\n// const API_BASE = \"http://localhost:4000\";\nconst R2_AVATAR_BASE = \"https://pub-48df6f7d60d6440bbd01676ea5d90e55.r2.dev\";\nconst DEFAULT_AVATAR_URL =\n \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/default-models/fullbodyavatarmale.glb\";\n\nasync function sha256Hex(message: string): Promise<string> {\n const buf = await crypto.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(message)\n );\n return Array.from(new Uint8Array(buf))\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n\n/** Resolves avatar URL from token hash. Returns null if token is missing or URL is unavailable. */\nfunction useResolvedAvatarUrl(token?: string): string | null {\n const [url, setUrl] = useState<string | null>(null);\n\n useEffect(() => {\n if (!token) {\n setUrl(null);\n return;\n }\n let cancelled = false;\n sha256Hex(token).then((hash) => {\n if (cancelled) return;\n const r2Url = `${R2_AVATAR_BASE}/${hash}.glb`;\n fetch(r2Url, { method: \"HEAD\" })\n .then((res) => {\n if (cancelled) return;\n setUrl(res.ok ? r2Url : DEFAULT_AVATAR_URL);\n })\n .catch(() => {\n if (!cancelled) setUrl(DEFAULT_AVATAR_URL);\n });\n });\n return () => {\n cancelled = true;\n };\n }, [token]);\n\n return url;\n}\n\nconst IDLE_ANIMATION_URLS = [\n \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/idle/M_Standing_Idle_Variations_001.glb\",\n \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/idle/M_Standing_Idle_Variations_002.glb\",\n \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/idle/M_Standing_Idle_002.glb\",\n \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/idle/M_Standing_Idle_Variations_005.glb\",\n];\n\nconst animations = [\n {\n id: \"m_expr_01\",\n name: \"Friendly Wave\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_001.glb\",\n },\n {\n id: \"m_expr_02\",\n name: \"You There\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_002.glb\",\n },\n {\n id: \"m_expr_04\",\n name: \"Awkward Agreement\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_004.glb\",\n },\n {\n id: \"m_expr_05\",\n name: \"What's Going On?\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_005.glb\",\n },\n {\n id: \"m_expr_06\",\n name: \"Tired Stretch\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_006.glb\",\n },\n {\n id: \"m_expr_07\",\n name: \"Conceilied Laughter\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_007.glb\",\n },\n {\n id: \"m_expr_08\",\n name: \"You Come Here\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_008.glb\",\n },\n {\n id: \"m_expr_09\",\n name: \"Come Here Kid\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_009.glb\",\n },\n {\n id: \"m_expr_10\",\n name: \"Come Here Everyone\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_010.glb\",\n },\n {\n id: \"m_expr_11\",\n name: \"No Freaking Way\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_011.glb\",\n },\n {\n id: \"m_expr_12\",\n name: \"Cheerful Approval\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_012.glb\",\n },\n {\n id: \"m_expr_13\",\n name: \"Waving Hello\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_013.glb\",\n },\n {\n id: \"m_expr_14\",\n name: \"Checking Surroundings\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_014.glb\",\n },\n {\n id: \"m_expr_15\",\n name: \"Referee Warning\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_015.glb\",\n },\n {\n id: \"m_expr_16\",\n name: \"You Thumbs Down\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_016.glb\",\n },\n {\n id: \"m_expr_17\",\n name: \"Side Thumbs Down\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_017.glb\",\n },\n {\n id: \"m_expr_18\",\n name: \"You're Finished\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Standing_Expressions_018.glb\",\n },\n {\n id: \"m_talk_01\",\n name: \"Oh God, Why Me?\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Talking_Variations_001.glb\",\n },\n {\n id: \"m_talk_02\",\n name: \"What Are You Doing?\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Talking_Variations_002.glb\",\n },\n {\n id: \"m_talk_03\",\n name: \"What Am I doing?\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Talking_Variations_003.glb\",\n },\n {\n id: \"m_talk_04\",\n name: \"No Way\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Talking_Variations_004.glb\",\n },\n {\n id: \"m_talk_05\",\n name: \"What's Going On?\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Talking_Variations_005.glb\",\n },\n {\n id: \"m_talk_06\",\n name: \"I have no idea\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Talking_Variations_006.glb\",\n },\n {\n id: \"m_talk_07\",\n name: \"What's going on here?\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Talking_Variations_007.glb\",\n },\n {\n id: \"m_talk_08\",\n name: \"Let's stop\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Talking_Variations_008.glb\",\n },\n {\n id: \"m_talk_09\",\n name: \"Fed Up Moment\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Talking_Variations_009.glb\",\n },\n {\n id: \"m_talk_10\",\n name: \"What's This? Hold On\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Talking_Variations_010.glb\",\n },\n {\n id: \"f_talk_01\",\n name: \"Great Job Clap\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/F_Talking_Variations_001.glb\",\n },\n {\n id: \"f_talk_02\",\n name: \"Chill Stretch\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/F_Talking_Variations_002.glb\",\n },\n {\n id: \"f_talk_03\",\n name: \"This Is Me\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/F_Talking_Variations_003.glb\",\n },\n {\n id: \"f_talk_04\",\n name: \"Empathize\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/F_Talking_Variations_004.glb\",\n },\n {\n id: \"f_talk_05\",\n name: \"Loose Hands Stretch\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/F_Talking_Variations_005.glb\",\n },\n {\n id: \"f_talk_06\",\n name: \"Take It Easy\",\n url: \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/F_Talking_Variations_006.glb\",\n },\n];\n// const ANIMATION_URL = \"/animation_talking.glb\"\n\nconst TALKING_ANIMATIONS = [\n \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Talking_Variations_005.glb\",\n \"https://pub-be53cae7bd99457a8c1f11b4d38f1672.r2.dev/masculine/expression/M_Talking_Variations_007.glb\",\n];\n\ninterface Adjustments {\n zoom: number;\n position: [number, number, number];\n scale: number;\n rotation: [number, number, number];\n}\n\nconst ADJUSTMENTS: Adjustments = {\n zoom: 0.85,\n position: [0.15, -0.8, 0],\n scale: 1.5,\n rotation: [0.15, 0.02, 0],\n};\n\n/** Fixed camera lookAt target (avatar upper body). */\nconst CAMERA_LOOKAT: [number, number, number] = [-0.45, 1.9, 0.1];\n\n// const HELLO_BLENDSHAPES = {\n// \"eyeWideLeft\" : \"0.5\",\n// \"eyeWideRight\" : \"0.5\",\n// \"mouthLowerDownLeft\" : \"0.5\",\n// \"mouthLowerDownRight\" : \"0.5\",\n// \"mouthDimpleLeft\" : \"0.5\",\n// \"mouthDimpleRight\" : \"0.5\",\n// \"mouthUpperUpLeft\" : \"1\",\n// \"mouthUpperUpRight\" : \"1\",\n// \"mouthSmileLeft\":\"0.77\",\n// \"mouthSmileRight\" : \"0.77\"\n// }\n\nconst blinking_blendshapes = {\n eyeBlinkLeft: 1,\n eyeBlinkRight: 1,\n browInnerUp: 0.2,\n};\n\nconst BROW_IDLE_AMPLITUDE = 0.18;\nconst BROW_IDLE_FREQUENCY = 1.0;\n\nconst EMOTION_PRESETS: Record<string, Record<string, number>> = {\n neutral: { eyeLookDownLeft: 0.1, eyeLookDownRight: 0.1 },\n happy: {\n mouthSmileLeft: 0.2,\n mouthSmileRight: 0.2,\n eyeLookDownLeft: 0.1,\n eyeLookDownRight: 0.1,\n },\n sad: {\n eyeLookDownLeft: 0.2,\n eyeLookDownRight: 0.2,\n browDownRight: 0.1,\n browInnerUp: 0.6,\n browOuterUpRight: 0.2,\n eyeSquintLeft: 0.7,\n eyeSquintRight: 0.7,\n mouthFrownLeft: 0.8,\n mouthFrownRight: 0.8,\n mouthLeft: 0.2,\n mouthPucker: 0.5,\n mouthRollLower: 0.2,\n mouthRollUpper: 0.2,\n mouthShrugLower: 0.2,\n mouthShrugUpper: 0.2,\n mouthStretchLeft: 0.4,\n },\n angry: {\n eyeLookDownLeft: 0.1,\n eyeLookDownRight: 0.1,\n browDownLeft: 0.6,\n browDownRight: 0.6,\n jawForward: 0.3,\n mouthFrownLeft: 0.7,\n mouthFrownRight: 0.7,\n mouthRollLower: 0.2,\n mouthShrugLower: 0.3,\n },\n fear: {\n browInnerUp: 0.7,\n eyeSquintLeft: 0.5,\n eyeSquintRight: 0.5,\n eyeWideLeft: 0.6,\n eyeWideRight: 0.6,\n mouthClose: 0.1,\n mouthFunnel: 0.3,\n mouthShrugLower: 0.5,\n mouthShrugUpper: 0.5,\n },\n disgust: {\n browDownLeft: 0.7,\n browDownRight: 0.1,\n browInnerUp: 0.3,\n eyeSquintLeft: 1,\n eyeSquintRight: 1,\n eyeWideLeft: 0.5,\n eyeWideRight: 0.5,\n mouthLeft: 0.4,\n mouthPressLeft: 0.3,\n mouthRollLower: 0.3,\n mouthShrugLower: 0.3,\n mouthShrugUpper: 0.8,\n mouthUpperUpLeft: 0.3,\n noseSneerLeft: 1,\n noseSneerRight: 0.7,\n },\n love: {\n browInnerUp: 0.4,\n browOuterUpLeft: 0.2,\n browOuterUpRight: 0.2,\n mouthSmileLeft: 0.2,\n mouthSmileRight: 0.2,\n eyeBlinkLeft: 0.6,\n eyeBlinkRight: 0.6,\n eyeWideLeft: 0.7,\n eyeWideRight: 0.7,\n mouthDimpleLeft: 0.1,\n mouthDimpleRight: 0.1,\n mouthPressLeft: 0.2,\n mouthShrugUpper: 0.2,\n mouthUpperUpLeft: 0.1,\n mouthUpperUpRight: 0.1,\n },\n};\n\ninterface VisemeMix {\n key: string;\n mix: Record<string, number>;\n}\n\nconst mtVisemeMixes: VisemeMix[] = [\n { key: \"viseme_aa\", mix: { jawOpen: 0.6 } },\n {\n key: \"viseme_E\",\n mix: {\n mouthPressLeft: 0.8,\n mouthPressRight: 0.8,\n mouthDimpleLeft: 1.0,\n mouthDimpleRight: 1.0,\n jawOpen: 0.3,\n },\n },\n {\n key: \"viseme_I\",\n mix: {\n mouthPressLeft: 0.6,\n mouthPressRight: 0.6,\n mouthDimpleLeft: 0.6,\n mouthDimpleRight: 0.6,\n jawOpen: 0.2,\n },\n },\n { key: \"viseme_O\", mix: { mouthPucker: 1.0, jawForward: 0.6, jawOpen: 0.2 } },\n { key: \"viseme_U\", mix: { mouthFunnel: 1.0 } },\n {\n key: \"viseme_PP\",\n mix: {\n mouthRollLower: 0.3,\n mouthRollUpper: 0.3,\n mouthUpperUpLeft: 0.3,\n mouthUpperUpRight: 0.3,\n },\n },\n {\n key: \"viseme_FF\",\n mix: {\n mouthPucker: 1.0,\n mouthShrugUpper: 1.0,\n mouthLowerDownLeft: 0.2,\n mouthLowerDownRight: 0.2,\n mouthDimpleLeft: 1.0,\n mouthDimpleRight: 1.0,\n mouthRollLower: 0.3,\n },\n },\n {\n key: \"viseme_DD\",\n mix: {\n mouthPressLeft: 0.8,\n mouthPressRight: 0.8,\n mouthFunnel: 0.5,\n jawOpen: 0.2,\n },\n },\n {\n key: \"viseme_SS\",\n mix: {\n mouthPressLeft: 0.8,\n mouthPressRight: 0.8,\n mouthLowerDownLeft: 0.5,\n mouthLowerDownRight: 0.5,\n jawOpen: 0.1,\n },\n },\n {\n key: \"viseme_TH\",\n mix: { mouthRollUpper: 0.3, jawOpen: 0.2, tongueOut: 0.4 },\n },\n { key: \"viseme_CH\", mix: { mouthPucker: 0.5, jawOpen: 0.2 } },\n { key: \"viseme_RR\", mix: { mouthPucker: 0.5, jawOpen: 0.2 } },\n {\n key: \"viseme_kk\",\n mix: {\n mouthLowerDownLeft: 0.4,\n mouthLowerDownRight: 0.4,\n mouthDimpleLeft: 0.3,\n mouthDimpleRight: 0.3,\n mouthFunnel: 0.3,\n mouthPucker: 0.3,\n jawOpen: 0.15,\n },\n },\n {\n key: \"viseme_nn\",\n mix: {\n mouthLowerDownLeft: 0.4,\n mouthLowerDownRight: 0.4,\n mouthDimpleLeft: 0.3,\n mouthDimpleRight: 0.3,\n mouthFunnel: 0.3,\n mouthPucker: 0.3,\n jawOpen: 0.15,\n tongueOut: 0.2,\n },\n },\n { key: \"viseme_sil\", mix: {} },\n];\n\nconst INWORLD_TO_OCULUS: Record<string, Array<{ v: string; w: number }>> = {\n aei: [\n { v: \"E\", w: 0.8 },\n { v: \"I\", w: 0.2 },\n ],\n ee: [{ v: \"I\", w: 1.0 }],\n oo: [{ v: \"O\", w: 1.0 }],\n u: [{ v: \"U\", w: 1.0 }],\n aa: [{ v: \"aa\", w: 1.0 }],\n ah: [\n { v: \"aa\", w: 0.7 },\n { v: \"O\", w: 0.3 },\n ],\n bmp: [{ v: \"PP\", w: 1.0 }],\n fv: [{ v: \"FF\", w: 1.0 }],\n th: [{ v: \"TH\", w: 1.0 }],\n l: [{ v: \"nn\", w: 1.0 }],\n r: [{ v: \"RR\", w: 1.0 }],\n qw: [\n { v: \"U\", w: 0.6 },\n { v: \"O\", w: 0.4 },\n ],\n chjsh: [{ v: \"CH\", w: 1.0 }],\n cdgknstxyz: [\n { v: \"DD\", w: 0.6 },\n { v: \"SS\", w: 0.4 },\n ],\n sil: [{ v: \"sil\", w: 1.0 }],\n};\n\nfunction convertInworldToOculus(\n inworldVisemeSymbol: string | null | undefined\n): Array<{ v: string; w: number }> {\n if (!inworldVisemeSymbol) return [{ v: \"sil\", w: 1.0 }];\n const symbol = inworldVisemeSymbol.toLowerCase();\n return INWORLD_TO_OCULUS[symbol] ?? [{ v: \"sil\", w: 1.0 }];\n}\n\ninterface VisemeQueueItem {\n viseme: string;\n weight: number;\n vtime: number;\n vduration: number;\n}\n\ninterface AvatarModelProps {\n avatarUrl: string;\n isPlayingRef: React.MutableRefObject<boolean>;\n visemeQueueRef: React.MutableRefObject<VisemeQueueItem[]>;\n audioContextRef: React.MutableRefObject<AudioContext | null>;\n responseAudioStartTimeRef: React.MutableRefObject<number>;\n adjustments: Adjustments;\n mood: string;\n expression: string;\n agentResponse: string;\n isSpeaking: boolean;\n nextStartTimeRef: React.MutableRefObject<number>;\n stopPlayback: () => void;\n setIsSpeaking: (v: boolean) => void;\n expressionUrl: string;\n onExpressionFinished: () => void;\n}\n\n/** Sets camera lookAt once; use inside Canvas. */\nfunction CameraLookAt({ target }: { target: [number, number, number] }) {\n const { camera } = useThree();\n useEffect(() => {\n camera.lookAt(...target);\n }, [camera, target]);\n return null;\n}\n\nfunction setMorphValue(\n mesh: THREE.Mesh | null,\n name: string,\n value: number\n): void {\n if (\n !mesh ||\n !(mesh as THREE.Mesh & { morphTargetDictionary?: Record<string, number> })\n .morphTargetDictionary\n )\n return;\n const m = mesh as THREE.Mesh & {\n morphTargetDictionary: Record<string, number>;\n morphTargetInfluences: number[];\n };\n const dict = m.morphTargetDictionary;\n const infl = m.morphTargetInfluences;\n if (!infl) return;\n for (const key in dict) {\n if (key.toLowerCase() === name.toLowerCase()) {\n infl[dict[key]] = value;\n }\n }\n}\n\nfunction decayVisemes(mesh: THREE.Mesh | null, factor = 0.97): void {\n if (!mesh) return;\n const m = mesh as THREE.Mesh & { morphTargetInfluences?: number[] };\n if (!m.morphTargetInfluences) return;\n for (let i = 0; i < m.morphTargetInfluences.length; i++) {\n m.morphTargetInfluences[i] *= factor;\n }\n}\n\n/** Set morph influence by matching key: key must include all of `patterns` (case-insensitive). */\nfunction setMorphValueByMatch(\n mesh: THREE.Mesh | null,\n patterns: string[],\n value: number\n): void {\n if (\n !mesh ||\n !(mesh as THREE.Mesh & { morphTargetDictionary?: Record<string, number> })\n .morphTargetDictionary\n )\n return;\n const m = mesh as THREE.Mesh & {\n morphTargetDictionary: Record<string, number>;\n morphTargetInfluences: number[];\n };\n const dict = m.morphTargetDictionary;\n const infl = m.morphTargetInfluences;\n if (!infl) return;\n const lower = patterns.map((p) => p.toLowerCase());\n for (const key in dict) {\n const k = key.toLowerCase();\n if (lower.every((p) => k.includes(p))) {\n infl[dict[key]] = value;\n return;\n }\n }\n}\n\nconst AvatarModel = memo(\n ({\n avatarUrl,\n isPlayingRef,\n visemeQueueRef,\n audioContextRef,\n responseAudioStartTimeRef,\n adjustments,\n mood,\n expression,\n agentResponse,\n isSpeaking,\n nextStartTimeRef,\n stopPlayback,\n setIsSpeaking,\n expressionUrl,\n onExpressionFinished,\n }: AvatarModelProps): React.ReactElement => {\n const { scene: avatarScene } = useGLTF(avatarUrl);\n const idleGLTFs = useGLTF(IDLE_ANIMATION_URLS) as any[];\n const idleAnimations = useMemo(\n () => idleGLTFs.flatMap((g) => g.animations),\n [idleGLTFs]\n );\n\n const talkGLTFs = useGLTF(TALKING_ANIMATIONS) as any[];\n const talkAnimations = useMemo(\n () => talkGLTFs.flatMap((g) => g.animations),\n [talkGLTFs]\n );\n\n const groupRef = useRef<THREE.Group>(null);\n const headMeshRef = useRef<THREE.Mesh | null>(null);\n const teethMeshRef = useRef<THREE.Mesh | null>(null);\n const browMeshesRef = useRef<THREE.Mesh[]>([]);\n\n const [mixer] = useState(() => new THREE.AnimationMixer(avatarScene));\n const actionsRef = useRef<Record<string, THREE.AnimationAction>>({});\n const currentActionRef = useRef<THREE.AnimationAction | null>(null);\n\n const nextBlinkRef = useRef(0);\n const blinkActiveRef = useRef(false);\n const blinkStartRef = useRef(0);\n const pendingExpressionRef = useRef<string | null>(null);\n\n // Load and cache idle animations\n useEffect(() => {\n if (!idleAnimations || !avatarScene) return;\n\n idleAnimations.forEach((a, i) => {\n const name = `idle_${i}`;\n if (!actionsRef.current[name]) {\n const action = mixer.clipAction(a, avatarScene);\n (action as any).name = name;\n action.setLoop(THREE.LoopOnce, 1);\n action.clampWhenFinished = true;\n actionsRef.current[name] = action;\n }\n });\n\n talkAnimations.forEach((a, i) => {\n const name = `talk_${i}`;\n if (!actionsRef.current[name]) {\n const action = mixer.clipAction(a, avatarScene);\n (action as any).name = name;\n action.setLoop(THREE.LoopOnce, 1);\n action.clampWhenFinished = true;\n actionsRef.current[name] = action;\n }\n });\n\n // Start with the first idle if nothing is playing\n if (idleAnimations.length > 0) {\n const firstIdle = actionsRef.current[\"idle_0\"];\n const isAnythingPlaying =\n currentActionRef.current &&\n mixer.existingAction(currentActionRef.current.getClip());\n\n if (firstIdle && !isAnythingPlaying) {\n devLog(\"[ANIMATION] Starting initial idle loop with idle_0\");\n firstIdle.reset().fadeIn(0.5).play();\n currentActionRef.current = firstIdle;\n }\n }\n\n return () => {\n devLog(\"[ANIMATION] Cleaning up mixer actions\");\n mixer.stopAllAction();\n actionsRef.current = {};\n currentActionRef.current = null;\n };\n }, [idleAnimations, talkAnimations, avatarScene, mixer]);\n\n const lastUrlRef = useRef(\"\");\n\n useEffect(() => {\n if (!expressionUrl || !avatarScene) return;\n if (expressionUrl === lastUrlRef.current) return;\n lastUrlRef.current = expressionUrl;\n pendingExpressionRef.current = expressionUrl;\n\n const loader = new GLTFLoader();\n devLog(`[ANIMATION] Pre-loading expression: ${expressionUrl}`);\n\n loader.load(\n expressionUrl,\n (gltf: any) => {\n if (gltf.animations && gltf.animations.length > 0) {\n const clip = gltf.animations[0];\n const action = mixer.clipAction(clip, avatarScene);\n (action as any).name = `EXPR_${expressionUrl}`; // Unique name\n action.setLoop(THREE.LoopOnce, 1);\n action.clampWhenFinished = true;\n actionsRef.current[`EXPR_${expressionUrl}`] = action;\n devLog(`[ANIMATION] Expression loaded and ready: ${expressionUrl}`);\n\n // Trigger IMMEDIATELY if we are already speaking\n if (isSpeaking && pendingExpressionRef.current === expressionUrl) {\n devLog(\n `[ANIMATION] Triggering late-loaded expression: ${expressionUrl}`\n );\n const current = currentActionRef.current;\n action.reset().fadeIn(0.3).play();\n if (current && current !== action)\n current.crossFadeTo(action, 0.3, true);\n currentActionRef.current = action;\n pendingExpressionRef.current = null;\n }\n }\n },\n undefined,\n (err: any) => {\n console.error(`[ANIMATION] Failed to load ${expressionUrl}`, err);\n }\n );\n }, [expressionUrl, avatarScene, mixer, isSpeaking]);\n\n // Global transition handler for finished animations\n useEffect(() => {\n const onActionFinished = (e: any) => {\n const action = e.action;\n const finishedName = (action as any).name || \"\";\n\n if (finishedName.startsWith(\"idle_\")) {\n const currentIndex = parseInt(finishedName.split(\"_\")[1]);\n const nextIndex = (currentIndex + 1) % idleAnimations.length;\n const nextAction = actionsRef.current[`idle_${nextIndex}`];\n if (nextAction) {\n nextAction.reset().fadeIn(0.5).play();\n action.crossFadeTo(nextAction, 0.5, true);\n currentActionRef.current = nextAction;\n }\n } else if (finishedName.startsWith(\"EXPR_\")) {\n devLog(\n `[ANIMATION] Expression finished: ${finishedName}. isSpeaking: ${isSpeaking}`\n );\n if (isSpeaking) {\n const talk0 = actionsRef.current[\"talk_0\"];\n if (talk0) {\n talk0.reset().fadeIn(0.5).play();\n action.crossFadeTo(talk0, 0.5, true);\n currentActionRef.current = talk0;\n }\n } else {\n const idle0 = actionsRef.current[\"idle_0\"];\n if (idle0) {\n idle0.reset().fadeIn(0.5).play();\n action.crossFadeTo(idle0, 0.5, true);\n currentActionRef.current = idle0;\n }\n }\n if (onExpressionFinished) onExpressionFinished();\n } else if (finishedName.startsWith(\"talk_\")) {\n if (isSpeaking) {\n const currentIndex = parseInt(finishedName.split(\"_\")[1]);\n const nextIndex = (currentIndex + 1) % talkAnimations.length;\n const nextAction = actionsRef.current[`talk_${nextIndex}`];\n if (nextAction) {\n nextAction.reset().fadeIn(0.3).play();\n action.crossFadeTo(nextAction, 0.3, true);\n currentActionRef.current = nextAction;\n }\n } else {\n const idle0 = actionsRef.current[\"idle_0\"];\n if (idle0) {\n idle0.reset().fadeIn(0.5).play();\n action.crossFadeTo(idle0, 0.5, true);\n currentActionRef.current = idle0;\n }\n }\n }\n };\n\n mixer.addEventListener(\"finished\", onActionFinished);\n return () => mixer.removeEventListener(\"finished\", onActionFinished);\n }, [\n mixer,\n idleAnimations,\n talkAnimations,\n isSpeaking,\n onExpressionFinished,\n ]);\n\n // Handle transition to talking/expression if isSpeaking becomes true or expressionUrl arrives late\n useEffect(() => {\n if (isSpeaking && avatarScene) {\n const current = currentActionRef.current;\n const currentName = (current as any)?.name || \"\";\n\n // Only transition if we are currently in idle or generic talk loop\n if (\n currentName.startsWith(\"idle_\") ||\n currentName.startsWith(\"talk_\") ||\n currentName === \"\"\n ) {\n const pendingUrl = pendingExpressionRef.current;\n if (pendingUrl) {\n const exprAction = actionsRef.current[`EXPR_${pendingUrl}`];\n if (exprAction) {\n devLog(`[ANIMATION] Playing SYNCED expression: ${pendingUrl}`);\n exprAction.reset().fadeIn(0.3).play();\n if (current && current !== exprAction)\n current.crossFadeTo(exprAction, 0.3, true);\n currentActionRef.current = exprAction;\n pendingExpressionRef.current = null; // Clear it\n return;\n }\n }\n\n // If no expression yet, but we just started speaking, fallback to talking loop\n if (currentName.startsWith(\"idle_\") || currentName === \"\") {\n const talk0 = actionsRef.current[\"talk_0\"];\n if (talk0) {\n devLog(\"[ANIMATION] Starting direct talk loop\");\n talk0.reset().fadeIn(0.5).play();\n if (current) current.crossFadeTo(talk0, 0.5, true);\n currentActionRef.current = talk0;\n }\n }\n }\n } else if (!isSpeaking && avatarScene) {\n const current = currentActionRef.current;\n const currentName = (current as any)?.name || \"\";\n if (\n currentName.startsWith(\"talk_\") ||\n currentName.startsWith(\"EXPR_\")\n ) {\n devLog(\"[ANIMATION] isSpeaking stopped, returning to idle_0\");\n const idle0 = actionsRef.current[\"idle_0\"];\n if (idle0) {\n idle0.reset().fadeIn(0.5).play();\n if (current) current.crossFadeTo(idle0, 0.5, true);\n currentActionRef.current = idle0;\n }\n }\n }\n }, [isSpeaking, avatarScene, expressionUrl]);\n\n // Hardened Mesh Traversal on mount/update\n useEffect(() => {\n if (!avatarScene) return;\n\n let foundHead = false;\n let foundTeeth = false;\n\n avatarScene.traverse((obj) => {\n if (\n (obj as THREE.Mesh).isMesh &&\n (obj as THREE.Mesh & { morphTargetDictionary?: unknown })\n .morphTargetDictionary\n ) {\n const name = obj.name.toLowerCase();\n if (name.includes(\"head\") || name.includes(\"avatar\")) {\n headMeshRef.current = obj as THREE.Mesh;\n foundHead = true;\n devLog(`[ANIMATION] Found head mesh: ${obj.name}`);\n }\n if (name.includes(\"teeth\")) {\n teethMeshRef.current = obj as THREE.Mesh;\n foundTeeth = true;\n devLog(`[ANIMATION] Found teeth mesh: ${obj.name}`);\n }\n }\n });\n\n if (!foundHead || !foundTeeth) {\n devWarn(\n \"[ANIMATION] Could not find all meshes during traversal. Head:\",\n foundHead,\n \"Teeth:\",\n foundTeeth\n );\n } else {\n devLog(\"[ANIMATION] Full Mesh Sync Ready: Head + Teeth found.\");\n }\n\n const headDict = (\n headMeshRef.current as\n | (THREE.Mesh & { morphTargetDictionary?: Record<string, number> })\n | null\n )?.morphTargetDictionary;\n if (headDict) {\n const browKeys = Object.keys(headDict).filter((k) =>\n k.toLowerCase().includes(\"brow\")\n );\n devLog(\"[ANIMATION] Head mesh brow-related morph keys:\", browKeys);\n }\n\n const browMeshes: THREE.Mesh[] = [];\n avatarScene.traverse((obj) => {\n if ((obj as THREE.Mesh).isMesh) {\n const mesh = obj as THREE.Mesh & {\n morphTargetDictionary?: Record<string, number>;\n };\n const dict = mesh.morphTargetDictionary;\n if (\n dict &&\n Object.keys(dict).some((k) => k.toLowerCase().includes(\"brow\"))\n ) {\n browMeshes.push(obj as THREE.Mesh);\n }\n }\n });\n browMeshesRef.current = browMeshes;\n if (browMeshes.length > 0) {\n devLog(\"[ANIMATION] Meshes with brow morphs:\", browMeshes.length);\n }\n }, [avatarScene]);\n\n const applyViseme = (viseme: string, weight = 1): void => {\n const key = `viseme_${viseme}`.toLowerCase();\n const mapping = mtVisemeMixes.find((m) => m.key.toLowerCase() === key);\n if (mapping) {\n for (const shape in mapping.mix) {\n const finalWeight = mapping.mix[shape] * weight;\n setMorphValue(headMeshRef.current, shape, finalWeight);\n setMorphValue(teethMeshRef.current, shape, finalWeight);\n }\n }\n };\n\n const applyMood = (currentMood: string): void => {\n const preset = EMOTION_PRESETS[currentMood] ?? EMOTION_PRESETS.neutral;\n for (const shape in preset) {\n setMorphValue(headMeshRef.current, shape, preset[shape]);\n setMorphValue(teethMeshRef.current, shape, preset[shape]);\n }\n };\n\n useFrame((state, delta) => {\n const decayFactor = Math.pow(0.88, 60 * delta);\n decayVisemes(headMeshRef.current, decayFactor);\n decayVisemes(teethMeshRef.current, decayFactor);\n\n applyMood(mood);\n\n // Blinking logic\n const time = state.clock.elapsedTime;\n let blinkBrowInfluence = 0;\n\n // Diagnostic: Log animation activity every 5 seconds\n if (Math.floor(time) % 5 === 0 && Math.floor((time - delta) % 5) !== 0) {\n let hips: THREE.Object3D | null = null;\n avatarScene.traverse((o) => {\n if (o.name.toLowerCase().includes(\"hips\")) hips = o;\n });\n const hipsPos = hips\n ? `Hips Y: ${(hips as THREE.Object3D).position.y.toFixed(4)}`\n : \"Hips not found\";\n devLog(`[ANIMATION] Mixer Time: ${mixer.time.toFixed(2)}, ${hipsPos}`);\n }\n\n mixer.update(delta);\n\n if (time > nextBlinkRef.current && !blinkActiveRef.current) {\n blinkActiveRef.current = true;\n blinkStartRef.current = time;\n }\n\n if (blinkActiveRef.current) {\n const duration = 0.3; // 200ms for a full blink\n const progress = (time - blinkStartRef.current) / duration;\n if (progress >= 1) {\n blinkActiveRef.current = false;\n nextBlinkRef.current = time + 2 + Math.random() * 5; // Next blink in 2-7 seconds\n } else {\n // Simple triangle wave for blink: 0 -> 1 -> 0\n const influence = progress < 0.5 ? progress * 2 : (1 - progress) * 2;\n\n // Apply eyes blinking\n setMorphValue(headMeshRef.current, \"eyeBlinkLeft\", influence);\n setMorphValue(headMeshRef.current, \"eyeBlinkRight\", influence);\n setMorphValue(teethMeshRef.current, \"eyeBlinkLeft\", influence);\n setMorphValue(teethMeshRef.current, \"eyeBlinkRight\", influence);\n\n blinkBrowInfluence = influence * blinking_blendshapes.browInnerUp;\n }\n }\n\n const preset = EMOTION_PRESETS[mood] ?? EMOTION_PRESETS.neutral;\n const baseInner = preset.browInnerUp ?? 0;\n const baseOuterL = preset.browOuterUpLeft ?? 0;\n const baseOuterR = preset.browOuterUpRight ?? 0;\n const t = time * BROW_IDLE_FREQUENCY;\n const idleInner = BROW_IDLE_AMPLITUDE * Math.sin(t);\n const idleOuterL = BROW_IDLE_AMPLITUDE * 0.7 * Math.sin(t + 0.7);\n const idleOuterR = BROW_IDLE_AMPLITUDE * 0.7 * Math.sin(t + 1.3);\n const clamp01 = (v: number) => Math.max(0, Math.min(1, v));\n const valInner = clamp01(baseInner + idleInner);\n const valOuterL = clamp01(baseOuterL + idleOuterL);\n const valOuterR = clamp01(baseOuterR + idleOuterR);\n const finalValInner = clamp01(valInner + blinkBrowInfluence);\n setMorphValue(headMeshRef.current, \"browInnerUp\", finalValInner);\n setMorphValue(teethMeshRef.current, \"browInnerUp\", finalValInner);\n setMorphValue(headMeshRef.current, \"browOuterUpLeft\", valOuterL);\n setMorphValue(teethMeshRef.current, \"browOuterUpLeft\", valOuterL);\n setMorphValue(headMeshRef.current, \"browOuterUpRight\", valOuterR);\n setMorphValue(teethMeshRef.current, \"browOuterUpRight\", valOuterR);\n\n if (groupRef.current) {\n const targetRotationY = isPlayingRef.current\n ? 0\n : adjustments.rotation[1];\n groupRef.current.rotation.y = THREE.MathUtils.lerp(\n groupRef.current.rotation.y,\n targetRotationY,\n 0.1\n );\n groupRef.current.position.set(...adjustments.position);\n groupRef.current.scale.setScalar(adjustments.scale);\n groupRef.current.rotation.x = adjustments.rotation[0];\n groupRef.current.rotation.z = adjustments.rotation[2];\n }\n\n if (isPlayingRef.current && audioContextRef.current) {\n // THE SYNC ENGINE:\n const audioNow = audioContextRef.current.currentTime;\n const elapsed = (audioNow - responseAudioStartTimeRef.current) * 1000;\n\n const FRAME_SYNC_OFFSET_MS = -150;\n const adjustedElapsed = elapsed - FRAME_SYNC_OFFSET_MS;\n\n for (let i = 0; i < visemeQueueRef.current.length; i++) {\n const item = visemeQueueRef.current[i];\n if (\n adjustedElapsed >= item.vtime &&\n adjustedElapsed < item.vtime + item.vduration\n ) {\n applyViseme(item.viseme, item.weight ?? 1.0);\n }\n }\n\n // AUTO-STOP: If we've passed the scheduled end time, stop\n if (audioNow > nextStartTimeRef.current + 0.5) {\n stopPlayback();\n setIsSpeaking(false);\n }\n }\n });\n return (\n <group ref={groupRef}>\n <primitive object={avatarScene} />\n </group>\n );\n }\n);\n\nfunction toTitleCase(s: string): string {\n if (!s) return \"\";\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\n\nexport interface AvatarWidgetProps {\n /** Encrypted agent token for chat/completions API (sent as query param token=). */\n token?: string;\n /** Backward-compatible alias for `token`. */\n agentToken?: string;\n /** When provided (e.g. on demo page), navigation is delegated here instead of opening a new tab. */\n onNavigationRequested?: (url: string) => void;\n}\n\nconst AvatarWidget = ({\n token,\n agentToken,\n onNavigationRequested,\n}: AvatarWidgetProps = {}): React.ReactElement => {\n const resolvedToken = token ?? agentToken ?? \"\";\n const resolvedAvatarUrl = useResolvedAvatarUrl(resolvedToken || undefined);\n const [query, setQuery] = useState(\"\");\n const [agentResponse, setAgentResponse] = useState(\"\");\n const [status, setStatus] = useState(\"Ready\");\n const [isLoading, setIsLoading] = useState(false);\n const [isSpeaking, setIsSpeaking] = useState(false);\n\n const [isMobile, setIsMobile] = useState(() =>\n typeof window !== \"undefined\" ? window.matchMedia(\"(max-width: 480px)\").matches : false\n );\n useEffect(() => {\n const mql = window.matchMedia(\"(max-width: 480px)\");\n const handler = () => setIsMobile(mql.matches);\n mql.addEventListener(\"change\", handler);\n return () => mql.removeEventListener(\"change\", handler);\n }, []);\n const canvasSize = isMobile ? 80 : 600;\n\n const isProcessingRef = useRef(false);\n const audioQueueRef = useRef<\n { audio: string; visemes: any[]; isNewSegment?: boolean }[]\n >([]);\n const playbackStartTimeRef = useRef(0);\n const isPlayingRef = useRef(false);\n const visemeQueueRef = useRef<VisemeQueueItem[]>([]);\n const audioContextRef = useRef<AudioContext | null>(null);\n const audioSourcesRef = useRef<AudioBufferSourceNode[]>([]);\n const lastAudioChunksRef = useRef<AudioBuffer[]>([]);\n const lastVisemeQueueRef = useRef<VisemeQueueItem[]>([]);\n const responseAudioStartTimeRef = useRef(0);\n const nextStartTimeRef = useRef(0);\n const totalScheduledDurationRef = useRef(0);\n const segmentOffsetMsRef = useRef(0);\n /** When true, user clicked stop; ignore all current and queued chunks until next request. */\n const playbackAbortedByUserRef = useRef(false);\n\n const [visibleSpeechLength, setVisibleSpeechLength] = useState<number | null>(\n null\n );\n const speechIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n\n const [currentMood, setCurrentMood] = useState(\"neutral\");\n const [currentExpression, setCurrentExpression] = useState(\"\");\n const [currentExpressionUrl, setCurrentExpressionUrl] = useState(\"\");\n const [currentPrompt, setCurrentPrompt] = useState(\"Chat with us\");\n const [leadCaptureAskFor, setLeadCaptureAskFor] = useState<string | null>(\n null\n );\n type ThinkingTabPhase = \"hidden\" | \"exiting\" | \"entering\" | \"visible\";\n const [thinkingTabPhase, setThinkingTabPhase] =\n useState<ThinkingTabPhase>(\"hidden\");\n const [thinkingTabContent, setThinkingTabContent] = useState(\"\");\n const nextThinkingContentRef = useRef<string | null>(null);\n const thinkingTabPhaseRef = useRef(thinkingTabPhase);\n thinkingTabPhaseRef.current = thinkingTabPhase;\n const [pendingNavigationUrl, setPendingNavigationUrl] = useState<\n string | null\n >(null);\n const redirectTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const bubbleRef = useRef<HTMLDivElement>(null);\n type BubblePhase = \"hidden\" | \"entering\" | \"visible\" | \"exiting\";\n const [bubblePhase, setBubblePhase] = useState<BubblePhase>(\"hidden\");\n const [bubbleContent, setBubbleContent] = useState(\"\");\n const bubblePhaseRef = useRef(bubblePhase);\n bubblePhaseRef.current = bubblePhase;\n const parsingBufferRef = useRef(\"\");\n const isSpeechModeRef = useRef(false);\n /** Latest response text for use inside SSE callbacks (avoids stale closure over agentResponse). */\n const agentResponseRef = useRef(\"\");\n\n const thinkingTabDesiredContent = useMemo(() => {\n if (status === \"Thinking...\" || status === \"Processing Voice...\")\n return status;\n if (\n leadCaptureAskFor != null &&\n leadCaptureAskFor !== \"\" &&\n leadCaptureAskFor !== \"none\" &&\n leadCaptureAskFor !== \"<none>\"\n ) {\n return `Enter ${toTitleCase(leadCaptureAskFor)}`;\n }\n return null;\n }, [status, leadCaptureAskFor]);\n const thinkingTabShow =\n thinkingTabDesiredContent != null && thinkingTabDesiredContent !== \"\";\n\n useEffect(() => {\n const phase = thinkingTabPhaseRef.current;\n if (phase === \"exiting\" || phase === \"entering\") return;\n if (phase === \"hidden\") {\n if (thinkingTabShow) {\n setThinkingTabContent(thinkingTabDesiredContent ?? \"\");\n setThinkingTabPhase(\"entering\");\n }\n return;\n }\n if (phase === \"visible\") {\n if (\n !thinkingTabShow ||\n thinkingTabDesiredContent !== thinkingTabContent\n ) {\n nextThinkingContentRef.current = thinkingTabShow\n ? thinkingTabDesiredContent\n : null;\n setThinkingTabPhase(\"exiting\");\n }\n }\n }, [\n thinkingTabShow,\n thinkingTabDesiredContent,\n thinkingTabPhase,\n thinkingTabContent,\n ]);\n\n const handleThinkingTabAnimationEnd = useCallback(() => {\n const phase = thinkingTabPhaseRef.current;\n if (phase === \"exiting\") {\n setThinkingTabPhase(\"hidden\");\n const next = nextThinkingContentRef.current;\n nextThinkingContentRef.current = null;\n if (next != null && next !== \"\") {\n setThinkingTabContent(next);\n setThinkingTabPhase(\"entering\");\n }\n } else if (phase === \"entering\") {\n setThinkingTabPhase(\"visible\");\n }\n }, []);\n\n const applyMetadata = (meta: any) => {\n if (!meta) return;\n devLog(\"[STREAM] Applying metadata chunk:\", meta);\n\n if (meta.mood != null) {\n const mood = String(meta.mood).toLowerCase();\n devLog(`[STREAM] Setting mood: ${mood}`);\n setCurrentMood(mood);\n }\n if (meta.expression != null) {\n const expr = String(meta.expression).trim();\n devLog(`[STREAM] Setting expression: \"${expr}\"`);\n setCurrentExpression(expr);\n const matchedAnim = animations.find(\n (a) => a.name.toLowerCase() === expr.toLowerCase()\n );\n devLog(\n `[STREAM] Animation match for \"${expr}\": ${\n matchedAnim ? matchedAnim.name : \"NONE\"\n }`\n );\n setCurrentExpressionUrl(matchedAnim?.url ?? \"\");\n }\n if (meta.navigation != null) {\n const targetUrl = String(meta.navigation).trim();\n if (targetUrl !== \"\") {\n devLog(`[STREAM] Navigation requested to: ${targetUrl}`);\n if (onNavigationRequested) onNavigationRequested(targetUrl);\n else window.open(targetUrl, \"_blank\");\n }\n }\n const askFor = meta.ask_for || meta.lead_capture?.ask_for;\n const requestedRaw = askFor ? String(askFor).trim().toLowerCase() : \"\";\n const isNone = requestedRaw === \"none\" || requestedRaw === \"<none>\";\n if (askFor && !isNone) {\n const requested = requestedRaw;\n devLog(`[STREAM] Lead capture ask_for: ${requested}`);\n setLeadCaptureAskFor(requested || null);\n if (requested === \"email\") setCurrentPrompt(\"Enter your email\");\n else if (requested === \"name\") setCurrentPrompt(\"Enter your name\");\n else if (requested === \"phone\")\n setCurrentPrompt(\"Enter your phone number\");\n else setCurrentPrompt(\"Chat with us\");\n } else if (\n isNone ||\n meta.ask_for === null ||\n (meta.lead_capture && meta.lead_capture.ask_for === null) ||\n meta.ask_for === \"none\"\n ) {\n devLog(\"[STREAM] Lead capture ask_for cleared\");\n setLeadCaptureAskFor(null);\n if (currentPrompt !== \"Chat with us\") setCurrentPrompt(\"Chat with us\");\n }\n\n if (meta.collected) {\n // Logic to handle collected JSON if needed\n }\n if (meta.valid) {\n // Logic to handle valid JSON if needed\n }\n };\n\n const parseMetadataLine = (line: string) => {\n const l = line.trim();\n if (!l) return null;\n if (l.startsWith(\"{\")) {\n try {\n return JSON.parse(l);\n } catch (e) {\n return null;\n }\n }\n if (l.includes(\":\")) {\n const parts = l.split(\":\");\n const k = parts[0].trim().toLowerCase();\n const v = parts.slice(1).join(\":\").trim();\n return { [k]: v };\n }\n return null;\n };\n\n const handleExpressionFinished = useCallback(() => {\n // No-op - redirection handled immediately in handlers\n }, []);\n\n const processTextDelta = (delta: string) => {\n if (!isSpeechModeRef.current) {\n if (delta === \"§\") {\n devLog(`[STREAM] Speech delimiter § detected!`);\n isSpeechModeRef.current = true;\n return;\n } else {\n parsingBufferRef.current += delta;\n while (parsingBufferRef.current.includes(\"\\n\")) {\n const nlIdx = parsingBufferRef.current.indexOf(\"\\n\");\n const line = parsingBufferRef.current.slice(0, nlIdx).trim();\n parsingBufferRef.current = parsingBufferRef.current.slice(nlIdx + 1);\n const parsed = parseMetadataLine(line);\n if (parsed) applyMetadata(parsed);\n }\n }\n } else {\n if (delta === \"§\") {\n // end marker for speech\n isSpeechModeRef.current = false;\n } else {\n agentResponseRef.current += delta;\n setAgentResponse((t) => t + delta);\n }\n }\n };\n\n // Session History Management\n useEffect(() => {\n const initSession = async () => {\n devLog(\"[SESSION] initSession starting...\");\n const existingUid = sessionStorage.getItem(\n \"STREAMOJI_LEADS_SESSION_LEAD_ID\"\n );\n if (!existingUid) {\n devLog(\"[SESSION] No UID found, generating new HMAC UID...\");\n const secret = \"secret\"; // Fallback secret\n const time = Math.floor(Date.now() / 1000).toString();\n\n try {\n // HMAC-SHA256 implementation using Web Crypto API\n const enc = new TextEncoder();\n const keyMaterial = await crypto.subtle.importKey(\n \"raw\",\n enc.encode(secret),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"]\n );\n const signature = await crypto.subtle.sign(\n \"HMAC\",\n keyMaterial,\n enc.encode(time)\n );\n const hashArray = Array.from(new Uint8Array(signature));\n const hashHex = hashArray\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n\n sessionStorage.setItem(\"STREAMOJI_LEADS_SESSION_LEAD_ID\", hashHex);\n devLog(\"[SESSION] New HMAC UID generated and saved:\", hashHex);\n } catch (err) {\n console.error(\"[SESSION] HMAC generation failed:\", err);\n const seed = Math.random().toString(36) + Date.now().toString();\n sessionStorage.setItem(\"STREAMOJI_LEADS_SESSION_LEAD_ID\", seed);\n devLog(\"[SESSION] Fallback UID saved:\", seed);\n }\n } else {\n devLog(\"[SESSION] Found existing UID:\", existingUid);\n }\n if (!sessionStorage.getItem(\"STREAMOJI_LEADS_SESSION_MESSAGES\")) {\n sessionStorage.setItem(\n \"STREAMOJI_LEADS_SESSION_MESSAGES\",\n JSON.stringify([])\n );\n }\n };\n initSession();\n }, []);\n\n const getSessionMessages = (): { role: string; content: string }[] => {\n try {\n return JSON.parse(\n sessionStorage.getItem(\"STREAMOJI_LEADS_SESSION_MESSAGES\") || \"[]\"\n );\n } catch {\n return [];\n }\n };\n\n const saveSessionMessages = (\n messages: { role: string; content: string }[]\n ) => {\n sessionStorage.setItem(\n \"STREAMOJI_LEADS_SESSION_MESSAGES\",\n JSON.stringify(messages)\n );\n };\n\n const [isRecording, setIsRecording] = useState(false);\n const [recordingDuration, setRecordingDuration] = useState(0);\n const mediaRecorderRef = useRef<MediaRecorder | null>(null);\n const audioChunksRef = useRef<Blob[]>([]);\n const recordingStartTimeRef = useRef<number>(0);\n // Use State instead of Ref for analysers to trigger re-renders in children\n const [outAudioAnalyser, setOutAudioAnalyser] = useState<AnalyserNode | null>(\n null\n );\n const [inAudioAnalyser, setInAudioAnalyser] = useState<AnalyserNode | null>(\n null\n );\n\n // Refs to keep audio context alive during recording\n const inputAudioContextRef = useRef<AudioContext | null>(null);\n const inputMediaStreamSourceRef = useRef<MediaStreamAudioSourceNode | null>(\n null\n );\n\n const stopPlayback = useCallback((markAbortedByUser: boolean = false): void => {\n devLog(\"[AUDIO] Stopping playback and clearing queues.\");\n if (markAbortedByUser) {\n playbackAbortedByUserRef.current = true;\n setIsLoading(false);\n setStatus(\"Ready\");\n }\n visemeQueueRef.current = [];\n audioQueueRef.current = [];\n isPlayingRef.current = false;\n setIsSpeaking(false);\n responseAudioStartTimeRef.current = 0;\n nextStartTimeRef.current = 0;\n totalScheduledDurationRef.current = 0;\n audioSourcesRef.current.forEach((s) => {\n try {\n s.stop();\n } catch {\n // ignore\n }\n });\n\n if (redirectTimeoutRef.current) {\n clearTimeout(redirectTimeoutRef.current);\n redirectTimeoutRef.current = null;\n }\n setPendingNavigationUrl(null);\n setCurrentExpressionUrl(\"\");\n\n audioSourcesRef.current = [];\n }, []);\n\n const startRecording = async () => {\n try {\n const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n\n // Setup Analyser for visualization\n const AudioContextClass =\n window.AudioContext || (window as any).webkitAudioContext;\n const audioCtx = new AudioContextClass();\n const source = audioCtx.createMediaStreamSource(stream);\n const analyser = audioCtx.createAnalyser();\n analyser.fftSize = 64;\n source.connect(analyser);\n\n // Store in refs to prevent garbage collection\n inputAudioContextRef.current = audioCtx;\n inputMediaStreamSourceRef.current = source;\n\n setInAudioAnalyser(analyser); // Trigger Render\n\n const mediaRecorder = new MediaRecorder(stream);\n mediaRecorderRef.current = mediaRecorder;\n audioChunksRef.current = [];\n\n mediaRecorder.ondataavailable = (event) => {\n if (event.data.size > 0) {\n audioChunksRef.current.push(event.data);\n }\n };\n\n mediaRecorder.onstop = async () => {\n const duration = Date.now() - recordingStartTimeRef.current;\n\n // Cleanup analyser\n setInAudioAnalyser(null);\n\n // Cleanup context/source\n if (inputMediaStreamSourceRef.current) {\n inputMediaStreamSourceRef.current.disconnect();\n inputMediaStreamSourceRef.current = null;\n }\n if (\n inputAudioContextRef.current &&\n inputAudioContextRef.current.state !== \"closed\"\n ) {\n inputAudioContextRef.current.close();\n inputAudioContextRef.current = null;\n }\n\n if (duration < 1000) {\n setStatus(\"Recording too short. Hold or click longer.\");\n setIsLoading(false);\n return;\n }\n\n const audioBlob = new Blob(audioChunksRef.current, {\n type: \"audio/wav\",\n });\n await handleAudioSubmit(audioBlob);\n };\n\n recordingStartTimeRef.current = Date.now();\n // Request data every 100ms to prevent auto-stop at ~11 seconds\n mediaRecorder.start(100);\n setIsRecording(true);\n setStatus(\"Listening...\");\n } catch (err) {\n console.error(\"Error accessing microphone:\", err);\n setStatus(\"Mic Access Error\");\n }\n };\n\n const stopRecording = () => {\n if (\n mediaRecorderRef.current &&\n mediaRecorderRef.current.state !== \"inactive\"\n ) {\n mediaRecorderRef.current.stop();\n mediaRecorderRef.current.stream\n .getTracks()\n .forEach((track) => track.stop());\n setIsRecording(false);\n }\n };\n\n const cancelRecording = () => {\n if (\n mediaRecorderRef.current &&\n mediaRecorderRef.current.state !== \"inactive\"\n ) {\n // Prevent the onstop handler from processing the audio\n mediaRecorderRef.current.onstop = null;\n mediaRecorderRef.current.stop();\n mediaRecorderRef.current.stream\n .getTracks()\n .forEach((track) => track.stop());\n\n // Cleanup Audio Context manually since we nuked onstop\n setInAudioAnalyser(null);\n if (inputMediaStreamSourceRef.current) {\n inputMediaStreamSourceRef.current.disconnect();\n inputMediaStreamSourceRef.current = null;\n }\n if (\n inputAudioContextRef.current &&\n inputAudioContextRef.current.state !== \"closed\"\n ) {\n inputAudioContextRef.current.close();\n inputAudioContextRef.current = null;\n }\n\n setIsRecording(false);\n audioChunksRef.current = [];\n setStatus(\"Ready\");\n }\n };\n\n const toggleRecording = () => {\n if (isRecording) {\n // If triggered by toggle, treat as a \"finish\"/send action for now,\n // essentially same as clicking the checkmark if we want legacy behavior,\n // but in new UI we have explicit buttons.\n // Here we just call stopRecording() which triggers onstop -> submit.\n stopRecording();\n } else {\n startRecording();\n }\n };\n\n useEffect(() => {\n if (!isSpeaking) return;\n const tick = (): void => {\n const total = totalScheduledDurationRef.current;\n if (total <= 0) return;\n devLog(`[AVATAR] total: ${total}`);\n const ctx = audioContextRef.current;\n const start = responseAudioStartTimeRef.current;\n if (!ctx) return;\n devLog(`[AVATAR] ctx: ${ctx}`);\n devLog(`[AVATAR] start: ${start}`);\n const elapsed = ctx.currentTime - start;\n const playedDuration = Math.min(Math.max(0, elapsed), total);\n const speechLen = agentResponse.trim().length;\n if (speechLen <= 0) return;\n devLog(`[AVATAR] speechLen: ${speechLen}`);\n const newLength = Math.min(\n Math.round((playedDuration / total) * speechLen),\n speechLen\n );\n devLog(`[AVATAR] newLength: ${newLength}`);\n setVisibleSpeechLength(newLength);\n };\n clearInterval(speechIntervalRef.current ?? undefined);\n speechIntervalRef.current = setInterval(tick, 90);\n return () => clearInterval(speechIntervalRef.current ?? undefined);\n }, [isSpeaking, agentResponse, totalScheduledDurationRef.current]);\n\n // Timer for recording duration\n useEffect(() => {\n let interval: number;\n if (isRecording) {\n setRecordingDuration(0);\n interval = window.setInterval(() => {\n setRecordingDuration((prev) => prev + 1);\n }, 1000);\n } else {\n setRecordingDuration(0);\n }\n return () => clearInterval(interval);\n }, [isRecording]);\n\n const audioBufferToWav = (buffer: AudioBuffer): Blob => {\n const numOfChan = buffer.numberOfChannels;\n const length = buffer.length * numOfChan * 2 + 44;\n const bufferOut = new ArrayBuffer(length);\n const view = new DataView(bufferOut);\n let pos = 0;\n\n const setUint16 = (data: number) => {\n view.setUint16(pos, data, true);\n pos += 2;\n };\n const setUint32 = (data: number) => {\n view.setUint32(pos, data, true);\n pos += 4;\n };\n\n setUint32(0x46464952); // \"RIFF\"\n setUint32(length - 8);\n setUint32(0x45564157); // \"WAVE\"\n\n setUint32(0x20746d66); // \"fmt \"\n setUint32(16);\n setUint16(1); // PCM\n setUint16(numOfChan);\n setUint32(buffer.sampleRate);\n setUint32(buffer.sampleRate * 2 * numOfChan);\n setUint16(numOfChan * 2);\n setUint16(16);\n\n setUint32(0x61746164); // \"data\"\n setUint32(length - pos - 4);\n\n const channels = [];\n for (let i = 0; i < numOfChan; i++) channels.push(buffer.getChannelData(i));\n\n let offset = 0;\n while (pos < length) {\n for (let i = 0; i < numOfChan; i++) {\n let sample = Math.max(-1, Math.min(1, channels[i][offset]));\n sample = sample < 0 ? sample * 0x8000 : sample * 0x7fff;\n view.setInt16(pos, sample, true);\n pos += 2;\n }\n offset++;\n }\n\n return new Blob([bufferOut], { type: \"audio/wav\" });\n };\n\n /**\n * Plays a single audio chunk and schedules visemes for sync.\n * This is called for each audio slice in the response.\n */\n const playAudioChunk = async (\n base64Audio: string,\n visemeData: any[],\n isNewSegment: boolean = false\n ) => {\n if (playbackAbortedByUserRef.current) {\n devLog(\"[AUDIO] Ignoring chunk (playback aborted by user).\");\n return;\n }\n // Serialization lock to prevent race conditions on FirstChunk detection\n if (isProcessingRef.current) {\n audioQueueRef.current.push({\n audio: base64Audio,\n visemes: visemeData,\n isNewSegment,\n });\n return;\n }\n isProcessingRef.current = true;\n\n try {\n const AudioContextClass =\n window.AudioContext || (window as any).webkitAudioContext;\n const audioCtx = audioContextRef.current ?? new AudioContextClass();\n\n if (audioCtx.state === \"suspended\") {\n await audioCtx.resume();\n }\n\n audioContextRef.current = audioCtx;\n\n // 1. Decode the binary audio chunk\n const binaryString = window.atob(base64Audio);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++)\n bytes[i] = binaryString.charCodeAt(i);\n const audioBuffer = await audioCtx.decodeAudioData(bytes.buffer.slice(0));\n totalScheduledDurationRef.current += audioBuffer.duration;\n\n // 2. Atomic Scheduling: Decide exactly when this chunk starts\n const now = audioCtx.currentTime;\n let startTime = nextStartTimeRef.current;\n\n // If we've fallen behind or this is the first chunk, reset to \"now\"\n const isFirstChunk = !isPlayingRef.current;\n if (startTime < now) {\n startTime = now + 0.1; // 100ms look-ahead buffer for stability\n }\n\n // UPDATE the global next pointer IMMEDIATELY\n nextStartTimeRef.current = startTime + audioBuffer.duration;\n\n // 3. Create and schedule the source\n const source = audioCtx.createBufferSource();\n source.buffer = audioBuffer;\n\n // Connect to Analyser for visualization\n let analyser = outAudioAnalyser;\n if (!analyser || analyser.context !== audioCtx) {\n analyser = audioCtx.createAnalyser();\n analyser.fftSize = 64;\n analyser.connect(audioCtx.destination);\n setOutAudioAnalyser(analyser); // Trigger Render\n }\n source.connect(analyser); // Connect source to analyser, which connects to destination\n\n audioSourcesRef.current.push(source);\n\n // If user stopped while we were decoding, don't start this chunk\n if (playbackAbortedByUserRef.current) {\n audioSourcesRef.current = audioSourcesRef.current.filter((s) => s !== source);\n return;\n }\n\n // Initial sync on the very first chunk\n if (isFirstChunk) {\n isPlayingRef.current = true;\n setIsSpeaking(true);\n devLog(\n `[AUDIO] setIsSpeaking(true) - First chunk starting at ${startTime.toFixed(\n 3\n )}`\n );\n responseAudioStartTimeRef.current = startTime;\n const delayInMs = (startTime - now) * 1000;\n playbackStartTimeRef.current = performance.now() + delayInMs;\n devLog(\n `[AUDIO] Response started. Initial startTime: ${startTime.toFixed(\n 3\n )}, CT: ${now.toFixed(3)}`\n );\n }\n\n source.start(startTime);\n\n // 4. Queue visemes with precise timing offsets\n const chunkOffsetMs =\n (startTime - responseAudioStartTimeRef.current) * 1000;\n\n // If this is the first chunk of a new TTS segment, this chunk's offset\n // becomes the base for all visemes in this segment.\n if (isNewSegment) {\n segmentOffsetMsRef.current = chunkOffsetMs;\n devLog(\n `[AUDIO] New segment detected at +${chunkOffsetMs.toFixed(\n 0\n )}ms. Resetting segment offset.`\n );\n }\n\n visemeData.forEach((v: any, idx: number) => {\n const symbol = v.symbol ?? \"\";\n if (symbol) {\n const mapped = convertInworldToOculus(symbol);\n\n // Inworld viseme timestamps are relative to segment start\n const vstartMs = Math.round(v.start * 1000);\n const vduration = Math.round((v.duration ?? 0) * 1000);\n const vtime = segmentOffsetMsRef.current + vstartMs;\n\n if (idx < 3) {\n devLog(\n `[AUDIO] Viseme \"${symbol}\": segment_relative=${vstartMs}ms, segment_offset=${segmentOffsetMsRef.current.toFixed(\n 0\n )}ms => vtime=${vtime}ms`\n );\n }\n\n mapped.forEach((m) => {\n visemeQueueRef.current.push({\n viseme: m.v,\n weight: m.w,\n vtime,\n vduration,\n });\n });\n }\n });\n setStatus(\"Speaking...\");\n } finally {\n isProcessingRef.current = false;\n // Process next in queue if any\n if (audioQueueRef.current.length > 0) {\n const next = audioQueueRef.current.shift();\n if (next)\n playAudioChunk(next.audio, next.visemes, (next as any).isNewSegment);\n }\n }\n };\n\n /**\n * Triggered when a voice recording is finished.\n */\n const handleAudioSubmit = async (blob: Blob) => {\n try {\n setIsLoading(true);\n agentResponseRef.current = \"\";\n setAgentResponse(\"\");\n setStatus(\"Processing Voice...\");\n\n const arrayBuffer = await blob.arrayBuffer();\n const audioCtx = new (window.AudioContext ||\n (window as any).webkitAudioContext)();\n const decodedBuffer = await audioCtx.decodeAudioData(arrayBuffer);\n const wavBlob = audioBufferToWav(decodedBuffer);\n\n const reader = new FileReader();\n reader.readAsDataURL(wavBlob);\n reader.onloadend = async () => {\n const base64Audio = (reader.result as string).split(\",\")[1];\n\n stopPlayback();\n setQuery(\"\");\n parsingBufferRef.current = \"\";\n isSpeechModeRef.current = false;\n const sttUrl = `${API_BASE}/stt?token=${encodeURIComponent(\n resolvedToken\n )}`;\n const resp = await fetch(sttUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n audio_base64: base64Audio,\n audio_format: \"wav\",\n }),\n });\n\n if (!resp.ok) {\n const errText = await resp.text();\n let errMessage = \"STT Failed\";\n try {\n const errData = JSON.parse(errText);\n errMessage = errData.error || errMessage;\n } catch {\n if (errText) errMessage = errText.slice(0, 200);\n }\n throw new Error(errMessage);\n }\n\n const body = resp.body;\n devLog(\"this is body\" + body);\n if (!body) {\n setStatus(\"STT Failed\");\n setIsLoading(false);\n return;\n }\n\n const streamReader = body.getReader();\n playbackAbortedByUserRef.current = false;\n const streamDecoder = new TextDecoder();\n let streamBuffer = \"\";\n let streamFinished = false;\n\n const dispatchSTTEvent = async (\n eventName: string,\n data: Record<string, unknown>\n ): Promise<void> => {\n switch (eventName) {\n case \"transcript\":\n if (data.transcript != null) setQuery(String(data.transcript));\n break;\n case \"text\": {\n const delta =\n (data.delta as string) ?? (data.text as string) ?? \"\";\n if (delta) processTextDelta(delta);\n break;\n }\n case \"audio\": {\n const chunk = data.chunk as string | undefined;\n const visemes =\n (data.visemes as Array<{\n symbol?: string;\n start?: number;\n duration?: number;\n }>) ?? [];\n const isNewSegment = !!data.is_new_segment;\n if (chunk) await playAudioChunk(chunk, visemes, isNewSegment);\n break;\n }\n case \"done\": {\n streamFinished = true;\n setStatus(\"Ready\");\n setIsLoading(false);\n break;\n }\n case \"error\": {\n streamFinished = true;\n // setAgentResponse((data.message as string) ?? \"STT failed\");\n setStatus(\"STT Failed\");\n setIsLoading(false);\n break;\n }\n default:\n break;\n }\n };\n\n while (true) {\n const { done, value } = await streamReader.read();\n if (value)\n streamBuffer += streamDecoder.decode(value, { stream: true });\n const parts = streamBuffer.split(\"\\n\\n\");\n streamBuffer = parts.pop() ?? \"\";\n for (const block of parts) {\n const parsed = parseSSEEventBlock(block);\n if (parsed) await dispatchSTTEvent(parsed.event, parsed.data);\n }\n if (done) {\n if (streamBuffer.trim()) {\n const parsed = parseSSEEventBlock(streamBuffer.trim());\n if (parsed) await dispatchSTTEvent(parsed.event, parsed.data);\n }\n if (!streamFinished) {\n setStatus(\"Ready\");\n setIsLoading(false);\n }\n break;\n }\n }\n };\n } catch (err) {\n console.error(\"Audio Submission Error:\", err);\n setStatus(\"STT Failed\");\n setIsLoading(false);\n }\n };\n\n const handleSend = async (e?: React.FormEvent): Promise<void> => {\n if (e) e.preventDefault();\n agentResponseRef.current = \"\";\n setAgentResponse(\"\");\n if (!query || isLoading) return;\n await handleSendAction(query);\n };\n\n /**\n * Parse one SSE event block (lines with event: and data:), return { event, data } or null.\n */\n const parseSSEEventBlock = (\n block: string\n ): { event: string; data: Record<string, unknown> } | null => {\n const lines = block.split(/\\r?\\n/);\n let eventName = \"\";\n let dataPayload = \"\";\n for (const line of lines) {\n if (line.startsWith(\"event:\")) {\n eventName = line.slice(6).trim();\n } else if (line.startsWith(\"data:\")) {\n dataPayload = line.slice(5).trim();\n }\n }\n if (!eventName) return null;\n let data: Record<string, unknown> = {};\n if (dataPayload) {\n try {\n data = JSON.parse(dataPayload) as Record<string, unknown>;\n } catch {\n data = { raw: dataPayload };\n }\n }\n return { event: eventName, data };\n };\n\n /**\n * Dispatch one parsed SSE event (connected, text, audio, done, error).\n */\n const dispatchSSEEvent = (\n eventName: string,\n data: Record<string, unknown>\n ): void => {\n switch (eventName) {\n case \"connected\":\n parsingBufferRef.current = \"\";\n isSpeechModeRef.current = false;\n break;\n case \"text\": {\n const delta = (data.delta as string) ?? \"\";\n devLog(`[SSE] Text Delta: \"${delta}\"`);\n if (delta) processTextDelta(delta);\n break;\n }\n case \"audio\": {\n const chunk = data.chunk as string | undefined;\n const visemes =\n (data.visemes as Array<{\n symbol?: string;\n start?: number;\n duration?: number;\n }>) ?? [];\n if (chunk) playAudioChunk(chunk, visemes);\n break;\n }\n case \"done\": {\n // Update Session History using the final text (ref has latest; agentResponse can be stale in closure).\n const currentMessages = getSessionMessages();\n const finalResponse = agentResponseRef.current.trim();\n const updatedHistory = [\n ...currentMessages,\n { role: \"user\", content: query || \"...\" },\n { role: \"assistant\", content: finalResponse },\n ];\n saveSessionMessages(updatedHistory);\n devLog(\"[SESSION] History updated (SSE done event)\");\n\n lastVisemeQueueRef.current = [...visemeQueueRef.current];\n setStatus(\"Ready\");\n setIsLoading(false);\n setQuery(\"\");\n break;\n }\n case \"error\": {\n const message = (data.message as string) ?? \"Unknown error\";\n agentResponseRef.current = message;\n setAgentResponse(message);\n setStatus(\"Agent Failed\");\n setIsLoading(false);\n break;\n }\n default:\n break;\n }\n };\n\n /**\n * Triggered when a text message is sent manually.\n */\n const handleSendAction = async (text: string): Promise<void> => {\n setIsLoading(true);\n setStatus(\"Thinking...\");\n agentResponseRef.current = \"\";\n parsingBufferRef.current = \"\";\n isSpeechModeRef.current = false;\n stopPlayback();\n totalScheduledDurationRef.current = 0;\n setVisibleSpeechLength(0);\n\n const chatUrl = `${API_BASE}/agent/chat?token=${encodeURIComponent(\n resolvedToken\n )}`;\n\n try {\n const currentMessages = getSessionMessages();\n let uid = sessionStorage.getItem(\"STREAMOJI_LEADS_SESSION_LEAD_ID\");\n\n // Safety check: if UID is missing, generate a quick one\n if (!uid) {\n devWarn(\n \"[CHAT] Session UID missing at send time! Generating emergency backup.\"\n );\n uid = \"emergency-\" + Math.random().toString(36).substring(7);\n sessionStorage.setItem(\"STREAMOJI_LEADS_SESSION_LEAD_ID\", uid);\n }\n\n const payload = {\n history: currentMessages,\n question: text,\n lead_id: uid,\n };\n devLog(\"[CHAT] Sending payload:\", payload);\n\n const resp = await fetch(chatUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(payload),\n cache: \"default\",\n });\n\n if (!resp.ok) {\n throw new Error(\"Agent request failed\");\n }\n\n const body = resp.body;\n if (!body) {\n setStatus(\"Agent Failed\");\n setIsLoading(false);\n return;\n }\n\n const reader = body.getReader();\n playbackAbortedByUserRef.current = false;\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n while (true) {\n const { done, value } = await reader.read();\n devLog(\n `[SSE] Chunk received. done=${done}, length=${value?.length || 0}`\n );\n if (value) {\n buffer += decoder.decode(value, { stream: true });\n }\n\n const parts = buffer.split(\"\\n\\n\");\n buffer = parts.pop() ?? \"\";\n\n for (const block of parts) {\n devLog(`[SSE] Processing block: ${block.slice(0, 50)}...`);\n const parsed = parseSSEEventBlock(block);\n if (parsed) {\n devLog(`[SSE] Event: ${parsed.event}`);\n dispatchSSEEvent(parsed.event, parsed.data);\n }\n }\n\n if (done) {\n devLog(\"[SSE] Stream finished\");\n if (buffer.trim()) {\n const parsed = parseSSEEventBlock(buffer.trim());\n if (parsed) dispatchSSEEvent(parsed.event, parsed.data);\n }\n // Ensure we always re-enable input when stream ends (in case server never sent \"done\" event)\n setStatus(\"Ready\");\n setIsLoading(false);\n setQuery(\"\");\n break;\n }\n }\n } catch (err) {\n console.error(\"Chat Error:\", err);\n setStatus(\"Agent Failed\");\n setIsLoading(false);\n }\n };\n\n const trimmed = agentResponse.trim();\n const displayedText = !trimmed\n ? \"\"\n : isSpeaking\n ? trimmed.slice(\n 0,\n visibleSpeechLength != null && visibleSpeechLength > 0\n ? visibleSpeechLength\n : 0\n )\n : \"\";\n\n useEffect(() => {\n const phase = bubblePhaseRef.current;\n if (phase === \"exiting\") return;\n if (displayedText) {\n setBubbleContent(displayedText);\n if (phase === \"hidden\") setBubblePhase(\"entering\");\n } else {\n if (phase === \"visible\" || phase === \"entering\")\n setBubblePhase(\"exiting\");\n }\n }, [displayedText, bubblePhase]);\n\n const handleBubbleAnimationEnd = useCallback(() => {\n const phase = bubblePhaseRef.current;\n if (phase === \"entering\") setBubblePhase(\"visible\");\n else if (phase === \"exiting\") setBubblePhase(\"hidden\");\n }, []);\n\n useLayoutEffect(() => {\n const el = bubbleRef.current;\n if (el) el.scrollTop = el.scrollHeight;\n }, [bubbleContent]);\n\n return (\n <div className=\"avatar-widget-container\">\n <div className=\"avatar-input-area\">\n {thinkingTabPhase !== \"hidden\" ? (\n <div\n className={`avatar-thinking-tab${\n thinkingTabPhase === \"exiting\"\n ? \" avatar-thinking-tab--exiting\"\n : thinkingTabPhase === \"entering\"\n ? \" avatar-thinking-tab--entering\"\n : \"\"\n }`}\n onAnimationEnd={handleThinkingTabAnimationEnd}\n >\n {thinkingTabContent}\n </div>\n ) : null}\n <div className=\"avatar-input-container\">\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n width: \"100%\",\n height: \"100%\",\n }}\n >\n {isRecording ? (\n <div className=\"avatar-input-recording\">\n <button\n type=\"button\"\n className=\"avatar-recording-cancel\"\n onClick={cancelRecording}\n title=\"Cancel\"\n >\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n style={{ display: \"block\" }}\n >\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n </svg>\n </button>\n <div\n style={{\n flex: 1,\n height: \"100%\",\n position: \"relative\",\n display: \"flex\",\n alignItems: \"center\",\n minWidth: 0,\n }}\n >\n <div style={{ flex: 1, height: \"100%\" }}>\n <VoiceVisualizer analyser={inAudioAnalyser} />\n </div>\n <span\n style={{\n fontSize: \"0.75rem\",\n color: \"#64748b\",\n fontWeight: 500,\n marginLeft: \"4px\",\n minWidth: \"32px\",\n textAlign: \"right\",\n fontVariantNumeric: \"tabular-nums\",\n }}\n >\n {Math.floor(recordingDuration / 60)}:\n {String(recordingDuration % 60).padStart(2, \"0\")}\n </span>\n </div>\n <button\n type=\"button\"\n className=\"avatar-recording-confirm\"\n onClick={stopRecording}\n title=\"Send\"\n >\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n style={{ display: \"block\" }}\n >\n <polyline points=\"20 6 9 17 4 12\"></polyline>\n </svg>\n </button>\n </div>\n ) : isSpeaking ? (\n <div className=\"avatar-input-speaking\">\n <div\n style={{\n flex: 1,\n height: \"100%\",\n display: \"flex\",\n alignItems: \"center\",\n paddingRight: \"8px\",\n }}\n >\n <VoiceVisualizer analyser={outAudioAnalyser} />\n </div>\n <button\n type=\"button\"\n className=\"avatar-speaking-stop\"\n onClick={() => stopPlayback(true)}\n title=\"Stop\"\n >\n <span className=\"avatar-speaking-stop__icon\" aria-hidden />\n </button>\n </div>\n ) : isLoading ? (\n <div\n style={{\n flex: 1,\n height: \"100%\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n }}\n >\n <div className=\"avatar-input-loader\" />\n </div>\n ) : (\n <form\n onSubmit={handleSend}\n style={{\n flex: 1,\n display: \"flex\",\n height: \"100%\",\n alignItems: \"center\",\n }}\n >\n <input\n id=\"avatar-text-input\"\n type=\"text\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Ask me anything\"\n disabled={isLoading}\n autoComplete=\"off\"\n style={{ width: \"100%\", height: \"100%\" }}\n />\n {query.trim() === \"\" ? (\n <button\n type=\"button\"\n className=\"mic-button\"\n onClick={startRecording}\n disabled={isLoading}\n style={{ backgroundColor: \"#1e4a5e\" }}\n >\n <svg\n width=\"28\"\n height=\"28\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M12 14C13.66 14 15 12.66 15 11V5C15 3.34 13.66 2 12 2C10.34 2 9 3.34 9 5V11C9 12.66 10.34 14 12 14Z\"\n fill=\"white\"\n />\n <path\n d=\"M17 11C17 13.76 14.76 16 12 16C9.24 16 7 13.76 7 11H5C5 14.53 7.61 17.43 11 17.93V21H13V17.93C16.39 17.43 19 14.53 19 11H17Z\"\n fill=\"white\"\n />\n </svg>\n </button>\n ) : (\n <button\n type=\"submit\"\n className=\"mic-button\"\n disabled={isLoading}\n style={{ backgroundColor: \"#1e4a5e\" }}\n title=\"Send\"\n >\n <img src=\"/assets/leadmoji-logo-send.png\" alt=\"Send\" width={24} height={24} />\n </button>\n )}\n </form>\n )}\n </div>\n </div>\n </div>\n\n <div className=\"avatar-wrapper\">\n <div className=\"avatar-scene-wrapper\">\n {bubblePhase !== \"hidden\" && (\n <div\n className={`avatar-bubble${\n bubblePhase === \"entering\"\n ? \" avatar-bubble--entering\"\n : bubblePhase === \"exiting\"\n ? \" avatar-bubble--exiting\"\n : \"\"\n }`}\n onAnimationEnd={handleBubbleAnimationEnd}\n >\n <div ref={bubbleRef} className=\"avatar-bubble__content\">\n {bubbleContent}\n </div>\n </div>\n )}\n <div\n className=\"avatar-canvas-layer\"\n style={{ width: canvasSize, height: canvasSize }}\n >\n <Canvas\n shadows\n camera={{ position: [0.2, 1.4, 3], fov: 42 }}\n gl={{ alpha: true }}\n dpr={1.8}\n style={{ pointerEvents: \"none\", width: \"100%\", height: \"100%\" }}\n >\n <CameraLookAt target={CAMERA_LOOKAT} />\n <ambientLight intensity={0.7} />\n <directionalLight position={[0, 2, 2]} intensity={1.0} />\n <Environment preset=\"city\" />\n <Suspense fallback={null}>\n {resolvedAvatarUrl !== null && (\n <AvatarModel\n avatarUrl={resolvedAvatarUrl}\n isPlayingRef={isPlayingRef}\n visemeQueueRef={visemeQueueRef}\n audioContextRef={audioContextRef}\n responseAudioStartTimeRef={responseAudioStartTimeRef}\n adjustments={ADJUSTMENTS}\n mood={currentMood}\n expression={currentExpression}\n agentResponse={agentResponse}\n isSpeaking={isSpeaking}\n nextStartTimeRef={nextStartTimeRef}\n stopPlayback={stopPlayback}\n setIsSpeaking={setIsSpeaking}\n expressionUrl={currentExpressionUrl}\n onExpressionFinished={handleExpressionFinished}\n />\n )}\n </Suspense>\n {/* <OrbitControls enableDamping={false} enableZoom={false} enableRotate={false} target={[-0.2, 2, 0]} /> */}\n </Canvas>\n </div>\n </div>\n </div>\n </div>\n );\n};\n\nexport default AvatarWidget;\n\n// You are a classifier + responder.\n// You MUST return ONLY valid JSON.\n// Do NOT include markdown, explanations, or extra text.\n\n// Schema:\n// {\n// \"expression\": for greetings like \"hey\" , \"hello\" return \"WAVING_HELLO\" , if you are not sure about the answer return \"I_HAVE_NO_IDEA\" , else send \"\".\n// \"mood\": one of [\"neutral\",\"happy\",\"angry\",\"sad\",\"fear\",\"disgust\",\"love\"],\n// \"navigation\": add the link of the chunk you're refering to,\n// \"text\": string\n// }\n\n// Rules:\n// - Always choose the closest matching mood.\n// - If unsure, use mood=\"neutral\".\n","import BaseAvatarWidget from \"../../../src/components/AvatarWidget\";\n\nexport interface AvatarWidgetProps {\n token?: string;\n onNavigationRequested?: (url: string) => void;\n}\n\nconst AvatarWidget = ({\n token,\n onNavigationRequested\n}: AvatarWidgetProps): React.ReactElement => {\n return (\n <BaseAvatarWidget\n token={token}\n onNavigationRequested={onNavigationRequested}\n />\n );\n};\n\nexport default AvatarWidget;\n"],"names":["devLog","args","devWarn","VoiceVisualizer","analyser","canvasRef","useRef","prevDataRef","useEffect","canvas","ctx","animationId","dataArray","draw","w","h","centerY","barWidth","totalBarSpace","availableWidth","maxBars","visualWidth","startX","bufferLength","step","targetHeights","i","maxVal","chunkStart","chunkEnd","j","val","percent","rawHeight","mirrorI","targetHeight","currentHeight","x","y","jsx","API_BASE","R2_AVATAR_BASE","DEFAULT_AVATAR_URL","sha256Hex","message","buf","b","useResolvedAvatarUrl","token","url","setUrl","useState","cancelled","hash","r2Url","res","IDLE_ANIMATION_URLS","animations","TALKING_ANIMATIONS","ADJUSTMENTS","CAMERA_LOOKAT","blinking_blendshapes","BROW_IDLE_AMPLITUDE","BROW_IDLE_FREQUENCY","EMOTION_PRESETS","mtVisemeMixes","INWORLD_TO_OCULUS","convertInworldToOculus","inworldVisemeSymbol","symbol","CameraLookAt","target","camera","useThree","setMorphValue","mesh","name","value","m","dict","infl","key","decayVisemes","factor","AvatarModel","memo","avatarUrl","isPlayingRef","visemeQueueRef","audioContextRef","responseAudioStartTimeRef","adjustments","mood","expression","agentResponse","isSpeaking","nextStartTimeRef","stopPlayback","setIsSpeaking","expressionUrl","onExpressionFinished","avatarScene","useGLTF","idleGLTFs","idleAnimations","useMemo","g","talkGLTFs","talkAnimations","groupRef","headMeshRef","teethMeshRef","browMeshesRef","mixer","THREE","actionsRef","currentActionRef","nextBlinkRef","blinkActiveRef","blinkStartRef","pendingExpressionRef","a","action","firstIdle","isAnythingPlaying","lastUrlRef","GLTFLoader","gltf","clip","current","err","onActionFinished","e","finishedName","nextIndex","nextAction","talk0","idle0","currentName","pendingUrl","exprAction","obj","headDict","k","browMeshes","applyViseme","viseme","weight","mapping","shape","finalWeight","applyMood","currentMood","preset","useFrame","state","delta","decayFactor","time","blinkBrowInfluence","hips","o","hipsPos","progress","influence","baseInner","baseOuterL","baseOuterR","t","idleInner","idleOuterL","idleOuterR","clamp01","v","valInner","valOuterL","valOuterR","finalValInner","targetRotationY","audioNow","adjustedElapsed","item","toTitleCase","s","AvatarWidget","agentToken","onNavigationRequested","resolvedToken","resolvedAvatarUrl","query","setQuery","setAgentResponse","status","setStatus","isLoading","setIsLoading","isMobile","setIsMobile","mql","handler","canvasSize","isProcessingRef","audioQueueRef","playbackStartTimeRef","audioSourcesRef","lastVisemeQueueRef","totalScheduledDurationRef","segmentOffsetMsRef","playbackAbortedByUserRef","visibleSpeechLength","setVisibleSpeechLength","speechIntervalRef","setCurrentMood","currentExpression","setCurrentExpression","currentExpressionUrl","setCurrentExpressionUrl","currentPrompt","setCurrentPrompt","leadCaptureAskFor","setLeadCaptureAskFor","thinkingTabPhase","setThinkingTabPhase","thinkingTabContent","setThinkingTabContent","nextThinkingContentRef","thinkingTabPhaseRef","pendingNavigationUrl","setPendingNavigationUrl","redirectTimeoutRef","bubbleRef","bubblePhase","setBubblePhase","bubbleContent","setBubbleContent","bubblePhaseRef","parsingBufferRef","isSpeechModeRef","agentResponseRef","thinkingTabDesiredContent","thinkingTabShow","phase","handleThinkingTabAnimationEnd","useCallback","next","applyMetadata","meta","expr","matchedAnim","targetUrl","askFor","requestedRaw","isNone","requested","parseMetadataLine","line","l","parts","handleExpressionFinished","processTextDelta","nlIdx","parsed","secret","enc","keyMaterial","signature","hashHex","seed","getSessionMessages","saveSessionMessages","messages","isRecording","setIsRecording","recordingDuration","setRecordingDuration","mediaRecorderRef","audioChunksRef","recordingStartTimeRef","outAudioAnalyser","setOutAudioAnalyser","inAudioAnalyser","setInAudioAnalyser","inputAudioContextRef","inputMediaStreamSourceRef","markAbortedByUser","startRecording","stream","AudioContextClass","audioCtx","source","mediaRecorder","event","duration","audioBlob","handleAudioSubmit","stopRecording","track","cancelRecording","tick","total","start","elapsed","playedDuration","speechLen","newLength","interval","prev","audioBufferToWav","buffer","numOfChan","length","bufferOut","view","pos","setUint16","data","setUint32","channels","offset","sample","playAudioChunk","base64Audio","visemeData","isNewSegment","binaryString","bytes","audioBuffer","now","startTime","isFirstChunk","delayInMs","chunkOffsetMs","idx","mapped","vstartMs","vduration","vtime","blob","arrayBuffer","decodedBuffer","wavBlob","reader","sttUrl","resp","errText","errMessage","body","streamReader","streamDecoder","streamBuffer","streamFinished","dispatchSTTEvent","eventName","chunk","visemes","done","block","parseSSEEventBlock","handleSend","handleSendAction","lines","dataPayload","dispatchSSEEvent","currentMessages","finalResponse","updatedHistory","text","chatUrl","uid","payload","decoder","trimmed","displayedText","handleBubbleAnimationEnd","useLayoutEffect","el","jsxs","Canvas","Environment","Suspense","BaseAvatarWidget"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
.avatar-widget-container{position:fixed;bottom:0;right:0;z-index:1000;display:flex;flex-direction:column;align-items:flex-end;gap:1rem;width:280px}.avatar-bubble{z-index:10002;background:#fff;padding:1.25rem 1.5rem 1.5rem;min-height:3em;border-radius:24px;color:#1e293b;font-family:Inter,system-ui,-apple-system,sans-serif;font-size:1rem;font-weight:500;line-height:1.5;box-shadow:0 10px 25px -5px #0000001a,0 8px 10px -6px #0000001a;border:1px solid rgba(0,0,0,.05);position:absolute;top:64px;left:50%;transform:translate(-50%,-100%);max-width:min(320px,85vw);width:100%;pointer-events:none;-webkit-user-select:text;user-select:text;overflow:visible;word-break:break-word}.avatar-bubble:after{content:"";position:absolute;bottom:0;right:18%;width:29px;height:33px;background-image:url("data:image/svg+xml,%3Csvg width='29' height='33' viewBox='0 0 29 33' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M5.5%209C3.55459%207.42985%204.13875e-07%208%204.13875e-07%208L0.517701%20-9.50694e-06L28.5%200.995422L28.2689%207.49131C28.2689%207.49131%2023.754%207.83102%2022.2194%208.77706C20.6848%209.7231%2019.1146%2011.6685%2018.5794%2012.6501C18.0442%2013.6317%206.37564%2032.2286%206.37564%2032.2286C6.37564%2032.2286%207.46192%2015.7568%207.53302%2013.7581C7.60412%2011.7594%207.44541%2010.5702%205.5%209Z' fill='white'/%3E%3C/svg%3E");background-size:100% 100%;background-repeat:no-repeat;filter:drop-shadow(0 2px 2px rgba(0,0,0,.08));transform:translateY(78%) rotate(0)}.avatar-bubble__content{max-height:45px;overflow-y:auto;word-break:break-word;scrollbar-width:none;-ms-overflow-style:none}.avatar-bubble__content::-webkit-scrollbar{display:none}.avatar-bubble--entering{animation:avatarBubbleEnter .35s ease-out forwards}.avatar-bubble--exiting{animation:avatarBubbleExit .6s ease-in 2s forwards}@keyframes avatarBubbleEnter{0%{transform:translate(120%,-100%);opacity:0}to{transform:translate(-50%,-100%);opacity:1}}@keyframes avatarBubbleExit{0%{transform:translate(-50%,-100%);opacity:1}to{transform:translate(120%,-100%);opacity:0}}.avatar-wrapper{pointer-events:none;width:300px;height:400px;cursor:pointer;transition:transform .3s ease;z-index:1000}.avatar-wrapper:hover{transform:scale(1.05)}.avatar-circle-bg{width:100%;height:100%;border-radius:50%;border:2px solid white;background:linear-gradient(135deg,#008b8b,#38bdf8);box-shadow:0 4px 20px #0000004d;position:absolute;top:0;left:0;z-index:1}.avatar-circle-bg:before{content:"";position:absolute;inset:0;border-radius:50%;box-shadow:0 0 #ffffffb3;animation:pulse-white 2s infinite;z-index:-1}.avatar-scene-wrapper{z-index:2;pointer-events:none;overflow:visible}.avatar-scene-wrapper .avatar-canvas-layer{position:absolute;bottom:0;right:0;pointer-events:none!important;overflow:visible}.avatar-canvas-layer{width:400px;height:400px;z-index:2;pointer-events:none!important;overflow:visible}.avatar-canvas-layer canvas{pointer-events:none!important}@keyframes pulse-white{0%{transform:scale(.95);box-shadow:0 0 #ffffffb3}70%{transform:scale(1);box-shadow:0 0 0 10px #fff0}to{transform:scale(.95);box-shadow:0 0 #fff0}}@keyframes popIn{0%{opacity:0;transform:scale(.8) translateY(10px)}to{opacity:1;transform:scale(1) translateY(0)}}.avatar-input-area{position:absolute;bottom:4px;right:4px;z-index:10001;display:flex;flex-direction:column;align-items:flex-end;gap:6px}@keyframes thinkingTabPopUp{0%{transform:translateY(28px);opacity:0}to{transform:translateY(0);opacity:1}}@keyframes thinkingTabPopDown{0%{transform:translateY(0);opacity:1}to{transform:translateY(28px);opacity:0}}.avatar-thinking-tab{background:#1e4a5e;color:#fff;font-size:.8rem;font-weight:500;padding:6px 14px;border-radius:12px 12px 0 0;font-family:Inter,system-ui,sans-serif;white-space:nowrap;box-shadow:0 2px 8px #0000001a;position:absolute;bottom:45px;left:25px}.avatar-thinking-tab--entering{animation:thinkingTabPopUp .25s ease-out forwards}.avatar-thinking-tab--exiting{animation:thinkingTabPopDown .25s ease-in forwards}.avatar-input-container{position:relative;bottom:auto;right:auto;z-index:10001;width:100%;min-width:280px;height:48px;min-height:48px;pointer-events:auto;display:flex;flex-direction:column;justify-content:center;align-items:stretch;overflow:hidden;background:#fff;border-radius:50px;padding:0 4px 0 20px;box-shadow:0 2px 12px #00000014}.avatar-input-container>div:first-child{display:flex;align-items:center;width:100%;height:100%;min-height:0}.avatar-input-container .avatar-input-loader{height:30px;aspect-ratio:2.5;--_g: no-repeat radial-gradient(farthest-side, #1e4a5e 90%, #0000);background:var(--_g),var(--_g),var(--_g),var(--_g);background-size:20% 50%;animation:avatar-input-l44 1s infinite linear alternate}@keyframes avatar-input-l44{0%,5%{background-position:0% 50%,calc(1*100%/3) 50%,calc(2*100%/3) 50%,100% 50%}12.5%{background-position:0% 0,calc(1*100%/3) 50%,calc(2*100%/3) 50%,100% 50%}25%{background-position:0% 0,calc(1*100%/3) 0,calc(2*100%/3) 50%,100% 50%}37.5%{background-position:0% 100%,calc(1*100%/3) 0,calc(2*100%/3) 0,100% 50%}50%{background-position:0% 100%,calc(1*100%/3) 100%,calc(2*100%/3) 0,100% 0}62.5%{background-position:0% 50%,calc(1*100%/3) 100%,calc(2*100%/3) 100%,100% 0}75%{background-position:0% 50%,calc(1*100%/3) 50%,calc(2*100%/3) 100%,100% 100%}87.5%{background-position:0% 50%,calc(1*100%/3) 50%,calc(2*100%/3) 50%,100% 100%}95%,to{background-position:0% 50%,calc(1*100%/3) 50%,calc(2*100%/3) 50%,100% 50%}}.avatar-input-header{background:#1e4a5e;color:#fff;padding:6px 16px;width:fit-content;min-width:35%;margin-left:8%;border-radius:12px 12px 0 0;font-family:Inter,system-ui,sans-serif;font-size:11px;font-weight:500;letter-spacing:-.01em;text-align:center;white-space:nowrap}.avatar-input-body{background:#fff;padding:6px;border-radius:50px;display:flex;flex-direction:column}.avatar-input-body form{display:flex;align-items:center;width:100%}#avatar-text-input{background:transparent;border:none;color:#334155;font-size:.95rem;line-height:1.25;outline:none;padding:0 2px 0 12px;flex:1;min-width:0;font-family:Inter,system-ui,sans-serif}#avatar-text-input::placeholder{color:#94a3b8}.avatar-input-container .mic-button{background:#1e4a5e;color:#fff;border:none;width:38px;height:38px;border-radius:50%!important;display:flex;justify-content:center;align-items:center;cursor:pointer;transition:all .2s cubic-bezier(.4,0,.2,1);flex-shrink:0;margin-right:0;margin-left:8px}.avatar-input-container .mic-button:hover{background:#163a4a;transform:translateY(-1px)}.avatar-input-recording{flex:1;display:flex;align-items:center;width:100%;height:100%;min-height:36px;gap:8px}.avatar-input-recording .avatar-recording-cancel,.avatar-input-recording .avatar-recording-confirm{width:32px;height:32px;min-width:32px;min-height:32px;border-radius:50%;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;padding:0;line-height:0;align-self:center;box-sizing:border-box}.avatar-input-recording .avatar-recording-cancel{background:#cbd5e1;color:#fff}.avatar-input-recording .avatar-recording-confirm{background:#1e4a5e;color:#fff}.avatar-input-recording .avatar-recording-cancel:hover,.avatar-input-recording .avatar-recording-confirm:hover{opacity:.9}.avatar-status-text{font-size:.75rem;color:#64748b;text-align:center;padding-bottom:4px;font-family:Inter,sans-serif}@media(max-width:480px){.avatar-widget-container{bottom:.5rem;right:.5rem}.avatar-bubble{font-size:.9rem;padding:.75rem 1rem;max-width:250px}.avatar-wrapper{width:56px;height:56px}.avatar-scene-wrapper{width:400px;height:400px}.avatar-input-container{right:0;bottom:0;min-width:220px;filter:drop-shadow(0 5px 15px rgba(0,0,0,.2))}.avatar-input-header{padding:8px 12px;font-size:14px}}.speech-bubble{background:#fff;border-radius:40px 40px 0/40px 40px 0px;padding:16px 24px;color:#1e293b;font-family:Inter,system-ui,-apple-system,sans-serif;font-size:16px;font-weight:500;line-height:1.4;max-width:320px;width:max-content;box-shadow:0 10px 25px -5px #0000001a,0 8px 10px -6px #0000001a;pointer-events:none;-webkit-user-select:none;user-select:none;position:relative;text-align:center;z-index:10002;transform:translate(-50%,-100%);margin-bottom:20px}@keyframes bubbleFadeIn{0%{opacity:0;transform:translate(-50%) scale(.9)}to{opacity:1;transform:translate(-50%) scale(1)}}.thinking-dots{display:flex;justify-content:center;align-items:center;gap:4px;height:12px}.thinking-dots span{width:6px;height:6px;background-color:#94a3b8;border-radius:50%;display:inline-block;animation:thinking-dot-pulse 1.4s infinite ease-in-out both}.thinking-dots span:nth-child(1){animation-delay:-.32s}.thinking-dots span:nth-child(2){animation-delay:-.16s}@keyframes thinking-dot-pulse{0%,80%,to{transform:scale(0);opacity:.3}40%{transform:scale(1);opacity:1}}
|
|
1
|
+
.avatar-widget-container{position:fixed;bottom:0;right:0;z-index:1000;display:flex;flex-direction:column;align-items:flex-end;gap:1rem;width:280px}.avatar-bubble{z-index:10002;background:#fff;padding:1.25rem 1.5rem 1.5rem;min-height:3em;border-radius:24px;color:#1e293b;font-family:Inter,system-ui,-apple-system,sans-serif;font-size:1rem;font-weight:500;line-height:1.5;box-shadow:0 10px 25px -5px #0000001a,0 8px 10px -6px #0000001a;border:1px solid rgba(0,0,0,.05);position:absolute;top:64px;left:50%;transform:translate(-50%,-100%);max-width:min(320px,85vw);width:100%;pointer-events:none;-webkit-user-select:text;user-select:text;overflow:visible;word-break:break-word}.avatar-bubble:after{content:"";position:absolute;bottom:0;right:18%;width:29px;height:33px;background-image:url("data:image/svg+xml,%3Csvg width='29' height='33' viewBox='0 0 29 33' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M5.5%209C3.55459%207.42985%204.13875e-07%208%204.13875e-07%208L0.517701%20-9.50694e-06L28.5%200.995422L28.2689%207.49131C28.2689%207.49131%2023.754%207.83102%2022.2194%208.77706C20.6848%209.7231%2019.1146%2011.6685%2018.5794%2012.6501C18.0442%2013.6317%206.37564%2032.2286%206.37564%2032.2286C6.37564%2032.2286%207.46192%2015.7568%207.53302%2013.7581C7.60412%2011.7594%207.44541%2010.5702%205.5%209Z' fill='white'/%3E%3C/svg%3E");background-size:100% 100%;background-repeat:no-repeat;filter:drop-shadow(0 2px 2px rgba(0,0,0,.08));transform:translateY(78%) rotate(0)}.avatar-bubble__content{max-height:63px;overflow-y:auto;word-break:break-word;scrollbar-width:none;-ms-overflow-style:none}.avatar-bubble__content::-webkit-scrollbar{display:none}.avatar-bubble--entering{animation:avatarBubbleEnter .35s ease-out forwards}.avatar-bubble--exiting{animation:avatarBubbleExit .6s ease-in 2s forwards}@keyframes avatarBubbleEnter{0%{transform:translate(120%,-100%);opacity:0}to{transform:translate(-50%,-100%);opacity:1}}@keyframes avatarBubbleExit{0%{transform:translate(-50%,-100%);opacity:1}to{transform:translate(120%,-100%);opacity:0}}.avatar-wrapper{pointer-events:none;width:300px;height:400px;cursor:pointer;transition:transform .3s ease;z-index:1000}.avatar-wrapper:hover{transform:scale(1.05)}.avatar-circle-bg{width:100%;height:100%;border-radius:50%;border:2px solid white;background:linear-gradient(135deg,#008b8b,#38bdf8);box-shadow:0 4px 20px #0000004d;position:absolute;top:0;left:0;z-index:1}.avatar-circle-bg:before{content:"";position:absolute;inset:0;border-radius:50%;box-shadow:0 0 #ffffffb3;animation:pulse-white 2s infinite;z-index:-1}.avatar-scene-wrapper{z-index:2;pointer-events:none;overflow:visible}.avatar-scene-wrapper .avatar-canvas-layer{position:absolute;bottom:0;right:0;pointer-events:none!important;overflow:visible}.avatar-canvas-layer{width:400px;height:400px;z-index:2;pointer-events:none!important;overflow:visible}.avatar-canvas-layer canvas{pointer-events:none!important}@keyframes pulse-white{0%{transform:scale(.95);box-shadow:0 0 #ffffffb3}70%{transform:scale(1);box-shadow:0 0 0 10px #fff0}to{transform:scale(.95);box-shadow:0 0 #fff0}}@keyframes popIn{0%{opacity:0;transform:scale(.8) translateY(10px)}to{opacity:1;transform:scale(1) translateY(0)}}.avatar-input-area{position:absolute;bottom:4px;right:4px;z-index:10001;display:flex;flex-direction:column;align-items:flex-end;gap:6px}@keyframes thinkingTabPopUp{0%{transform:translateY(28px);opacity:0}to{transform:translateY(0);opacity:1}}@keyframes thinkingTabPopDown{0%{transform:translateY(0);opacity:1}to{transform:translateY(28px);opacity:0}}.avatar-thinking-tab{background:#1e4a5e;color:#fff;font-size:.8rem;font-weight:500;padding:6px 14px;border-radius:12px 12px 0 0;font-family:Inter,system-ui,sans-serif;white-space:nowrap;box-shadow:0 2px 8px #0000001a;position:absolute;bottom:45px;left:25px}.avatar-thinking-tab--entering{animation:thinkingTabPopUp .25s ease-out forwards}.avatar-thinking-tab--exiting{animation:thinkingTabPopDown .25s ease-in forwards}.avatar-input-container{position:relative;bottom:auto;right:auto;z-index:10001;width:100%;min-width:280px;height:48px;min-height:48px;pointer-events:auto;display:flex;flex-direction:column;justify-content:center;align-items:stretch;overflow:hidden;background:#fff;border-radius:50px;padding:0 4px 0 20px;box-shadow:0 2px 12px #00000014}.avatar-input-container>div:first-child{display:flex;align-items:center;width:100%;height:100%;min-height:0}.avatar-input-container .avatar-input-loader{height:30px;aspect-ratio:2.5;--_g: no-repeat radial-gradient(farthest-side, #1e4a5e 90%, #0000);background:var(--_g),var(--_g),var(--_g),var(--_g);background-size:20% 50%;animation:avatar-input-l44 1s infinite linear alternate}@keyframes avatar-input-l44{0%,5%{background-position:0% 50%,calc(1*100%/3) 50%,calc(2*100%/3) 50%,100% 50%}12.5%{background-position:0% 0,calc(1*100%/3) 50%,calc(2*100%/3) 50%,100% 50%}25%{background-position:0% 0,calc(1*100%/3) 0,calc(2*100%/3) 50%,100% 50%}37.5%{background-position:0% 100%,calc(1*100%/3) 0,calc(2*100%/3) 0,100% 50%}50%{background-position:0% 100%,calc(1*100%/3) 100%,calc(2*100%/3) 0,100% 0}62.5%{background-position:0% 50%,calc(1*100%/3) 100%,calc(2*100%/3) 100%,100% 0}75%{background-position:0% 50%,calc(1*100%/3) 50%,calc(2*100%/3) 100%,100% 100%}87.5%{background-position:0% 50%,calc(1*100%/3) 50%,calc(2*100%/3) 50%,100% 100%}95%,to{background-position:0% 50%,calc(1*100%/3) 50%,calc(2*100%/3) 50%,100% 50%}}.avatar-input-header{background:#1e4a5e;color:#fff;padding:6px 16px;width:fit-content;min-width:35%;margin-left:8%;border-radius:12px 12px 0 0;font-family:Inter,system-ui,sans-serif;font-size:11px;font-weight:500;letter-spacing:-.01em;text-align:center;white-space:nowrap}.avatar-input-body{background:#fff;padding:6px;border-radius:50px;display:flex;flex-direction:column}.avatar-input-body form{display:flex;align-items:center;width:100%}#avatar-text-input{background:transparent;border:none;color:#334155;font-size:.95rem;line-height:1.25;outline:none;padding:0 2px 0 12px;flex:1;min-width:0;font-family:Inter,system-ui,sans-serif}#avatar-text-input::placeholder{color:#94a3b8}.avatar-input-container .mic-button{background:#1e4a5e;color:#fff;border:none;width:38px;height:38px;border-radius:50%!important;display:flex;justify-content:center;align-items:center;cursor:pointer;transition:all .2s cubic-bezier(.4,0,.2,1);flex-shrink:0;margin-right:0;margin-left:8px}.avatar-input-container .mic-button:hover{background:#163a4a;transform:translateY(-1px)}.avatar-input-recording{flex:1;display:flex;align-items:center;width:100%;height:100%;min-height:36px;gap:8px}.avatar-input-recording .avatar-recording-cancel,.avatar-input-recording .avatar-recording-confirm{width:32px;height:32px;min-width:32px;min-height:32px;border-radius:50%;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;padding:0;line-height:0;align-self:center;box-sizing:border-box}.avatar-input-recording .avatar-recording-cancel{background:#cbd5e1;color:#fff}.avatar-input-recording .avatar-recording-confirm{background:#1e4a5e;color:#fff}.avatar-input-recording .avatar-recording-cancel:hover,.avatar-input-recording .avatar-recording-confirm:hover{opacity:.9}.avatar-input-speaking{flex:1;display:flex;align-items:center;width:100%;height:100%;min-height:36px;gap:8px}.avatar-input-speaking .avatar-speaking-stop{width:32px;height:32px;min-width:32px;min-height:32px;border-radius:50%;border:2px solid #7dd3fc;background:#1e4a5e;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;padding:0;line-height:0;align-self:center;box-sizing:border-box}.avatar-input-speaking .avatar-speaking-stop:hover{opacity:.9}.avatar-speaking-stop__icon{width:10px;height:10px;background:#cbd5e1;border-radius:2px;display:block}.avatar-status-text{font-size:.75rem;color:#64748b;text-align:center;padding-bottom:4px;font-family:Inter,sans-serif}@media(max-width:480px){.avatar-widget-container{width:100%;left:0;right:0;bottom:0;padding:0 max(16px,env(safe-area-inset-left));padding-right:max(16px,env(safe-area-inset-right));box-sizing:border-box;flex-direction:column-reverse;align-items:flex-end}.avatar-input-area{position:relative;width:100%}.avatar-input-container{width:100%;min-width:0;max-width:100%;right:auto;bottom:auto;filter:drop-shadow(0 5px 15px rgba(0,0,0,.2))}.avatar-wrapper{width:80px;height:80px;align-self:flex-end}.avatar-scene-wrapper{width:80px;height:80px}.avatar-canvas-layer{width:350px!important;height:350px!important;right:-28px!important}.avatar-bubble{left:16px;right:auto;top:40px;transform:translateY(-50%);max-width:calc(100vw - 120px);font-size:.85rem;padding:.6rem .85rem .75rem;border-radius:18px;height:90px;box-shadow:0 6px 22px -4px #00000021,0 2px 7px -2px #00000014}.avatar-bubble--entering{animation:avatarBubbleEnterMobile .35s ease-out forwards}.avatar-bubble--exiting{animation:avatarBubbleExitMobile .6s ease-in 2s forwards}@keyframes avatarBubbleEnterMobile{0%{transform:translate(-20px,-50%);opacity:0}to{transform:translateY(-50%);opacity:1}}@keyframes avatarBubbleExitMobile{0%{transform:translateY(-50%);opacity:1}to{transform:translate(-20px,-50%);opacity:0}}.avatar-bubble:after{left:auto;right:4px;bottom:50%;width:16px;height:18px;background-size:100% 100%;transform:translate(100%,50%) rotate(-90deg)}.avatar-input-header{padding:8px 12px;font-size:14px}}.speech-bubble{background:#fff;border-radius:40px 40px 0/40px 40px 0px;padding:16px 24px;color:#1e293b;font-family:Inter,system-ui,-apple-system,sans-serif;font-size:16px;font-weight:500;line-height:1.4;max-width:320px;width:max-content;box-shadow:0 10px 25px -5px #0000001a,0 8px 10px -6px #0000001a;pointer-events:none;-webkit-user-select:none;user-select:none;position:relative;text-align:center;z-index:10002;transform:translate(-50%,-100%);margin-bottom:20px}@keyframes bubbleFadeIn{0%{opacity:0;transform:translate(-50%) scale(.9)}to{opacity:1;transform:translate(-50%) scale(1)}}.thinking-dots{display:flex;justify-content:center;align-items:center;gap:4px;height:12px}.thinking-dots span{width:6px;height:6px;background-color:#94a3b8;border-radius:50%;display:inline-block;animation:thinking-dot-pulse 1.4s infinite ease-in-out both}.thinking-dots span:nth-child(1){animation-delay:-.32s}.thinking-dots span:nth-child(2){animation-delay:-.16s}@keyframes thinking-dot-pulse{0%,80%,to{transform:scale(0);opacity:.3}40%{transform:scale(1);opacity:1}}
|