@sage-rsc/talking-head-react 1.0.38 → 1.0.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1 -1
- package/dist/index.js +8 -22
- package/package.json +1 -1
- package/src/components/CurriculumLearning.jsx +29 -43
package/dist/index.cjs
CHANGED
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
${e}
|
|
5
5
|
</voice>
|
|
6
6
|
</speak>
|
|
7
|
-
`,s=await fetch(this.opt.ttsEndpoint,{method:"POST",headers:{"Ocp-Apim-Subscription-Key":this.opt.ttsApikey,"Content-Type":"application/ssml+xml","X-Microsoft-OutputFormat":"audio-16khz-128kbitrate-mono-mp3"},body:n});if(!s.ok)throw new Error(`Azure TTS error: ${s.status} ${s.statusText}`);const o=await s.arrayBuffer(),r=await this.audioCtx.decodeAudioData(o);console.log("Analyzing audio for precise lip-sync...");const h=await this.audioAnalyzer.analyzeAudio(r,e);console.log("Azure TTS Audio Analysis:",{text:e,audioDuration:r.duration,visemeCount:h.visemes.length,wordCount:h.words.length,features:{onsets:h.features.onsets.length,boundaries:h.features.phonemeBoundaries.length}});const a=[];for(let l=0;l<h.visemes.length;l++){const c=h.visemes[l],d=c.startTime*1e3,g=c.duration*1e3,f=c.intensity;a.push({template:{name:"viseme"},ts:[d-Math.min(60,2*g/3),d+Math.min(25,g/2),d+g+Math.min(60,g/2)],vs:{["viseme_"+c.viseme]:[null,f,0]}})}const u=[...t.anim,...a];this.audioPlaylist.push({anim:u,audio:r}),this.onSubtitles=t.onSubtitles||null,this.resetLips(),t.mood&&this.setMood(t.mood),this.playAudio()}async synthesizeWithExternalTTS(t){let e="<speak>";t.text.forEach((o,r)=>{r>0&&(e+=" <mark name='"+o.mark+"'/>"),e+=o.word.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'").replace(new RegExp("^\\p{Dash_Punctuation}$","ug"),'<break time="750ms"/>')}),e+="</speak>";const i={method:"POST",headers:{"Content-Type":"application/json; charset=utf-8"},body:JSON.stringify({input:{ssml:e},voice:{languageCode:t.lang||this.avatar.ttsLang||this.opt.ttsLang,name:t.voice||this.avatar.ttsVoice||this.opt.ttsVoice},audioConfig:{audioEncoding:this.ttsAudioEncoding,speakingRate:(t.rate||this.avatar.ttsRate||this.opt.ttsRate)+this.mood.speech.deltaRate,pitch:(t.pitch||this.avatar.ttsPitch||this.opt.ttsPitch)+this.mood.speech.deltaPitch,volumeGainDb:(t.volume||this.avatar.ttsVolume||this.opt.ttsVolume)+this.mood.speech.deltaVolume},enableTimePointing:[1]})};this.opt.jwtGet&&typeof this.opt.jwtGet=="function"&&(i.headers.Authorization="Bearer "+await this.opt.jwtGet());const n=await fetch(this.opt.ttsEndpoint+(this.opt.ttsApikey?"?key="+this.opt.ttsApikey:""),i),s=await n.json();if(n.status===200&&s&&s.audioContent){const o=this.b64ToArrayBuffer(s.audioContent),r=await this.audioCtx.decodeAudioData(o);this.speakWithHands();const h=[0];let a=0;t.text.forEach((c,d)=>{if(d>0){let g=h[h.length-1];s.timepoints[a]&&(g=s.timepoints[a].timeSeconds*1e3,s.timepoints[a].markName===""+c.mark&&a++),h.push(g)}});const u=[{mark:0,time:0}];h.forEach((c,d)=>{if(d>0){let g=c-h[d-1];u[d-1].duration=g,u.push({mark:d,time:c})}});let l=1e3*r.duration;l>this.opt.ttsTrimEnd&&(l=l-this.opt.ttsTrimEnd),u[u.length-1].duration=l-u[u.length-1].time,t.anim.forEach(c=>{const d=u[c.mark];if(d)for(let g=0;g<c.ts.length;g++)c.ts[g]=d.time+c.ts[g]*d.duration+this.opt.ttsTrimStart}),this.audioPlaylist.push({anim:t.anim,audio:r}),this.onSubtitles=t.onSubtitles||null,this.resetLips(),t.mood&&this.setMood(t.mood),this.playAudio()}else this.startSpeaking(!0)}async startSpeaking(t=!1){if(!(!this.armature||this.isSpeaking&&!t))if(this.stateName="speaking",this.isSpeaking=!0,this.speechQueue.length){let e=this.speechQueue.shift();if(e.emoji){this.lookAtCamera(500);let i=e.emoji.dt.reduce((n,s)=>n+s,0);this.animQueue.push(this.animFactory(e.emoji)),setTimeout(this.startSpeaking.bind(this),i,!0)}else if(e.break)setTimeout(this.startSpeaking.bind(this),e.break,!0);else if(e.audio)e.isRaw||(this.lookAtCamera(500),this.speakWithHands(),this.resetLips()),this.audioPlaylist.push({anim:e.anim,audio:e.audio,isRaw:e.isRaw}),this.onSubtitles=e.onSubtitles||null,e.mood&&this.setMood(e.mood),this.playAudio();else if(e.text){this.lookAtCamera(500);try{!this.opt.ttsEndpoint||this.opt.ttsEndpoint===""?await this.synthesizeWithBrowserTTS(e):this.opt.ttsService==="elevenlabs"?await this.synthesizeWithElevenLabsTTS(e):this.opt.ttsService==="azure"?await this.synthesizeWithAzureTTS(e):await this.synthesizeWithExternalTTS(e)}catch(i){console.error("Error:",i),this.startSpeaking(!0)}}else e.anim?(this.onSubtitles=e.onSubtitles||null,this.resetLips(),e.mood&&this.setMood(e.mood),e.anim.forEach((i,n)=>{for(let s=0;s<i.ts.length;s++)i.ts[s]=this.animClock+10*n;this.animQueue.push(i)}),setTimeout(this.startSpeaking.bind(this),10*e.anim.length,!0)):e.marker?(typeof e.marker=="function"&&e.marker(),this.startSpeaking(!0)):this.startSpeaking(!0)}else this.stateName="idle",this.isSpeaking=!1}pauseSpeaking(){try{this.audioSpeechSource.stop()}catch{}this.audioPlaylist.length=0,this.stateName="idle",this.isSpeaking=!1,this.isAudioPlaying=!1,this.animQueue=this.animQueue.filter(t=>t.template.name!=="viseme"&&t.template.name!=="subtitles"&&t.template.name!=="blendshapes"),this.armature&&(this.resetLips(),this.render())}stopSpeaking(){try{this.audioSpeechSource.stop()}catch{}this.audioPlaylist.length=0,this.speechQueue.length=0,this.animQueue=this.animQueue.filter(t=>t.template.name!=="viseme"&&t.template.name!=="subtitles"&&t.template.name!=="blendshapes"),this.stateName="idle",this.isSpeaking=!1,this.isAudioPlaying=!1,this.armature&&(this.resetLips(),this.render())}async streamStart(t={},e=null,i=null,n=null,s=null){if(this.stopSpeaking(),this.isStreaming=!0,t.waitForAudioChunks!==void 0&&(this.streamWaitForAudioChunks=t.waitForAudioChunks),this.streamWaitForAudioChunks||(this.streamAudioStartTime=this.animClock),this.streamLipsyncQueue=[],this.streamLipsyncType=t.lipsyncType||this.streamLipsyncType||"visemes",this.streamLipsyncLang=t.lipsyncLang||this.streamLipsyncLang||this.avatar.lipsyncLang||this.opt.lipsyncLang,this.onAudioStart=e,this.onAudioEnd=i,this.onMetrics=s,t.sampleRate!==void 0){const r=t.sampleRate;typeof r=="number"&&r>=8e3&&r<=96e3?r!==this.audioCtx.sampleRate&&this.initAudioGraph(r):console.warn("Invalid sampleRate provided. It must be a number between 8000 and 96000 Hz.")}if(t.gain!==void 0&&(this.audioStreamGainNode.gain.value=t.gain),!this.streamWorkletNode||!this.streamWorkletNode.port||this.streamWorkletNode.numberOfOutputs===0||this.streamWorkletNode.context!==this.audioCtx){if(this.streamWorkletNode)try{this.streamWorkletNode.disconnect(),this.streamWorkletNode=null}catch{}if(!this.workletLoaded)try{const r=this.audioCtx.audioWorklet.addModule(Qe.href),h=new Promise((a,u)=>setTimeout(()=>u(new Error("Worklet loading timed out")),5e3));await Promise.race([r,h]),this.workletLoaded=!0}catch(r){throw console.error("Failed to load audio worklet:",r),new Error("Failed to initialize streaming speech")}this.streamWorkletNode=new AudioWorkletNode(this.audioCtx,"playback-worklet",{processorOptions:{sampleRate:this.audioCtx.sampleRate,metrics:t.metrics||{enabled:!1}}}),this.streamWorkletNode.connect(this.audioStreamGainNode),this.streamWorkletNode.connect(this.audioAnalyzerNode),this.streamWorkletNode.port.onmessage=r=>{if(r.data.type==="playback-started"&&(this.isSpeaking=!0,this.stateName="speaking",this.streamWaitForAudioChunks&&(this.streamAudioStartTime=this.animClock),this._processStreamLipsyncQueue(),this.speakWithHands(),this.onAudioStart))try{this.onAudioStart?.()}catch(h){console.error(h)}if(r.data.type==="playback-ended"&&(this._streamPause(),this.onAudioEnd))try{this.onAudioEnd()}catch{}if(this.onMetrics&&r.data.type==="metrics")try{this.onMetrics(r.data)}catch{}}}if(t.metrics)try{this.streamWorkletNode.port.postMessage({type:"config-metrics",data:t.metrics})}catch{}if(this.resetLips(),this.lookAtCamera(500),t.mood&&this.setMood(t.mood),this.onSubtitles=n||null,this.audioCtx.state==="suspended"||this.audioCtx.state==="interrupted"){const r=this.audioCtx.resume(),h=new Promise((a,u)=>setTimeout(()=>u("p2"),1e3));try{await Promise.race([r,h])}catch{console.warn("Can't play audio. Web Audio API suspended. This is often due to calling some speak method before the first user action, which is typically prevented by the browser.");return}}}streamNotifyEnd(){!this.isStreaming||!this.streamWorkletNode||this.streamWorkletNode.port.postMessage({type:"no-more-data"})}streamInterrupt(){if(!this.isStreaming)return;const t=this.isSpeaking;if(this.streamWorkletNode)try{this.streamWorkletNode.port.postMessage({type:"stop"})}catch{}if(this._streamPause(!0),t&&this.onAudioEnd)try{this.onAudioEnd()}catch{}}streamStop(){if(this.isStreaming){if(this.streamInterrupt(),this.streamWorkletNode){try{this.streamWorkletNode.disconnect()}catch{}this.streamWorkletNode=null}this.isStreaming=!1}}_streamPause(t=!1){this.isSpeaking=!1,this.stateName="idle",t&&(this.streamWaitForAudioChunks&&(this.streamAudioStartTime=null),this.streamLipsyncQueue=[],this.animQueue=this.animQueue.filter(e=>e.template.name!=="viseme"&&e.template.name!=="subtitles"&&e.template.name!=="blendshapes"),this.armature&&(this.resetLips(),this.render()))}_processStreamLipsyncQueue(){if(this.isStreaming)for(;this.streamLipsyncQueue.length>0;){const t=this.streamLipsyncQueue.shift();this._processLipsyncData(t,this.streamAudioStartTime)}}_processLipsyncData(t,e){if(this.isStreaming){if(t.visemes&&this.streamLipsyncType=="visemes")for(let i=0;i<t.visemes.length;i++){const n=t.visemes[i],s=e+t.vtimes[i],o=t.vdurations[i],r={template:{name:"viseme"},ts:[s-2*o/3,s+o/2,s+o+o/2],vs:{["viseme_"+n]:[null,n==="PP"||n==="FF"?.9:.6,0]}};this.animQueue.push(r)}if(t.words&&(this.onSubtitles||this.streamLipsyncType=="words"))for(let i=0;i<t.words.length;i++){const n=t.words[i],s=t.wtimes[i];let o=t.wdurations[i];if(n.length&&(this.onSubtitles&&this.animQueue.push({template:{name:"subtitles"},ts:[e+s],vs:{subtitles:[" "+n]}}),this.streamLipsyncType=="words")){const r=this.streamLipsyncLang||this.avatar.lipsyncLang||this.opt.lipsyncLang,h=this.lipsyncPreProcessText(n,r),a=this.lipsyncWordsToVisemes(h,r);if(a&&a.visemes&&a.visemes.length){const u=a.times[a.visemes.length-1]+a.durations[a.visemes.length-1],l=Math.min(o,Math.max(0,o-a.visemes.length*150));let c=.6+this.convertRange(l,[0,o],[0,.4]);if(o=Math.min(o,a.visemes.length*200),u>0)for(let d=0;d<a.visemes.length;d++){const g=e+s+a.times[d]/u*o,f=a.durations[d]/u*o;this.animQueue.push({template:{name:"viseme"},ts:[g-Math.min(60,2*f/3),g+Math.min(25,f/2),g+f+Math.min(60,f/2)],vs:{["viseme_"+a.visemes[d]]:[null,a.visemes[d]==="PP"||a.visemes[d]==="FF"?.9:c,0]}})}}}}if(t.anims&&this.streamLipsyncType=="blendshapes")for(let i=0;i<t.anims.length;i++){let n=t.anims[i];n.delay+=e;let s=this.animFactory(n,!1,1,1,!0);this.animQueue.push(s)}}}streamAudio(t){if(!(!this.isStreaming||!this.streamWorkletNode)){if(this.isSpeaking||(this.streamLipsyncQueue=[],this.streamAudioStartTime=null),this.isSpeaking=!0,this.stateName="speaking",t.audio!==void 0){const e={type:"audioData",data:null};if(t.audio instanceof ArrayBuffer)e.data=t.audio,this.streamWorkletNode.port.postMessage(e,[e.data]);else if(t.audio instanceof Int16Array||t.audio instanceof Uint8Array){const i=t.audio.buffer.slice(t.audio.byteOffset,t.audio.byteOffset+t.audio.byteLength);e.data=i,this.streamWorkletNode.port.postMessage(e,[e.data])}else if(t.audio instanceof Float32Array){const i=new Int16Array(t.audio.length);for(let n=0;n<t.audio.length;n++){let s=Math.max(-1,Math.min(1,t.audio[n]));i[n]=s<0?s*32768:s*32767}e.data=i.buffer,this.streamWorkletNode.port.postMessage(e,[e.data])}else console.error("r.audio is not a supported type. Must be ArrayBuffer, Int16Array, Uint8Array, or Float32Array:",t.audio)}if(t.visemes||t.anims||t.words){if(this.streamWaitForAudioChunks&&!this.streamAudioStartTime){this.streamLipsyncQueue.length>=200&&this.streamLipsyncQueue.shift(),this.streamLipsyncQueue.push(t);return}else!this.streamWaitForAudioChunks&&!this.streamAudioStartTime&&(this.streamAudioStartTime=this.animClock);this._processLipsyncData(t,this.streamAudioStartTime)}}}makeEyeContact(t){this.animQueue.push(this.animFactory({name:"eyecontact",dt:[0,t],vs:{eyeContact:[1]}}))}lookAhead(t){if(t){let e=(Math.random()-.5)/4,i=(Math.random()-.5)/4,n=this.animQueue.findIndex(o=>o.template.name==="lookat");n!==-1&&this.animQueue.splice(n,1);const s={name:"lookat",dt:[750,t],vs:{bodyRotateX:[e],bodyRotateY:[i],eyesRotateX:[-3*e+.1],eyesRotateY:[-5*i],browInnerUp:[[0,.7]],mouthLeft:[[0,.7]],mouthRight:[[0,.7]],eyeContact:[0],headMove:[0]}};this.animQueue.push(this.animFactory(s))}}lookAtCamera(t){let e;if(this.speakTo&&(e=new y.Vector3,this.speakTo.objectLeftEye?.isObject3D?(this.speakTo.armature.objectHead,this.speakTo.objectLeftEye.updateMatrixWorld(!0),this.speakTo.objectRightEye.updateMatrixWorld(!0),ie.setFromMatrixPosition(this.speakTo.objectLeftEye.matrixWorld),oe.setFromMatrixPosition(this.speakTo.objectRightEye.matrixWorld),e.addVectors(ie,oe).divideScalar(2)):this.speakTo.isObject3D?this.speakTo.getWorldPosition(e):this.speakTo.isVector3?e.set(this.speakTo):this.speakTo.x&&this.speakTo.y&&this.speakTo.z&&e.set(this.speakTo.x,this.speakTo.y,this.speakTo.z)),!e){if(this.avatar.hasOwnProperty("avatarIgnoreCamera")){if(this.avatar.avatarIgnoreCamera){this.lookAhead(t);return}}else if(this.opt.avatarIgnoreCamera){this.lookAhead(t);return}this.lookAt(null,null,t);return}this.objectLeftEye.updateMatrixWorld(!0),this.objectRightEye.updateMatrixWorld(!0),ie.setFromMatrixPosition(this.objectLeftEye.matrixWorld),oe.setFromMatrixPosition(this.objectRightEye.matrixWorld),ie.add(oe).divideScalar(2),N.copy(this.armature.quaternion),N.multiply(this.poseTarget.props["Hips.quaternion"]),N.multiply(this.poseTarget.props["Spine.quaternion"]),N.multiply(this.poseTarget.props["Spine1.quaternion"]),N.multiply(this.poseTarget.props["Spine2.quaternion"]),N.multiply(this.poseTarget.props["Neck.quaternion"]),N.multiply(this.poseTarget.props["Head.quaternion"]);const i=new y.Vector3().subVectors(e,ie).normalize(),n=Math.atan2(i.x,i.z),s=Math.asin(-i.y);E.set(s,n,0,"YXZ");const r=new y.Quaternion().setFromEuler(E),h=new y.Quaternion().copy(r).multiply(N.clone().invert());E.setFromQuaternion(h,"YXZ");let a=E.x/(40/24)+.2,u=E.y/(9/4),l=Math.min(.6,Math.max(-.3,a)),c=Math.min(.8,Math.max(-.8,u)),d=(Math.random()-.5)/4,g=(Math.random()-.5)/4;if(t){let f=this.animQueue.findIndex(I=>I.template.name==="lookat");f!==-1&&this.animQueue.splice(f,1);const x={name:"lookat",dt:[750,t],vs:{bodyRotateX:[l+d],bodyRotateY:[c+g],eyesRotateX:[-3*d+.1],eyesRotateY:[-5*g],browInnerUp:[[0,.7]],mouthLeft:[[0,.7]],mouthRight:[[0,.7]],eyeContact:[0],headMove:[0]}};this.animQueue.push(this.animFactory(x))}}lookAt(t,e,i){if(!this.camera)return;const n=this.nodeAvatar.getBoundingClientRect();this.objectLeftEye.updateMatrixWorld(!0),this.objectRightEye.updateMatrixWorld(!0);const s=new y.Vector3().setFromMatrixPosition(this.objectLeftEye.matrixWorld),o=new y.Vector3().setFromMatrixPosition(this.objectRightEye.matrixWorld),r=new y.Vector3().addVectors(s,o).divideScalar(2);r.project(this.camera);let h=(r.x+1)/2*n.width+n.left,a=-(r.y-1)/2*n.height+n.top;t===null&&(t=h),e===null&&(e=a),N.copy(this.armature.quaternion),N.multiply(this.poseTarget.props["Hips.quaternion"]),N.multiply(this.poseTarget.props["Spine.quaternion"]),N.multiply(this.poseTarget.props["Spine1.quaternion"]),N.multiply(this.poseTarget.props["Spine2.quaternion"]),N.multiply(this.poseTarget.props["Neck.quaternion"]),N.multiply(this.poseTarget.props["Head.quaternion"]),E.setFromQuaternion(N);let u=E.x/(40/24),l=E.y/(9/4),c=Math.min(.4,Math.max(-.4,this.camera.rotation.x)),d=Math.min(.4,Math.max(-.4,this.camera.rotation.y)),g=Math.max(window.innerWidth-h,h),f=Math.max(window.innerHeight-a,a),x=this.convertRange(e,[a-f,a+f],[-.3,.6])-u+c,I=this.convertRange(t,[h-g,h+g],[-.8,.8])-l+d;x=Math.min(.6,Math.max(-.3,x)),I=Math.min(.8,Math.max(-.8,I));let M=(Math.random()-.5)/4,p=(Math.random()-.5)/4;if(i){let F=this.animQueue.findIndex(v=>v.template.name==="lookat");F!==-1&&this.animQueue.splice(F,1);const C={name:"lookat",dt:[750,i],vs:{bodyRotateX:[x+M],bodyRotateY:[I+p],eyesRotateX:[-3*M+.1],eyesRotateY:[-5*p],browInnerUp:[[0,.7]],mouthLeft:[[0,.7]],mouthRight:[[0,.7]],eyeContact:[0],headMove:[0]}};this.animQueue.push(this.animFactory(C))}}touchAt(t,e){if(!this.camera)return;const i=this.nodeAvatar.getBoundingClientRect(),n=new y.Vector2((t-i.left)/i.width*2-1,-((e-i.top)/i.height)*2+1),s=new y.Raycaster;s.setFromCamera(n,this.camera);const o=s.intersectObject(this.armature);if(o.length>0){const r=o[0].point,h=new y.Vector3,a=new y.Vector3;this.objectLeftArm.getWorldPosition(h),this.objectRightArm.getWorldPosition(a);const u=h.distanceToSquared(r),l=a.distanceToSquared(r);u<l?(this.ikSolve({iterations:20,root:"LeftShoulder",effector:"LeftHandMiddle1",links:[{link:"LeftHand",minx:-.5,maxx:.5,miny:-1,maxy:1,minz:-.5,maxz:.5,maxAngle:.1},{link:"LeftForeArm",minx:-.5,maxx:1.5,miny:-1.5,maxy:1.5,minz:-.5,maxz:3,maxAngle:.2},{link:"LeftArm",minx:-1.5,maxx:1.5,miny:0,maxy:0,minz:-1,maxz:3}]},r,!1,1e3),this.setValue("handFistLeft",0)):(this.ikSolve({iterations:20,root:"RightShoulder",effector:"RightHandMiddle1",links:[{link:"RightHand",minx:-.5,maxx:.5,miny:-1,maxy:1,minz:-.5,maxz:.5,maxAngle:.1},{link:"RightForeArm",minx:-.5,maxx:1.5,miny:-1.5,maxy:1.5,minz:-3,maxz:.5,maxAngle:.2},{link:"RightArm",minx:-1.5,maxx:1.5,miny:0,maxy:0,minz:-1,maxz:3}]},r,!1,1e3),this.setValue("handFistRight",0))}else["LeftArm","LeftForeArm","LeftHand","RightArm","RightForeArm","RightHand"].forEach(r=>{let h=r+".quaternion";this.poseTarget.props[h].copy(this.getPoseTemplateProp(h)),this.poseTarget.props[h].t=this.animClock,this.poseTarget.props[h].d=1e3});return o.length>0}speakWithHands(t=0,e=.5){if(this.mixer||this.gesture||!this.poseTarget.template.standing||this.poseTarget.template.bend||Math.random()>e)return;this.ikSolve({root:"LeftShoulder",effector:"LeftHandMiddle1",links:[{link:"LeftHand",minx:-.5,maxx:.5,miny:-1,maxy:1,minz:-.5,maxz:.5},{link:"LeftForeArm",minx:-.5,maxx:1.5,miny:-1.5,maxy:1.5,minz:-.5,maxz:3},{link:"LeftArm",minx:-1.5,maxx:1.5,miny:-1.5,maxy:1.5,minz:-1,maxz:3}]},new y.Vector3(this.gaussianRandom(0,.5),this.gaussianRandom(-.8,-.2),this.gaussianRandom(0,.5)),!0),this.ikSolve({root:"RightShoulder",effector:"RightHandMiddle1",links:[{link:"RightHand",minx:-.5,maxx:.5,miny:-1,maxy:1,minz:-.5,maxz:.5},{link:"RightForeArm",minx:-.5,maxx:1.5,miny:-1.5,maxy:1.5,minz:-3,maxz:.5},{link:"RightArm"}]},new y.Vector3(this.gaussianRandom(-.5,0),this.gaussianRandom(-.8,-.2),this.gaussianRandom(0,.5)),!0);const i=[],n=[];i.push(100+Math.round(Math.random()*500)),n.push({duration:1e3,props:{"LeftHand.quaternion":new y.Quaternion().setFromEuler(new y.Euler(0,-1-Math.random(),0)),"RightHand.quaternion":new y.Quaternion().setFromEuler(new y.Euler(0,1+Math.random(),0))}}),["LeftArm","LeftForeArm","RightArm","RightForeArm"].forEach(o=>{n[0].props[o+".quaternion"]=this.ikMesh.getObjectByName(o).quaternion.clone()}),i.push(1e3+Math.round(Math.random()*500)),n.push({duration:2e3,props:{}}),["LeftArm","LeftForeArm","RightArm","RightForeArm","LeftHand","RightHand"].forEach(o=>{n[1].props[o+".quaternion"]=null});const s=this.animFactory({name:"talkinghands",delay:t,dt:i,vs:{moveto:n}});this.animQueue.push(s)}getSlowdownRate(t){return this.animSlowdownRate}setSlowdownRate(t){this.animSlowdownRate=t,this.audioSpeechSource.playbackRate.value=1/this.animSlowdownRate,this.audioBackgroundSource.playbackRate.value=1/this.animSlowdownRate}getAutoRotateSpeed(t){return this.controls.autoRotateSpeed}setAutoRotateSpeed(t){this.controls.autoRotateSpeed=t,this.controls.autoRotate=t>0}start(){this.armature&&this.isRunning===!1&&(this.audioCtx.resume(),this.animTimeLast=performance.now(),this.isRunning=!0,this.isAvatarOnly||requestAnimationFrame(this.animate.bind(this)))}stop(){this.isRunning=!1,this.audioCtx.suspend()}startListening(t,e={},i=null){this.listeningAnalyzer=t,this.listeningAnalyzer.fftSize=256,this.listeningAnalyzer.smoothingTimeConstant=.1,this.listeningAnalyzer.minDecibels=-70,this.listeningAnalyzer.maxDecibels=-10,this.listeningOnchange=i&&typeof i=="function"?i:null,this.listeningSilenceThresholdLevel=e?.hasOwnProperty("listeningSilenceThresholdLevel")?e.listeningSilenceThresholdLevel:this.opt.listeningSilenceThresholdLevel,this.listeningSilenceThresholdMs=e?.hasOwnProperty("listeningSilenceThresholdMs")?e.listeningSilenceThresholdMs:this.opt.listeningSilenceThresholdMs,this.listeningSilenceDurationMax=e?.hasOwnProperty("listeningSilenceDurationMax")?e.listeningSilenceDurationMax:this.opt.listeningSilenceDurationMax,this.listeningActiveThresholdLevel=e?.hasOwnProperty("listeningActiveThresholdLevel")?e.listeningActiveThresholdLevel:this.opt.listeningActiveThresholdLevel,this.listeningActiveThresholdMs=e?.hasOwnProperty("listeningActiveThresholdMs")?e.listeningActiveThresholdMs:this.opt.listeningActiveThresholdMs,this.listeningActiveDurationMax=e?.hasOwnProperty("listeningActiveDurationMax")?e.listeningActiveDurationMax:this.opt.listeningActiveDurationMax,this.listeningActive=!1,this.listeningVolume=0,this.listeningTimer=0,this.listeningTimerTotal=0,this.isListening=!0}stopListening(){this.isListening=!1}async playAnimation(t,e=null,i=10,n=0,s=.01,o=!1){if(!this.armature)return;this.positionWasLocked=!o,o?console.log("Position locking disabled for FBX animation:",t):(this.lockAvatarPosition(),console.log("Position locked immediately before FBX animation:",t));let r=this.animClips.find(h=>h.url===t+"-"+n);if(r){let h=this.animQueue.find(l=>l.template.name==="pose");h&&(h.ts[0]=1/0),Object.entries(r.pose.props).forEach(l=>{this.poseBase.props[l[0]]=l[1].clone(),this.poseTarget.props[l[0]]=l[1].clone(),this.poseTarget.props[l[0]].t=0,this.poseTarget.props[l[0]].d=1e3}),this.mixer?console.log("Using existing mixer for FBX animation, preserving morph targets"):(this.mixer=new y.AnimationMixer(this.armature),console.log("Created new mixer for FBX animation")),this.mixer.addEventListener("finished",this.stopAnimation.bind(this),{once:!0});const a=Math.ceil(i/r.clip.duration),u=this.mixer.clipAction(r.clip);u.setLoop(y.LoopRepeat,a),u.clampWhenFinished=!0,this.currentFBXAction=u;try{u.fadeIn(.5).play(),console.log("FBX animation started successfully:",t)}catch(l){console.warn("FBX animation failed to start:",l),this.stopAnimation();return}if(u.getClip().tracks.length===0){console.warn("FBX animation has no valid tracks, stopping"),this.stopAnimation();return}}else{if(t.split(".").pop().toLowerCase()!=="fbx"){console.error(`Invalid file type for FBX animation: ${t}. Expected .fbx file.`);return}let a=!1;try{const c=await fetch(t,{method:"HEAD"});if(a=c.ok,!a){console.error(`FBX file not found at ${t}. Status: ${c.status}`),console.error("Please check:"),console.error("1. File path is correct (note: path is case-sensitive)"),console.error("2. File exists in your public folder"),console.error("3. File is accessible (not blocked by server)");return}}catch(c){console.warn(`Could not verify file existence for ${t}, attempting to load anyway:`,c)}const u=new fe.FBXLoader;let l;try{l=await u.loadAsync(t,e)}catch(c){console.error(`Failed to load FBX animation from ${t}:`,c),console.error("Error details:",{message:c.message,url:t,suggestion:"Make sure the file is a valid FBX file and the path is correct"}),c.message&&c.message.includes("version number")&&(console.error("FBX Loader Error: Cannot find version number"),console.error("This error usually means:"),console.error("1. The file is not a valid FBX file (might be GLB, corrupted, or wrong format)"),console.error("2. The file might be corrupted"),console.error("3. The file path might be incorrect"),console.error("4. The server returned an HTML error page instead of the FBX file"),console.error("5. The file might not exist at that path"),console.error(""),console.error("Solution: Please verify:"),console.error(` - File exists at: ${t}`),console.error(" - File is a valid FBX binary file"),console.error(" - File path matches your public folder structure"),console.error(" - File is not corrupted"));try{const d=await fetch(t),g=d.headers.get("content-type"),f=await d.text();console.error("Response details:",{status:d.status,contentType:g,firstBytes:f.substring(0,100),isHTML:f.trim().startsWith("<!DOCTYPE")||f.trim().startsWith("<html")}),(f.trim().startsWith("<!DOCTYPE")||f.trim().startsWith("<html"))&&console.error("The server returned an HTML page instead of an FBX file. The file path is likely incorrect.")}catch(d){console.error("Could not fetch file for debugging:",d)}return}if(l&&l.animations&&l.animations[n]){let c=l.animations[n];const d={};c.tracks.forEach(f=>{f.name=f.name.replaceAll("mixamorig","");const x=f.name.split(".");if(x[1]==="position"){for(let I=0;I<f.values.length;I++)f.values[I]=f.values[I]*s;d[f.name]=new y.Vector3(f.values[0],f.values[1],f.values[2])}else x[1]==="quaternion"?d[f.name]=new y.Quaternion(f.values[0],f.values[1],f.values[2],f.values[3]):x[1]==="rotation"&&(d[x[0]+".quaternion"]=new y.Quaternion().setFromEuler(new y.Euler(f.values[0],f.values[1],f.values[2],"XYZ")).normalize())});const g={props:d};d["Hips.position"]&&(d["Hips.position"].y<.5?g.lying=!0:g.standing=!0),this.animClips.push({url:t+"-"+n,clip:c,pose:g}),this.playAnimation(t,e,i,n,s)}else{const c="Animation "+t+" (ndx="+n+") not found";console.error(c),l&&l.animations?console.error(`FBX file loaded but has ${l.animations.length} animation(s), requested index ${n}`):console.error(l?"FBX file loaded but contains no animations":"FBX file failed to load or is invalid")}}}stopAnimation(){if(this.currentFBXAction&&(this.currentFBXAction.stop(),this.currentFBXAction=null,console.log("FBX animation action stopped, mixer preserved for lip-sync")),this.mixer&&this.mixer._actions.length===0&&(this.mixer=null,console.log("Mixer destroyed as no actions remain")),this.positionWasLocked?(this.unlockAvatarPosition(),console.log("Position unlocked after FBX animation stopped")):console.log("Position was not locked, no unlock needed"),this.gesture)for(let[e,i]of Object.entries(this.gesture))i.t=this.animClock,i.d=1e3,this.poseTarget.props.hasOwnProperty(e)&&(this.poseTarget.props[e].copy(i),this.poseTarget.props[e].t=this.animClock,this.poseTarget.props[e].d=1e3);let t=this.animQueue.find(e=>e.template.name==="pose");t&&(t.ts[0]=this.animClock),this.setPoseFromTemplate(null)}async playPose(t,e=null,i=5,n=0,s=.01){if(!this.armature)return;let o=this.poseTemplates[t];if(!o){const r=this.animPoses.find(h=>h.url===t+"-"+n);r&&(o=r.pose)}if(o){this.poseName=t,this.mixer=null;let r=this.animQueue.find(h=>h.template.name==="pose");r&&(r.ts[0]=this.animClock+i*1e3+2e3),this.setPoseFromTemplate(o)}else{let h=await new fe.FBXLoader().loadAsync(t,e);if(h&&h.animations&&h.animations[n]){let a=h.animations[n];const u={};a.tracks.forEach(c=>{c.name=c.name.replaceAll("mixamorig","");const d=c.name.split(".");d[1]==="position"?u[c.name]=new y.Vector3(c.values[0]*s,c.values[1]*s,c.values[2]*s):d[1]==="quaternion"?u[c.name]=new y.Quaternion(c.values[0],c.values[1],c.values[2],c.values[3]):d[1]==="rotation"&&(u[d[0]+".quaternion"]=new y.Quaternion().setFromEuler(new y.Euler(c.values[0],c.values[1],c.values[2],"XYZ")).normalize())});const l={props:u};u["Hips.position"]&&(u["Hips.position"].y<.5?l.lying=!0:l.standing=!0),this.animPoses.push({url:t+"-"+n,pose:l}),this.playPose(t,e,i,n,s)}else{const a="Pose "+t+" (ndx="+n+") not found";console.error(a)}}}stopPose(){this.stopAnimation()}playGesture(t,e=3,i=!1,n=1e3){if(!this.armature)return;let s=this.gestureTemplates[t];if(s){this.gestureTimeout&&(clearTimeout(this.gestureTimeout),this.gestureTimeout=null);let r=this.animQueue.findIndex(h=>h.template.name==="talkinghands");r!==-1&&(this.animQueue[r].ts=this.animQueue[r].ts.map(h=>0)),this.gesture=this.propsToThreeObjects(s),i&&(this.gesture=this.mirrorPose(this.gesture)),t==="namaste"&&this.avatar.body==="M"&&(this.gesture["RightArm.quaternion"].rotateTowards(new y.Quaternion(0,1,0,0),-.25),this.gesture["LeftArm.quaternion"].rotateTowards(new y.Quaternion(0,1,0,0),-.25));for(let[h,a]of Object.entries(this.gesture))a.t=this.animClock,a.d=n,this.poseTarget.props.hasOwnProperty(h)&&(this.poseTarget.props[h].copy(a),this.poseTarget.props[h].t=this.animClock,this.poseTarget.props[h].d=n);e&&Number.isFinite(e)&&(this.gestureTimeout=setTimeout(this.stopGesture.bind(this,n),1e3*e))}let o=this.animEmojis[t];if(o&&(o&&o.link&&(o=this.animEmojis[o.link]),o)){this.lookAtCamera(500);const r=this.animFactory(o);if(r.gesture=!0,e&&Number.isFinite(e)){const h=r.ts[0],u=r.ts[r.ts.length-1]-h;if(e*1e3-u>0){const c=[];for(let f=1;f<r.ts.length;f++)c.push(r.ts[f]-r.ts[f-1]);const d=o.template?.rescale||c.map(f=>f/u),g=e*1e3-u;r.ts=r.ts.map((f,x,I)=>x===0?h:I[x-1]+c[x-1]+d[x-1]*g)}else{const c=e*1e3/u;r.ts=r.ts.map(d=>h+c*(d-h))}}this.animQueue.push(r)}}stopGesture(t=1e3){if(this.gestureTimeout&&(clearTimeout(this.gestureTimeout),this.gestureTimeout=null),this.gesture){const i=Object.entries(this.gesture);this.gesture=null;for(const[n,s]of i)this.poseTarget.props.hasOwnProperty(n)&&(this.poseTarget.props[n].copy(this.getPoseTemplateProp(n)),this.poseTarget.props[n].t=this.animClock,this.poseTarget.props[n].d=t)}let e=this.animQueue.findIndex(i=>i.gesture);e!==-1&&this.animQueue.splice(e,1)}ikSolve(t,e=null,i=!1,n=null){const s=new y.Vector3,o=new y.Vector3,r=new y.Vector3,h=new y.Vector3,a=new y.Quaternion,u=new y.Vector3,l=new y.Vector3,c=new y.Vector3,d=this.ikMesh.getObjectByName(t.root);d.position.setFromMatrixPosition(this.armature.getObjectByName(t.root).matrixWorld),d.quaternion.setFromRotationMatrix(this.armature.getObjectByName(t.root).matrixWorld),e&&i&&e.applyQuaternion(this.armature.quaternion).add(d.position);const g=this.ikMesh.getObjectByName(t.effector),f=t.links;f.forEach(I=>{I.bone=this.ikMesh.getObjectByName(I.link),I.bone.quaternion.copy(this.getPoseTemplateProp(I.link+".quaternion"))}),d.updateMatrixWorld(!0);const x=t.iterations||10;if(e)for(let I=0;I<x;I++){let M=!1;for(let p=0,F=f.length;p<F;p++){const C=f[p].bone;C.matrixWorld.decompose(h,a,u),a.invert(),o.setFromMatrixPosition(g.matrixWorld),r.subVectors(o,h),r.applyQuaternion(a),r.normalize(),s.subVectors(e,h),s.applyQuaternion(a),s.normalize();let v=s.dot(r);v>1?v=1:v<-1&&(v=-1),v=Math.acos(v),!(v<1e-5)&&(f[p].minAngle!==void 0&&v<f[p].minAngle&&(v=f[p].minAngle),f[p].maxAngle!==void 0&&v>f[p].maxAngle&&(v=f[p].maxAngle),l.crossVectors(r,s),l.normalize(),N.setFromAxisAngle(l,v),C.quaternion.multiply(N),C.rotation.setFromVector3(c.setFromEuler(C.rotation).clamp(new y.Vector3(f[p].minx!==void 0?f[p].minx:-1/0,f[p].miny!==void 0?f[p].miny:-1/0,f[p].minz!==void 0?f[p].minz:-1/0),new y.Vector3(f[p].maxx!==void 0?f[p].maxx:1/0,f[p].maxy!==void 0?f[p].maxy:1/0,f[p].maxz!==void 0?f[p].maxz:1/0))),C.updateMatrixWorld(!0),M=!0)}if(!M)break}n&&f.forEach(I=>{this.poseTarget.props[I.link+".quaternion"].copy(I.bone.quaternion),this.poseTarget.props[I.link+".quaternion"].t=this.animClock,this.poseTarget.props[I.link+".quaternion"].d=n})}dispose(){this.isRunning=!1,this.stop(),this.stopSpeaking(),this.streamStop(),this.isAvatarOnly?this.armature&&(this.armature.parent&&this.armature.parent.remove(this.armature),this.clearThree(this.armature)):(this.clearThree(this.scene),this.resizeobserver.disconnect(),this.renderer&&(this.renderer.dispose(),this.renderer.domElement&&this.renderer.domElement.parentNode&&this.renderer.domElement.parentNode.removeChild(this.renderer.domElement),this.renderer=null)),this.clearThree(this.ikMesh),this.dynamicbones.dispose()}}const se={apiKey:"sk_ace57ef3ef65a92b9d3bee2a00183b78ca790bc3e10964f2",endpoint:"https://api.elevenlabs.io/v1/text-to-speech",defaultVoice:"21m00Tcm4TlvDq8ikWAM",voices:{rachel:"21m00Tcm4TlvDq8ikWAM",drew:"29vD33N1CtxCmqQRPOHJ",bella:"EXAVITQu4vr4xnSDxMaL",antoni:"ErXwobaYiN019PkySvjV",elli:"MF3mGyEYCl7XYWbV9V6O",josh:"VR6AewLTigWG4xSOukaG"}};function ue(){return{service:"elevenlabs",endpoint:se.endpoint,apiKey:se.apiKey,defaultVoice:se.defaultVoice,voices:se.voices}}function _e(){const T=ue(),t=[];return Object.entries(T.voices).forEach(([e,i])=>{t.push({value:i,label:`${e.charAt(0).toUpperCase()+e.slice(1)} (${T.service})`})}),t}const ge=A.forwardRef(({avatarUrl:T="/avatars/brunette.glb",avatarBody:t="F",mood:e="neutral",ttsLang:i="en",ttsService:n=null,ttsVoice:s=null,ttsApiKey:o=null,bodyMovement:r="idle",movementIntensity:h=.5,showFullAvatar:a=!0,cameraView:u="upper",onReady:l=()=>{},onLoading:c=()=>{},onError:d=()=>{},className:g="",style:f={},animations:x={}},I)=>{const M=A.useRef(null),p=A.useRef(null),[F,C]=A.useState(!0),[v,O]=A.useState(null),[B,_]=A.useState(!1),K=ue(),S=n||K.service;let P;S==="browser"?P={service:"browser",endpoint:"",apiKey:null,defaultVoice:"Google US English"}:S==="elevenlabs"?P={service:"elevenlabs",endpoint:"https://api.elevenlabs.io/v1/text-to-speech",apiKey:o||K.apiKey,defaultVoice:s||K.defaultVoice||se.defaultVoice,voices:K.voices||se.voices}:P={...K,apiKey:o!==null?o:K.apiKey};const W={url:T,body:t,avatarMood:e,ttsLang:S==="browser"?"en-US":i,ttsVoice:s||P.defaultVoice,lipsyncLang:"en",showFullAvatar:a,bodyMovement:r,movementIntensity:h},V={ttsEndpoint:P.endpoint,ttsApikey:P.apiKey,ttsService:S,lipsyncModules:["en"],cameraView:u},Y=A.useCallback(async()=>{if(!(!M.current||p.current))try{if(C(!0),O(null),p.current=new ve(M.current,V),p.current.controls&&(p.current.controls.enableRotate=!1,p.current.controls.enableZoom=!1,p.current.controls.enablePan=!1,p.current.controls.enableDamping=!1),x&&Object.keys(x).length>0&&(p.current.customAnimations=x),await p.current.showAvatar(W,U=>{if(U.lengthComputable){const D=Math.min(100,Math.round(U.loaded/U.total*100));c(D)}}),await new Promise(U=>{const D=()=>{p.current.lipsync&&Object.keys(p.current.lipsync).length>0?U():setTimeout(D,100)};D()}),p.current&&p.current.setShowFullAvatar)try{p.current.setShowFullAvatar(a)}catch(U){console.warn("Error setting full body mode on initialization:",U)}p.current&&p.current.controls&&(p.current.controls.enableRotate=!1,p.current.controls.enableZoom=!1,p.current.controls.enablePan=!1,p.current.controls.enableDamping=!1,p.current.controls.update()),C(!1),_(!0),l(p.current);const H=()=>{document.visibilityState==="visible"?p.current?.start():p.current?.stop()};return document.addEventListener("visibilitychange",H),()=>{document.removeEventListener("visibilitychange",H)}}catch(k){console.error("Error initializing TalkingHead:",k),O(k.message||"Failed to initialize avatar"),C(!1),d(k)}},[T,t,e,i,n,s,o,a,r,h,u]);A.useEffect(()=>(Y(),()=>{p.current&&(p.current.stop(),p.current.dispose(),p.current=null)}),[Y]),A.useEffect(()=>{if(!M.current||!p.current)return;const k=new ResizeObserver(U=>{for(const D of U)p.current&&p.current.onResize&&p.current.onResize()});k.observe(M.current);const H=()=>{p.current&&p.current.onResize&&p.current.onResize()};return window.addEventListener("resize",H),()=>{k.disconnect(),window.removeEventListener("resize",H)}},[B]);const j=A.useCallback(async()=>{if(p.current&&p.current.audioCtx)try{(p.current.audioCtx.state==="suspended"||p.current.audioCtx.state==="interrupted")&&(await p.current.audioCtx.resume(),console.log("Audio context resumed"))}catch(k){console.warn("Failed to resume audio context:",k)}},[]),ae=A.useCallback(async(k,H={})=>{if(p.current&&B)try{await j();const U={...H,lipsyncLang:H.lipsyncLang||W.lipsyncLang||"en"};if(H.onSpeechEnd&&p.current){const D=p.current,ne=D.onAudioEnd;let J=null,ce=0;const Le=600,ke=()=>{if(ce++,ce>Le){J&&(clearInterval(J),J=null);try{H.onSpeechEnd()}catch(me){console.error("Error in onSpeechEnd callback:",me)}return}D&&(!D.isSpeaking||D.isSpeaking===!1)&&(!D.audioPlaylist||D.audioPlaylist.length===0)&&(!D.isAudioPlaying||D.isAudioPlaying===!1)&&(J&&(clearInterval(J),J=null),setTimeout(()=>{try{H.onSpeechEnd()}catch(me){console.error("Error in onSpeechEnd callback:",me)}},100))};setTimeout(()=>{J=setInterval(ke,100)},500)}p.current.lipsync&&Object.keys(p.current.lipsync).length>0?(p.current.setSlowdownRate&&p.current.setSlowdownRate(1.05),p.current.speakText(k,U)):setTimeout(async()=>{await j(),p.current&&p.current.lipsync&&(p.current.setSlowdownRate&&p.current.setSlowdownRate(1.05),p.current.speakText(k,U))},500)}catch(U){console.error("Error speaking text:",U),O(U.message||"Failed to speak text")}},[B,j,W.lipsyncLang]),de=A.useCallback(()=>{p.current&&(p.current.stopSpeaking(),p.current.setSlowdownRate&&p.current.setSlowdownRate(1))},[]),Q=A.useCallback(k=>{p.current&&p.current.setMood(k)},[]),b=A.useCallback(k=>{p.current&&p.current.setSlowdownRate&&p.current.setSlowdownRate(k)},[]),R=A.useCallback((k,H=!1)=>{if(p.current&&p.current.playAnimation){if(x&&x[k]&&(k=x[k]),p.current.setShowFullAvatar)try{p.current.setShowFullAvatar(!0)}catch(D){console.warn("Error setting full body mode:",D)}if(k.includes("."))try{p.current.playAnimation(k,null,10,0,.01,H)}catch(D){console.warn(`Failed to play ${k}:`,D);try{p.current.setBodyMovement("idle")}catch(ne){console.warn("Fallback animation also failed:",ne)}}else{const D=[".fbx",".glb",".gltf"];let ne=!1;for(const J of D)try{p.current.playAnimation(k+J,null,10,0,.01,H),ne=!0;break}catch{}if(!ne){console.warn("Animation not found:",k);try{p.current.setBodyMovement("idle")}catch(J){console.warn("Fallback animation also failed:",J)}}}}},[x]),z=A.useCallback(()=>{p.current&&p.current.onResize&&p.current.onResize()},[]);return A.useImperativeHandle(I,()=>({speakText:ae,stopSpeaking:de,resumeAudioContext:j,setMood:Q,setTimingAdjustment:b,playAnimation:R,isReady:B,talkingHead:p.current,handleResize:z,setBodyMovement:k=>{if(p.current&&p.current.setShowFullAvatar&&p.current.setBodyMovement)try{p.current.setShowFullAvatar(!0),p.current.setBodyMovement(k)}catch(H){console.warn("Error setting body movement:",H)}},setMovementIntensity:k=>p.current?.setMovementIntensity(k),playRandomDance:()=>{if(p.current&&p.current.setShowFullAvatar&&p.current.playRandomDance)try{p.current.setShowFullAvatar(!0),p.current.playRandomDance()}catch(k){console.warn("Error playing random dance:",k)}},playReaction:k=>{if(p.current&&p.current.setShowFullAvatar&&p.current.playReaction)try{p.current.setShowFullAvatar(!0),p.current.playReaction(k)}catch(H){console.warn("Error playing reaction:",H)}},playCelebration:()=>{if(p.current&&p.current.setShowFullAvatar&&p.current.playCelebration)try{p.current.setShowFullAvatar(!0),p.current.playCelebration()}catch(k){console.warn("Error playing celebration:",k)}},setShowFullAvatar:k=>{if(p.current&&p.current.setShowFullAvatar)try{p.current.setShowFullAvatar(k)}catch(H){console.warn("Error setting showFullAvatar:",H)}},lockAvatarPosition:()=>{if(p.current&&p.current.lockAvatarPosition)try{p.current.lockAvatarPosition()}catch(k){console.warn("Error locking avatar position:",k)}},unlockAvatarPosition:()=>{if(p.current&&p.current.unlockAvatarPosition)try{p.current.unlockAvatarPosition()}catch(k){console.warn("Error unlocking avatar position:",k)}}})),te.jsxs("div",{className:`talking-head-avatar ${g}`,style:{width:"100%",height:"100%",position:"relative",...f},children:[te.jsx("div",{ref:M,className:"talking-head-viewer",style:{width:"100%",height:"100%",minHeight:"400px"}}),F&&te.jsx("div",{className:"loading-overlay",style:{position:"absolute",top:"50%",left:"50%",transform:"translate(-50%, -50%)",color:"white",fontSize:"18px",zIndex:10},children:"Loading avatar..."}),v&&te.jsx("div",{className:"error-overlay",style:{position:"absolute",top:"50%",left:"50%",transform:"translate(-50%, -50%)",color:"#ff6b6b",fontSize:"16px",textAlign:"center",zIndex:10,padding:"20px",borderRadius:"8px"},children:v})]})});ge.displayName="TalkingHeadAvatar";const Ie=A.forwardRef(({text:T="Hello! I'm a talking avatar. How are you today?",onLoading:t=()=>{},onError:e=()=>{},onReady:i=()=>{},className:n="",style:s={},avatarConfig:o={}},r)=>{const h=A.useRef(null),a=A.useRef(null),[u,l]=A.useState(!0),[c,d]=A.useState(null),[g,f]=A.useState(!1),x=ue(),I=o.ttsService||x.service,M=I==="browser"?{endpoint:"",apiKey:null,defaultVoice:"Google US English"}:{...x,apiKey:o.ttsApiKey!==void 0&&o.ttsApiKey!==null?o.ttsApiKey:x.apiKey,endpoint:I==="elevenlabs"&&o.ttsApiKey?"https://api.elevenlabs.io/v1/text-to-speech":x.endpoint},p={url:"/avatars/brunette.glb",body:"F",avatarMood:"neutral",ttsLang:I==="browser"?"en-US":"en",ttsVoice:o.ttsVoice||M.defaultVoice,lipsyncLang:"en",showFullAvatar:!0,bodyMovement:"idle",movementIntensity:.5,...o},F={ttsEndpoint:M.endpoint,ttsApikey:M.apiKey,ttsService:I,lipsyncModules:["en"],cameraView:"upper"},C=A.useCallback(async()=>{if(!(!h.current||a.current))try{if(l(!0),d(null),a.current=new ve(h.current,F),await a.current.showAvatar(p,W=>{if(W.lengthComputable){const V=Math.min(100,Math.round(W.loaded/W.total*100));t(V)}}),a.current.morphs&&a.current.morphs.length>0){const W=a.current.morphs[0].morphTargetDictionary;console.log("Available morph targets:",Object.keys(W));const V=Object.keys(W).filter(Y=>Y.startsWith("viseme_"));console.log("Viseme morph targets found:",V),V.length===0&&(console.warn("No viseme morph targets found! Lip-sync will not work properly."),console.log("Expected viseme targets: viseme_aa, viseme_E, viseme_I, viseme_O, viseme_U, viseme_PP, viseme_SS, viseme_TH, viseme_DD, viseme_FF, viseme_kk, viseme_nn, viseme_RR, viseme_CH, viseme_sil"))}if(await new Promise(W=>{const V=()=>{a.current.lipsync&&Object.keys(a.current.lipsync).length>0?(console.log("Lip-sync modules loaded:",Object.keys(a.current.lipsync)),W()):(console.log("Waiting for lip-sync modules to load..."),setTimeout(V,100))};V()}),a.current&&a.current.setShowFullAvatar)try{a.current.setShowFullAvatar(!0),console.log("Avatar initialized in full body mode")}catch(W){console.warn("Error setting full body mode on initialization:",W)}l(!1),f(!0),i(a.current);const P=()=>{document.visibilityState==="visible"?a.current?.start():a.current?.stop()};return document.addEventListener("visibilitychange",P),()=>{document.removeEventListener("visibilitychange",P)}}catch(S){console.error("Error initializing TalkingHead:",S),d(S.message||"Failed to initialize avatar"),l(!1),e(S)}},[]);A.useEffect(()=>(C(),()=>{a.current&&(a.current.stop(),a.current.dispose(),a.current=null)}),[C]);const v=A.useCallback(S=>{if(a.current&&g)try{console.log("Speaking text:",S),console.log("Avatar config:",p),console.log("TalkingHead instance:",a.current),a.current.lipsync&&Object.keys(a.current.lipsync).length>0?(console.log("Lip-sync modules loaded:",Object.keys(a.current.lipsync)),a.current.setSlowdownRate&&(a.current.setSlowdownRate(1.05),console.log("Applied timing adjustment for better lip-sync")),a.current.speakText(S)):(console.warn("Lip-sync modules not ready, waiting..."),setTimeout(()=>{a.current&&a.current.lipsync?(console.log("Lip-sync now ready, speaking..."),a.current.setSlowdownRate&&(a.current.setSlowdownRate(1.05),console.log("Applied timing adjustment for better lip-sync")),a.current.speakText(S)):console.error("Lip-sync still not ready after waiting")},500))}catch(P){console.error("Error speaking text:",P),d(P.message||"Failed to speak text")}else console.warn("Avatar not ready for speaking. isReady:",g,"talkingHeadRef:",!!a.current)},[g,p]),O=A.useCallback(()=>{a.current&&(a.current.stopSpeaking(),a.current.setSlowdownRate&&(a.current.setSlowdownRate(1),console.log("Reset timing to normal")))},[]),B=A.useCallback(S=>{a.current&&a.current.setMood(S)},[]),_=A.useCallback(S=>{a.current&&a.current.setSlowdownRate&&(a.current.setSlowdownRate(S),console.log("Timing adjustment set to:",S))},[]),K=A.useCallback((S,P=!1)=>{if(a.current&&a.current.playAnimation){if(a.current.setShowFullAvatar)try{a.current.setShowFullAvatar(!0)}catch(V){console.warn("Error setting full body mode:",V)}if(S.includes("."))try{a.current.playAnimation(S,null,10,0,.01,P),console.log("Playing animation:",S)}catch(V){console.log(`Failed to play ${S}:`,V);try{a.current.setBodyMovement("idle"),console.log("Fallback to idle animation")}catch(Y){console.warn("Fallback animation also failed:",Y)}}else{const V=[".fbx",".glb",".gltf"];let Y=!1;for(const j of V)try{a.current.playAnimation(S+j,null,10,0,.01,P),console.log("Playing animation:",S+j),Y=!0;break}catch{console.log(`Failed to play ${S}${j}, trying next format...`)}if(!Y){console.warn("Animation system not available or animation not found:",S);try{a.current.setBodyMovement("idle"),console.log("Fallback to idle animation")}catch(j){console.warn("Fallback animation also failed:",j)}}}}else console.warn("Animation system not available or animation not found:",S)},[]);return A.useImperativeHandle(r,()=>({speakText:v,stopSpeaking:O,setMood:B,setTimingAdjustment:_,playAnimation:K,isReady:g,talkingHead:a.current,setBodyMovement:S=>{if(a.current&&a.current.setShowFullAvatar&&a.current.setBodyMovement)try{a.current.setShowFullAvatar(!0),a.current.setBodyMovement(S),console.log("Body movement set with full body mode:",S)}catch(P){console.warn("Error setting body movement:",P)}},setMovementIntensity:S=>a.current?.setMovementIntensity(S),playRandomDance:()=>{if(a.current&&a.current.setShowFullAvatar&&a.current.playRandomDance)try{a.current.setShowFullAvatar(!0),a.current.playRandomDance(),console.log("Random dance played with full body mode")}catch(S){console.warn("Error playing random dance:",S)}},playReaction:S=>{if(a.current&&a.current.setShowFullAvatar&&a.current.playReaction)try{a.current.setShowFullAvatar(!0),a.current.playReaction(S),console.log("Reaction played with full body mode:",S)}catch(P){console.warn("Error playing reaction:",P)}},playCelebration:()=>{if(a.current&&a.current.setShowFullAvatar&&a.current.playCelebration)try{a.current.setShowFullAvatar(!0),a.current.playCelebration(),console.log("Celebration played with full body mode")}catch(S){console.warn("Error playing celebration:",S)}},setShowFullAvatar:S=>{if(a.current&&a.current.setShowFullAvatar)try{a.current.setShowFullAvatar(S),console.log("Show full avatar set to:",S)}catch(P){console.warn("Error setting showFullAvatar:",P)}},lockAvatarPosition:()=>{if(a.current&&a.current.lockAvatarPosition)try{a.current.lockAvatarPosition()}catch(S){console.warn("Error locking avatar position:",S)}},unlockAvatarPosition:()=>{if(a.current&&a.current.unlockAvatarPosition)try{a.current.unlockAvatarPosition()}catch(S){console.warn("Error unlocking avatar position:",S)}}})),te.jsxs("div",{className:`talking-head-container ${n}`,style:s,children:[te.jsx("div",{ref:h,className:"talking-head-viewer",style:{width:"100%",height:"100%",minHeight:"400px"}}),u&&te.jsx("div",{className:"loading-overlay",style:{position:"absolute",top:"50%",left:"50%",transform:"translate(-50%, -50%)",color:"white",fontSize:"18px",zIndex:10},children:"Loading avatar..."}),c&&te.jsx("div",{className:"error-overlay",style:{position:"absolute",top:"50%",left:"50%",transform:"translate(-50%, -50%)",color:"#ff6b6b",fontSize:"16px",textAlign:"center",zIndex:10,padding:"20px",borderRadius:"8px"},children:c})]})});Ie.displayName="TalkingHeadComponent";const Ae=A.forwardRef(({curriculumData:T=null,avatarConfig:t={},animations:e={},onLessonStart:i=()=>{},onLessonComplete:n=()=>{},onQuestionAnswer:s=()=>{},onCurriculumComplete:o=()=>{},onCustomAction:r=()=>{},autoStart:h=!1},a)=>{const u=A.useRef(null),l=A.useRef({currentModuleIndex:0,currentLessonIndex:0,currentQuestionIndex:0,isTeaching:!1,isQuestionMode:!1,lessonCompleted:!1,curriculumCompleted:!1,score:0,totalQuestions:0}),c=A.useRef({onLessonStart:i,onLessonComplete:n,onQuestionAnswer:s,onCurriculumComplete:o,onCustomAction:r}),d=A.useRef(null),g=A.useRef(null),f=A.useRef(null),x=A.useRef(null),I=A.useRef(null),M=A.useRef(null),p=A.useRef(null),F=A.useRef(T?.curriculum||{title:"Default Curriculum",description:"No curriculum data provided",language:"en",modules:[]}),C=A.useRef({avatarUrl:t.avatarUrl||"/avatars/brunette.glb",avatarBody:t.avatarBody||"F",mood:t.mood||"happy",ttsLang:t.ttsLang||"en",ttsService:t.ttsService||null,ttsVoice:t.ttsVoice||null,ttsApiKey:t.ttsApiKey||null,bodyMovement:t.bodyMovement||"gesturing",movementIntensity:t.movementIntensity||.7,showFullAvatar:t.showFullAvatar!==void 0?t.showFullAvatar:!0,animations:e,lipsyncLang:"en"});A.useEffect(()=>{c.current={onLessonStart:i,onLessonComplete:n,onQuestionAnswer:s,onCurriculumComplete:o,onCustomAction:r}},[i,n,s,o,r]),A.useEffect(()=>{F.current=T?.curriculum||{title:"Default Curriculum",description:"No curriculum data provided",language:"en",modules:[]},C.current={avatarUrl:t.avatarUrl||"/avatars/brunette.glb",avatarBody:t.avatarBody||"F",mood:t.mood||"happy",ttsLang:t.ttsLang||"en",ttsService:t.ttsService||null,ttsVoice:t.ttsVoice||null,ttsApiKey:t.ttsApiKey||null,bodyMovement:t.bodyMovement||"gesturing",movementIntensity:t.movementIntensity||.7,showFullAvatar:t.showFullAvatar!==void 0?t.showFullAvatar:!0,animations:e,lipsyncLang:"en"}},[T,t,e]);const v=A.useCallback(()=>(F.current||{modules:[]}).modules[l.current.currentModuleIndex]?.lessons[l.current.currentLessonIndex],[]),O=A.useCallback(()=>v()?.questions[l.current.currentQuestionIndex],[v]),B=A.useCallback((b,R)=>R.type==="multiple_choice"||R.type==="true_false"?b===R.answer:R.type==="code_test"&&typeof b=="object"&&b!==null?b.passed===!0:!1,[]),_=A.useCallback(()=>{l.current.lessonCompleted=!0,l.current.isQuestionMode=!1;const b=l.current.totalQuestions>0?Math.round(l.current.score/l.current.totalQuestions*100):100;let R="Congratulations! You've completed this lesson";if(l.current.totalQuestions>0?R+=` with a score of ${l.current.score} out of ${l.current.totalQuestions} (${b}%). `:R+="! ",b>=80?R+="Excellent work! You have a great understanding of this topic.":b>=60?R+="Good job! You understand most of the concepts.":R+="Keep practicing! You're making progress.",c.current.onLessonComplete({moduleIndex:l.current.currentModuleIndex,lessonIndex:l.current.currentLessonIndex,score:l.current.score,totalQuestions:l.current.totalQuestions,percentage:b}),c.current.onCustomAction({type:"lessonComplete",moduleIndex:l.current.currentModuleIndex,lessonIndex:l.current.currentLessonIndex,score:l.current.score,totalQuestions:l.current.totalQuestions,percentage:b}),u.current){if(u.current.setMood("happy"),e.lessonComplete)try{u.current.playAnimation(e.lessonComplete,!0)}catch{u.current.playCelebration()}const z=F.current||{modules:[]},k=z.modules[l.current.currentModuleIndex],H=l.current.currentLessonIndex<(k?.lessons?.length||0)-1,U=l.current.currentModuleIndex<(z.modules?.length||0)-1,D=H||U,ne=C.current||{lipsyncLang:"en"};D?u.current.speakText(R,{lipsyncLang:ne.lipsyncLang,onSpeechEnd:()=>{setTimeout(()=>{g.current&&g.current()},100)}}):u.current.speakText(R,{lipsyncLang:ne.lipsyncLang,onSpeechEnd:()=>{setTimeout(()=>{I.current&&I.current()},100)}})}},[e.lessonComplete]),K=A.useCallback(()=>{l.current.curriculumCompleted=!0;const b=F.current||{modules:[]};if(c.current.onCurriculumComplete({modules:b.modules.length,totalLessons:b.modules.reduce((R,z)=>R+z.lessons.length,0)}),u.current){if(u.current.setMood("celebrating"),e.curriculumComplete)try{u.current.playAnimation(e.curriculumComplete,!0)}catch{u.current.playCelebration()}const R=C.current||{lipsyncLang:"en"};u.current.speakText("Amazing! You've completed the entire curriculum! You're now ready to move on to more advanced topics. Well done!",{lipsyncLang:R.lipsyncLang})}},[e.curriculumComplete]),S=A.useCallback(()=>{const b=v();l.current.isQuestionMode=!0,l.current.currentQuestionIndex=0,l.current.totalQuestions=b?.questions?.length||0;const R=O();if(R&&c.current.onCustomAction({type:"questionStart",moduleIndex:l.current.currentModuleIndex,lessonIndex:l.current.currentLessonIndex,questionIndex:l.current.currentQuestionIndex,totalQuestions:l.current.totalQuestions,question:R}),u.current&&R){if(u.current.setMood("curious"),e.questionStart)try{u.current.playAnimation(e.questionStart,!0)}catch(k){console.warn("Failed to play questionStart animation:",k)}const z=C.current||{lipsyncLang:"en"};R.type==="code_test"?u.current.speakText(`Let's test your coding skills! Here's your first challenge: ${R.question}`,{lipsyncLang:z.lipsyncLang}):R.type==="multiple_choice"?u.current.speakText(`Now let me ask you some questions. Here's the first one: ${R.question}`,{lipsyncLang:z.lipsyncLang}):R.type==="true_false"?u.current.speakText(`Let's start with some true or false questions. First question: ${R.question}`,{lipsyncLang:z.lipsyncLang}):u.current.speakText(`Now let me ask you some questions. Here's the first one: ${R.question}`,{lipsyncLang:z.lipsyncLang})}else if(u.current){const z=C.current||{lipsyncLang:"en"};u.current.speakText("Now let me ask you some questions to test your understanding.",{lipsyncLang:z.lipsyncLang})}},[e.questionStart,v,O]),P=A.useCallback(()=>{const b=v();if(l.current.currentQuestionIndex<(b?.questions?.length||0)-1){l.current.currentQuestionIndex+=1;const R=O();if(R&&c.current.onCustomAction({type:"nextQuestion",moduleIndex:l.current.currentModuleIndex,lessonIndex:l.current.currentLessonIndex,questionIndex:l.current.currentQuestionIndex,totalQuestions:l.current.totalQuestions,question:R}),u.current&&R){if(u.current.setMood("happy"),u.current.setBodyMovement("idle"),e.nextQuestion)try{u.current.playAnimation(e.nextQuestion,!0)}catch(k){console.warn("Failed to play nextQuestion animation:",k)}const z=C.current||{lipsyncLang:"en"};R.type==="code_test"?u.current.speakText(`Great! Now let's move on to your next coding challenge: ${R.question}`,{lipsyncLang:z.lipsyncLang}):R.type==="multiple_choice"?u.current.speakText(`Alright! Here's your next question: ${R.question}`,{lipsyncLang:z.lipsyncLang}):R.type==="true_false"?u.current.speakText(`Now let's try this one: ${R.question}`,{lipsyncLang:z.lipsyncLang}):u.current.speakText(`Here's the next question: ${R.question}`,{lipsyncLang:z.lipsyncLang})}}else f.current&&f.current()},[e.nextQuestion,v,O]),W=A.useCallback(()=>{const b=F.current||{modules:[]},R=b.modules[l.current.currentModuleIndex];l.current.currentLessonIndex<(R?.lessons?.length||0)-1?(l.current.currentLessonIndex+=1,l.current.currentQuestionIndex=0,l.current.lessonCompleted=!1,l.current.isQuestionMode=!1,l.current.isTeaching=!1,l.current.score=0,l.current.totalQuestions=0,c.current.onCustomAction({type:"lessonStart",moduleIndex:l.current.currentModuleIndex,lessonIndex:l.current.currentLessonIndex}),u.current&&(u.current.setMood("happy"),u.current.setBodyMovement("idle"),setTimeout(()=>{d.current&&d.current()},100))):l.current.currentModuleIndex<(b.modules?.length||0)-1?(l.current.currentModuleIndex+=1,l.current.currentLessonIndex=0,l.current.currentQuestionIndex=0,l.current.lessonCompleted=!1,l.current.isQuestionMode=!1,l.current.isTeaching=!1,l.current.score=0,l.current.totalQuestions=0,c.current.onCustomAction({type:"lessonStart",moduleIndex:l.current.currentModuleIndex,lessonIndex:l.current.currentLessonIndex}),u.current&&(u.current.setMood("happy"),u.current.setBodyMovement("idle"),setTimeout(()=>{d.current&&d.current()},100))):I.current&&I.current()},[]),V=A.useCallback(()=>{const b=v();let R=null;if(b?.avatar_script&&b?.body){const z=b.avatar_script.trim(),k=b.body.trim(),H=z.match(/[.!?]$/)?" ":". ";R=`${z}${H}${k}`}else R=b?.avatar_script||b?.body||null;if(u.current&&u.current.isReady&&R){l.current.isTeaching=!0,l.current.isQuestionMode=!1,u.current.setMood("happy");let z=!1;if(e.teaching)try{u.current.playAnimation(e.teaching,!0),z=!0}catch(H){console.warn("Failed to play teaching animation:",H)}z||u.current.setBodyMovement("gesturing");const k=C.current||{lipsyncLang:"en"};c.current.onLessonStart({moduleIndex:l.current.currentModuleIndex,lessonIndex:l.current.currentLessonIndex,lesson:b}),c.current.onCustomAction({type:"teachingStart",moduleIndex:l.current.currentModuleIndex,lessonIndex:l.current.currentLessonIndex,lesson:b}),u.current.speakText(R,{lipsyncLang:k.lipsyncLang,onSpeechEnd:()=>{l.current.isTeaching=!1,setTimeout(()=>{b.questions&&b.questions.length>0?M.current&&M.current():f.current&&f.current()},50)}})}},[e.teaching,v]),Y=A.useCallback(b=>{const R=O(),z=B(b,R);if(z&&(l.current.score+=1),c.current.onQuestionAnswer({moduleIndex:l.current.currentModuleIndex,lessonIndex:l.current.currentLessonIndex,questionIndex:l.current.currentQuestionIndex,answer:b,isCorrect:z,question:R}),u.current)if(z){if(u.current.setMood("happy"),e.correct)try{u.current.playReaction("happy")}catch{u.current.setBodyMovement("happy")}u.current.setBodyMovement("gesturing");const k=R.type==="code_test"?`Great job! Your code passed all the tests! ${R.explanation||""}`:`Excellent! That's correct! ${R.explanation||""}`,H=C.current||{lipsyncLang:"en"};u.current.speakText(k,{lipsyncLang:H.lipsyncLang,onSpeechEnd:()=>{setTimeout(()=>{x.current&&x.current()},50)}})}else{if(u.current.setMood("sad"),e.incorrect)try{u.current.playAnimation(e.incorrect,!0)}catch{u.current.setBodyMovement("idle")}u.current.setBodyMovement("gesturing");const k=R.type==="code_test"?`Your code didn't pass all the tests. ${R.explanation||"Try again!"}`:`Not quite right, but don't worry! ${R.explanation||""} Let's move on to the next question.`,H=C.current||{lipsyncLang:"en"};u.current.speakText(k,{lipsyncLang:H.lipsyncLang,onSpeechEnd:()=>{setTimeout(()=>{x.current&&x.current()},50)}})}else x.current&&x.current()},[e.correct,e.incorrect,O,B]),j=A.useCallback(b=>{const R=O();if(!b||typeof b!="object"){console.error("Invalid code test result format. Expected object with {passed: boolean, ...}");return}if(R?.type!=="code_test"){console.warn("Current question is not a code test. Use handleAnswerSelect for other question types.");return}const z={passed:b.passed===!0,results:b.results||[],output:b.output||"",error:b.error||null,executionTime:b.executionTime||null,testCount:b.testCount||0,passedCount:b.passedCount||0,failedCount:b.failedCount||0};c.current.onCustomAction({type:"codeTestSubmitted",moduleIndex:l.current.currentModuleIndex,lessonIndex:l.current.currentLessonIndex,questionIndex:l.current.currentQuestionIndex,testResult:z,question:R}),p.current&&p.current(z)},[O,B]),ae=A.useCallback(()=>{l.current.currentModuleIndex=0,l.current.currentLessonIndex=0,l.current.currentQuestionIndex=0,l.current.isTeaching=!1,l.current.isQuestionMode=!1,l.current.lessonCompleted=!1,l.current.curriculumCompleted=!1,l.current.score=0,l.current.totalQuestions=0},[]),de=A.useCallback(b=>{console.log("Avatar is ready!",b);const R=v(),z=R?.avatar_script||R?.body;h&&z&&setTimeout(()=>{d.current&&d.current()},200)},[h,v]);A.useLayoutEffect(()=>{d.current=V,g.current=W,f.current=_,x.current=P,I.current=K,M.current=S,p.current=Y}),A.useImperativeHandle(a,()=>({startTeaching:V,startQuestions:S,handleAnswerSelect:Y,handleCodeTestResult:j,nextQuestion:P,nextLesson:W,completeLesson:_,completeCurriculum:K,resetCurriculum:ae,getState:()=>({...l.current}),getCurrentQuestion:()=>O(),getCurrentLesson:()=>v(),getAvatarRef:()=>u.current,speakText:async(b,R={})=>{await u.current?.resumeAudioContext?.();const z=C.current||{lipsyncLang:"en"};u.current?.speakText(b,{...R,lipsyncLang:R.lipsyncLang||z.lipsyncLang})},resumeAudioContext:async()=>{if(u.current?.resumeAudioContext)return await u.current.resumeAudioContext();const b=u.current?.talkingHead;if(b?.audioCtx){const R=b.audioCtx;if(R.state==="suspended"||R.state==="interrupted")try{await R.resume(),console.log("Audio context resumed via talkingHead")}catch(z){console.warn("Failed to resume audio context:",z)}}else console.warn("Audio context not available yet")},stopSpeaking:()=>u.current?.stopSpeaking(),setMood:b=>u.current?.setMood(b),playAnimation:(b,R)=>u.current?.playAnimation(b,R),setBodyMovement:b=>u.current?.setBodyMovement(b),setMovementIntensity:b=>u.current?.setMovementIntensity(b),playRandomDance:()=>u.current?.playRandomDance(),playReaction:b=>u.current?.playReaction(b),playCelebration:()=>u.current?.playCelebration(),setShowFullAvatar:b=>u.current?.setShowFullAvatar(b),setTimingAdjustment:b=>u.current?.setTimingAdjustment(b),lockAvatarPosition:()=>u.current?.lockAvatarPosition(),unlockAvatarPosition:()=>u.current?.unlockAvatarPosition(),triggerCustomAction:(b,R)=>{c.current.onCustomAction({type:b,...R,state:{...l.current}})},handleResize:()=>u.current?.handleResize(),isAvatarReady:()=>u.current?.isReady||!1}),[V,S,Y,j,P,W,_,K,ae,O,v]);const Q=C.current||{avatarUrl:"/avatars/brunette.glb",avatarBody:"F",mood:"happy",ttsLang:"en",ttsService:null,ttsVoice:null,ttsApiKey:null,bodyMovement:"gesturing",movementIntensity:.7,showFullAvatar:!0,animations:e};return te.jsx("div",{style:{width:"100%",height:"100%"},children:te.jsx(ge,{ref:u,avatarUrl:Q.avatarUrl,avatarBody:Q.avatarBody,mood:Q.mood,ttsLang:Q.ttsLang,ttsService:Q.ttsService,ttsVoice:Q.ttsVoice,ttsApiKey:Q.ttsApiKey,bodyMovement:Q.bodyMovement,movementIntensity:Q.movementIntensity,showFullAvatar:Q.showFullAvatar,cameraView:"upper",animations:Q.animations,onReady:de,onLoading:()=>{},onError:b=>{console.error("Avatar error:",b)}})})});Ae.displayName="CurriculumLearning";const ye={dance:{name:"dance",type:"code-based",variations:["dancing","dancing2","dancing3"],loop:!0,duration:1e4,description:"Celebration dance animation with multiple variations"},happy:{name:"happy",type:"code-based",loop:!0,duration:5e3,description:"Happy, upbeat body movement"},surprised:{name:"surprised",type:"code-based",loop:!1,duration:3e3,description:"Surprised reaction with quick movements"},thinking:{name:"thinking",type:"code-based",loop:!0,duration:8e3,description:"Thoughtful, contemplative movement"},nodding:{name:"nodding",type:"code-based",loop:!1,duration:2e3,description:"Nodding agreement gesture"},shaking:{name:"shaking",type:"code-based",loop:!1,duration:2e3,description:"Shaking head disagreement gesture"},celebration:{name:"celebration",type:"code-based",loop:!1,duration:3e3,description:"Celebration animation for achievements"},energetic:{name:"energetic",type:"code-based",loop:!0,duration:6e3,description:"High-energy, enthusiastic movement"},swaying:{name:"swaying",type:"code-based",loop:!0,duration:8e3,description:"Gentle swaying motion"},bouncing:{name:"bouncing",type:"code-based",loop:!0,duration:4e3,description:"Bouncy, playful movement"},gesturing:{name:"gesturing",type:"code-based",loop:!0,duration:5e3,description:"Teaching gesture animation"},walking:{name:"walking",type:"code-based",loop:!0,duration:6e3,description:"Walking motion"},prancing:{name:"prancing",type:"code-based",loop:!0,duration:4e3,description:"Playful prancing movement"},excited:{name:"excited",type:"code-based",loop:!0,duration:5e3,description:"Excited, energetic movement"}},Ke=T=>ye[T]||null,Je=T=>ye.hasOwnProperty(T);exports.CurriculumLearning=Ae;exports.TalkingHeadAvatar=ge;exports.TalkingHeadComponent=Ie;exports.animations=ye;exports.getActiveTTSConfig=ue;exports.getAnimation=Ke;exports.getVoiceOptions=_e;exports.hasAnimation=Je;
|
|
7
|
+
`,s=await fetch(this.opt.ttsEndpoint,{method:"POST",headers:{"Ocp-Apim-Subscription-Key":this.opt.ttsApikey,"Content-Type":"application/ssml+xml","X-Microsoft-OutputFormat":"audio-16khz-128kbitrate-mono-mp3"},body:n});if(!s.ok)throw new Error(`Azure TTS error: ${s.status} ${s.statusText}`);const o=await s.arrayBuffer(),r=await this.audioCtx.decodeAudioData(o);console.log("Analyzing audio for precise lip-sync...");const h=await this.audioAnalyzer.analyzeAudio(r,e);console.log("Azure TTS Audio Analysis:",{text:e,audioDuration:r.duration,visemeCount:h.visemes.length,wordCount:h.words.length,features:{onsets:h.features.onsets.length,boundaries:h.features.phonemeBoundaries.length}});const a=[];for(let l=0;l<h.visemes.length;l++){const c=h.visemes[l],d=c.startTime*1e3,g=c.duration*1e3,f=c.intensity;a.push({template:{name:"viseme"},ts:[d-Math.min(60,2*g/3),d+Math.min(25,g/2),d+g+Math.min(60,g/2)],vs:{["viseme_"+c.viseme]:[null,f,0]}})}const u=[...t.anim,...a];this.audioPlaylist.push({anim:u,audio:r}),this.onSubtitles=t.onSubtitles||null,this.resetLips(),t.mood&&this.setMood(t.mood),this.playAudio()}async synthesizeWithExternalTTS(t){let e="<speak>";t.text.forEach((o,r)=>{r>0&&(e+=" <mark name='"+o.mark+"'/>"),e+=o.word.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'").replace(new RegExp("^\\p{Dash_Punctuation}$","ug"),'<break time="750ms"/>')}),e+="</speak>";const i={method:"POST",headers:{"Content-Type":"application/json; charset=utf-8"},body:JSON.stringify({input:{ssml:e},voice:{languageCode:t.lang||this.avatar.ttsLang||this.opt.ttsLang,name:t.voice||this.avatar.ttsVoice||this.opt.ttsVoice},audioConfig:{audioEncoding:this.ttsAudioEncoding,speakingRate:(t.rate||this.avatar.ttsRate||this.opt.ttsRate)+this.mood.speech.deltaRate,pitch:(t.pitch||this.avatar.ttsPitch||this.opt.ttsPitch)+this.mood.speech.deltaPitch,volumeGainDb:(t.volume||this.avatar.ttsVolume||this.opt.ttsVolume)+this.mood.speech.deltaVolume},enableTimePointing:[1]})};this.opt.jwtGet&&typeof this.opt.jwtGet=="function"&&(i.headers.Authorization="Bearer "+await this.opt.jwtGet());const n=await fetch(this.opt.ttsEndpoint+(this.opt.ttsApikey?"?key="+this.opt.ttsApikey:""),i),s=await n.json();if(n.status===200&&s&&s.audioContent){const o=this.b64ToArrayBuffer(s.audioContent),r=await this.audioCtx.decodeAudioData(o);this.speakWithHands();const h=[0];let a=0;t.text.forEach((c,d)=>{if(d>0){let g=h[h.length-1];s.timepoints[a]&&(g=s.timepoints[a].timeSeconds*1e3,s.timepoints[a].markName===""+c.mark&&a++),h.push(g)}});const u=[{mark:0,time:0}];h.forEach((c,d)=>{if(d>0){let g=c-h[d-1];u[d-1].duration=g,u.push({mark:d,time:c})}});let l=1e3*r.duration;l>this.opt.ttsTrimEnd&&(l=l-this.opt.ttsTrimEnd),u[u.length-1].duration=l-u[u.length-1].time,t.anim.forEach(c=>{const d=u[c.mark];if(d)for(let g=0;g<c.ts.length;g++)c.ts[g]=d.time+c.ts[g]*d.duration+this.opt.ttsTrimStart}),this.audioPlaylist.push({anim:t.anim,audio:r}),this.onSubtitles=t.onSubtitles||null,this.resetLips(),t.mood&&this.setMood(t.mood),this.playAudio()}else this.startSpeaking(!0)}async startSpeaking(t=!1){if(!(!this.armature||this.isSpeaking&&!t))if(this.stateName="speaking",this.isSpeaking=!0,this.speechQueue.length){let e=this.speechQueue.shift();if(e.emoji){this.lookAtCamera(500);let i=e.emoji.dt.reduce((n,s)=>n+s,0);this.animQueue.push(this.animFactory(e.emoji)),setTimeout(this.startSpeaking.bind(this),i,!0)}else if(e.break)setTimeout(this.startSpeaking.bind(this),e.break,!0);else if(e.audio)e.isRaw||(this.lookAtCamera(500),this.speakWithHands(),this.resetLips()),this.audioPlaylist.push({anim:e.anim,audio:e.audio,isRaw:e.isRaw}),this.onSubtitles=e.onSubtitles||null,e.mood&&this.setMood(e.mood),this.playAudio();else if(e.text){this.lookAtCamera(500);try{!this.opt.ttsEndpoint||this.opt.ttsEndpoint===""?await this.synthesizeWithBrowserTTS(e):this.opt.ttsService==="elevenlabs"?await this.synthesizeWithElevenLabsTTS(e):this.opt.ttsService==="azure"?await this.synthesizeWithAzureTTS(e):await this.synthesizeWithExternalTTS(e)}catch(i){console.error("Error:",i),this.startSpeaking(!0)}}else e.anim?(this.onSubtitles=e.onSubtitles||null,this.resetLips(),e.mood&&this.setMood(e.mood),e.anim.forEach((i,n)=>{for(let s=0;s<i.ts.length;s++)i.ts[s]=this.animClock+10*n;this.animQueue.push(i)}),setTimeout(this.startSpeaking.bind(this),10*e.anim.length,!0)):e.marker?(typeof e.marker=="function"&&e.marker(),this.startSpeaking(!0)):this.startSpeaking(!0)}else this.stateName="idle",this.isSpeaking=!1}pauseSpeaking(){try{this.audioSpeechSource.stop()}catch{}this.audioPlaylist.length=0,this.stateName="idle",this.isSpeaking=!1,this.isAudioPlaying=!1,this.animQueue=this.animQueue.filter(t=>t.template.name!=="viseme"&&t.template.name!=="subtitles"&&t.template.name!=="blendshapes"),this.armature&&(this.resetLips(),this.render())}stopSpeaking(){try{this.audioSpeechSource.stop()}catch{}this.audioPlaylist.length=0,this.speechQueue.length=0,this.animQueue=this.animQueue.filter(t=>t.template.name!=="viseme"&&t.template.name!=="subtitles"&&t.template.name!=="blendshapes"),this.stateName="idle",this.isSpeaking=!1,this.isAudioPlaying=!1,this.armature&&(this.resetLips(),this.render())}async streamStart(t={},e=null,i=null,n=null,s=null){if(this.stopSpeaking(),this.isStreaming=!0,t.waitForAudioChunks!==void 0&&(this.streamWaitForAudioChunks=t.waitForAudioChunks),this.streamWaitForAudioChunks||(this.streamAudioStartTime=this.animClock),this.streamLipsyncQueue=[],this.streamLipsyncType=t.lipsyncType||this.streamLipsyncType||"visemes",this.streamLipsyncLang=t.lipsyncLang||this.streamLipsyncLang||this.avatar.lipsyncLang||this.opt.lipsyncLang,this.onAudioStart=e,this.onAudioEnd=i,this.onMetrics=s,t.sampleRate!==void 0){const r=t.sampleRate;typeof r=="number"&&r>=8e3&&r<=96e3?r!==this.audioCtx.sampleRate&&this.initAudioGraph(r):console.warn("Invalid sampleRate provided. It must be a number between 8000 and 96000 Hz.")}if(t.gain!==void 0&&(this.audioStreamGainNode.gain.value=t.gain),!this.streamWorkletNode||!this.streamWorkletNode.port||this.streamWorkletNode.numberOfOutputs===0||this.streamWorkletNode.context!==this.audioCtx){if(this.streamWorkletNode)try{this.streamWorkletNode.disconnect(),this.streamWorkletNode=null}catch{}if(!this.workletLoaded)try{const r=this.audioCtx.audioWorklet.addModule(Qe.href),h=new Promise((a,u)=>setTimeout(()=>u(new Error("Worklet loading timed out")),5e3));await Promise.race([r,h]),this.workletLoaded=!0}catch(r){throw console.error("Failed to load audio worklet:",r),new Error("Failed to initialize streaming speech")}this.streamWorkletNode=new AudioWorkletNode(this.audioCtx,"playback-worklet",{processorOptions:{sampleRate:this.audioCtx.sampleRate,metrics:t.metrics||{enabled:!1}}}),this.streamWorkletNode.connect(this.audioStreamGainNode),this.streamWorkletNode.connect(this.audioAnalyzerNode),this.streamWorkletNode.port.onmessage=r=>{if(r.data.type==="playback-started"&&(this.isSpeaking=!0,this.stateName="speaking",this.streamWaitForAudioChunks&&(this.streamAudioStartTime=this.animClock),this._processStreamLipsyncQueue(),this.speakWithHands(),this.onAudioStart))try{this.onAudioStart?.()}catch(h){console.error(h)}if(r.data.type==="playback-ended"&&(this._streamPause(),this.onAudioEnd))try{this.onAudioEnd()}catch{}if(this.onMetrics&&r.data.type==="metrics")try{this.onMetrics(r.data)}catch{}}}if(t.metrics)try{this.streamWorkletNode.port.postMessage({type:"config-metrics",data:t.metrics})}catch{}if(this.resetLips(),this.lookAtCamera(500),t.mood&&this.setMood(t.mood),this.onSubtitles=n||null,this.audioCtx.state==="suspended"||this.audioCtx.state==="interrupted"){const r=this.audioCtx.resume(),h=new Promise((a,u)=>setTimeout(()=>u("p2"),1e3));try{await Promise.race([r,h])}catch{console.warn("Can't play audio. Web Audio API suspended. This is often due to calling some speak method before the first user action, which is typically prevented by the browser.");return}}}streamNotifyEnd(){!this.isStreaming||!this.streamWorkletNode||this.streamWorkletNode.port.postMessage({type:"no-more-data"})}streamInterrupt(){if(!this.isStreaming)return;const t=this.isSpeaking;if(this.streamWorkletNode)try{this.streamWorkletNode.port.postMessage({type:"stop"})}catch{}if(this._streamPause(!0),t&&this.onAudioEnd)try{this.onAudioEnd()}catch{}}streamStop(){if(this.isStreaming){if(this.streamInterrupt(),this.streamWorkletNode){try{this.streamWorkletNode.disconnect()}catch{}this.streamWorkletNode=null}this.isStreaming=!1}}_streamPause(t=!1){this.isSpeaking=!1,this.stateName="idle",t&&(this.streamWaitForAudioChunks&&(this.streamAudioStartTime=null),this.streamLipsyncQueue=[],this.animQueue=this.animQueue.filter(e=>e.template.name!=="viseme"&&e.template.name!=="subtitles"&&e.template.name!=="blendshapes"),this.armature&&(this.resetLips(),this.render()))}_processStreamLipsyncQueue(){if(this.isStreaming)for(;this.streamLipsyncQueue.length>0;){const t=this.streamLipsyncQueue.shift();this._processLipsyncData(t,this.streamAudioStartTime)}}_processLipsyncData(t,e){if(this.isStreaming){if(t.visemes&&this.streamLipsyncType=="visemes")for(let i=0;i<t.visemes.length;i++){const n=t.visemes[i],s=e+t.vtimes[i],o=t.vdurations[i],r={template:{name:"viseme"},ts:[s-2*o/3,s+o/2,s+o+o/2],vs:{["viseme_"+n]:[null,n==="PP"||n==="FF"?.9:.6,0]}};this.animQueue.push(r)}if(t.words&&(this.onSubtitles||this.streamLipsyncType=="words"))for(let i=0;i<t.words.length;i++){const n=t.words[i],s=t.wtimes[i];let o=t.wdurations[i];if(n.length&&(this.onSubtitles&&this.animQueue.push({template:{name:"subtitles"},ts:[e+s],vs:{subtitles:[" "+n]}}),this.streamLipsyncType=="words")){const r=this.streamLipsyncLang||this.avatar.lipsyncLang||this.opt.lipsyncLang,h=this.lipsyncPreProcessText(n,r),a=this.lipsyncWordsToVisemes(h,r);if(a&&a.visemes&&a.visemes.length){const u=a.times[a.visemes.length-1]+a.durations[a.visemes.length-1],l=Math.min(o,Math.max(0,o-a.visemes.length*150));let c=.6+this.convertRange(l,[0,o],[0,.4]);if(o=Math.min(o,a.visemes.length*200),u>0)for(let d=0;d<a.visemes.length;d++){const g=e+s+a.times[d]/u*o,f=a.durations[d]/u*o;this.animQueue.push({template:{name:"viseme"},ts:[g-Math.min(60,2*f/3),g+Math.min(25,f/2),g+f+Math.min(60,f/2)],vs:{["viseme_"+a.visemes[d]]:[null,a.visemes[d]==="PP"||a.visemes[d]==="FF"?.9:c,0]}})}}}}if(t.anims&&this.streamLipsyncType=="blendshapes")for(let i=0;i<t.anims.length;i++){let n=t.anims[i];n.delay+=e;let s=this.animFactory(n,!1,1,1,!0);this.animQueue.push(s)}}}streamAudio(t){if(!(!this.isStreaming||!this.streamWorkletNode)){if(this.isSpeaking||(this.streamLipsyncQueue=[],this.streamAudioStartTime=null),this.isSpeaking=!0,this.stateName="speaking",t.audio!==void 0){const e={type:"audioData",data:null};if(t.audio instanceof ArrayBuffer)e.data=t.audio,this.streamWorkletNode.port.postMessage(e,[e.data]);else if(t.audio instanceof Int16Array||t.audio instanceof Uint8Array){const i=t.audio.buffer.slice(t.audio.byteOffset,t.audio.byteOffset+t.audio.byteLength);e.data=i,this.streamWorkletNode.port.postMessage(e,[e.data])}else if(t.audio instanceof Float32Array){const i=new Int16Array(t.audio.length);for(let n=0;n<t.audio.length;n++){let s=Math.max(-1,Math.min(1,t.audio[n]));i[n]=s<0?s*32768:s*32767}e.data=i.buffer,this.streamWorkletNode.port.postMessage(e,[e.data])}else console.error("r.audio is not a supported type. Must be ArrayBuffer, Int16Array, Uint8Array, or Float32Array:",t.audio)}if(t.visemes||t.anims||t.words){if(this.streamWaitForAudioChunks&&!this.streamAudioStartTime){this.streamLipsyncQueue.length>=200&&this.streamLipsyncQueue.shift(),this.streamLipsyncQueue.push(t);return}else!this.streamWaitForAudioChunks&&!this.streamAudioStartTime&&(this.streamAudioStartTime=this.animClock);this._processLipsyncData(t,this.streamAudioStartTime)}}}makeEyeContact(t){this.animQueue.push(this.animFactory({name:"eyecontact",dt:[0,t],vs:{eyeContact:[1]}}))}lookAhead(t){if(t){let e=(Math.random()-.5)/4,i=(Math.random()-.5)/4,n=this.animQueue.findIndex(o=>o.template.name==="lookat");n!==-1&&this.animQueue.splice(n,1);const s={name:"lookat",dt:[750,t],vs:{bodyRotateX:[e],bodyRotateY:[i],eyesRotateX:[-3*e+.1],eyesRotateY:[-5*i],browInnerUp:[[0,.7]],mouthLeft:[[0,.7]],mouthRight:[[0,.7]],eyeContact:[0],headMove:[0]}};this.animQueue.push(this.animFactory(s))}}lookAtCamera(t){let e;if(this.speakTo&&(e=new y.Vector3,this.speakTo.objectLeftEye?.isObject3D?(this.speakTo.armature.objectHead,this.speakTo.objectLeftEye.updateMatrixWorld(!0),this.speakTo.objectRightEye.updateMatrixWorld(!0),ie.setFromMatrixPosition(this.speakTo.objectLeftEye.matrixWorld),oe.setFromMatrixPosition(this.speakTo.objectRightEye.matrixWorld),e.addVectors(ie,oe).divideScalar(2)):this.speakTo.isObject3D?this.speakTo.getWorldPosition(e):this.speakTo.isVector3?e.set(this.speakTo):this.speakTo.x&&this.speakTo.y&&this.speakTo.z&&e.set(this.speakTo.x,this.speakTo.y,this.speakTo.z)),!e){if(this.avatar.hasOwnProperty("avatarIgnoreCamera")){if(this.avatar.avatarIgnoreCamera){this.lookAhead(t);return}}else if(this.opt.avatarIgnoreCamera){this.lookAhead(t);return}this.lookAt(null,null,t);return}this.objectLeftEye.updateMatrixWorld(!0),this.objectRightEye.updateMatrixWorld(!0),ie.setFromMatrixPosition(this.objectLeftEye.matrixWorld),oe.setFromMatrixPosition(this.objectRightEye.matrixWorld),ie.add(oe).divideScalar(2),N.copy(this.armature.quaternion),N.multiply(this.poseTarget.props["Hips.quaternion"]),N.multiply(this.poseTarget.props["Spine.quaternion"]),N.multiply(this.poseTarget.props["Spine1.quaternion"]),N.multiply(this.poseTarget.props["Spine2.quaternion"]),N.multiply(this.poseTarget.props["Neck.quaternion"]),N.multiply(this.poseTarget.props["Head.quaternion"]);const i=new y.Vector3().subVectors(e,ie).normalize(),n=Math.atan2(i.x,i.z),s=Math.asin(-i.y);E.set(s,n,0,"YXZ");const r=new y.Quaternion().setFromEuler(E),h=new y.Quaternion().copy(r).multiply(N.clone().invert());E.setFromQuaternion(h,"YXZ");let a=E.x/(40/24)+.2,u=E.y/(9/4),l=Math.min(.6,Math.max(-.3,a)),c=Math.min(.8,Math.max(-.8,u)),d=(Math.random()-.5)/4,g=(Math.random()-.5)/4;if(t){let f=this.animQueue.findIndex(I=>I.template.name==="lookat");f!==-1&&this.animQueue.splice(f,1);const x={name:"lookat",dt:[750,t],vs:{bodyRotateX:[l+d],bodyRotateY:[c+g],eyesRotateX:[-3*d+.1],eyesRotateY:[-5*g],browInnerUp:[[0,.7]],mouthLeft:[[0,.7]],mouthRight:[[0,.7]],eyeContact:[0],headMove:[0]}};this.animQueue.push(this.animFactory(x))}}lookAt(t,e,i){if(!this.camera)return;const n=this.nodeAvatar.getBoundingClientRect();this.objectLeftEye.updateMatrixWorld(!0),this.objectRightEye.updateMatrixWorld(!0);const s=new y.Vector3().setFromMatrixPosition(this.objectLeftEye.matrixWorld),o=new y.Vector3().setFromMatrixPosition(this.objectRightEye.matrixWorld),r=new y.Vector3().addVectors(s,o).divideScalar(2);r.project(this.camera);let h=(r.x+1)/2*n.width+n.left,a=-(r.y-1)/2*n.height+n.top;t===null&&(t=h),e===null&&(e=a),N.copy(this.armature.quaternion),N.multiply(this.poseTarget.props["Hips.quaternion"]),N.multiply(this.poseTarget.props["Spine.quaternion"]),N.multiply(this.poseTarget.props["Spine1.quaternion"]),N.multiply(this.poseTarget.props["Spine2.quaternion"]),N.multiply(this.poseTarget.props["Neck.quaternion"]),N.multiply(this.poseTarget.props["Head.quaternion"]),E.setFromQuaternion(N);let u=E.x/(40/24),l=E.y/(9/4),c=Math.min(.4,Math.max(-.4,this.camera.rotation.x)),d=Math.min(.4,Math.max(-.4,this.camera.rotation.y)),g=Math.max(window.innerWidth-h,h),f=Math.max(window.innerHeight-a,a),x=this.convertRange(e,[a-f,a+f],[-.3,.6])-u+c,I=this.convertRange(t,[h-g,h+g],[-.8,.8])-l+d;x=Math.min(.6,Math.max(-.3,x)),I=Math.min(.8,Math.max(-.8,I));let M=(Math.random()-.5)/4,p=(Math.random()-.5)/4;if(i){let F=this.animQueue.findIndex(v=>v.template.name==="lookat");F!==-1&&this.animQueue.splice(F,1);const C={name:"lookat",dt:[750,i],vs:{bodyRotateX:[x+M],bodyRotateY:[I+p],eyesRotateX:[-3*M+.1],eyesRotateY:[-5*p],browInnerUp:[[0,.7]],mouthLeft:[[0,.7]],mouthRight:[[0,.7]],eyeContact:[0],headMove:[0]}};this.animQueue.push(this.animFactory(C))}}touchAt(t,e){if(!this.camera)return;const i=this.nodeAvatar.getBoundingClientRect(),n=new y.Vector2((t-i.left)/i.width*2-1,-((e-i.top)/i.height)*2+1),s=new y.Raycaster;s.setFromCamera(n,this.camera);const o=s.intersectObject(this.armature);if(o.length>0){const r=o[0].point,h=new y.Vector3,a=new y.Vector3;this.objectLeftArm.getWorldPosition(h),this.objectRightArm.getWorldPosition(a);const u=h.distanceToSquared(r),l=a.distanceToSquared(r);u<l?(this.ikSolve({iterations:20,root:"LeftShoulder",effector:"LeftHandMiddle1",links:[{link:"LeftHand",minx:-.5,maxx:.5,miny:-1,maxy:1,minz:-.5,maxz:.5,maxAngle:.1},{link:"LeftForeArm",minx:-.5,maxx:1.5,miny:-1.5,maxy:1.5,minz:-.5,maxz:3,maxAngle:.2},{link:"LeftArm",minx:-1.5,maxx:1.5,miny:0,maxy:0,minz:-1,maxz:3}]},r,!1,1e3),this.setValue("handFistLeft",0)):(this.ikSolve({iterations:20,root:"RightShoulder",effector:"RightHandMiddle1",links:[{link:"RightHand",minx:-.5,maxx:.5,miny:-1,maxy:1,minz:-.5,maxz:.5,maxAngle:.1},{link:"RightForeArm",minx:-.5,maxx:1.5,miny:-1.5,maxy:1.5,minz:-3,maxz:.5,maxAngle:.2},{link:"RightArm",minx:-1.5,maxx:1.5,miny:0,maxy:0,minz:-1,maxz:3}]},r,!1,1e3),this.setValue("handFistRight",0))}else["LeftArm","LeftForeArm","LeftHand","RightArm","RightForeArm","RightHand"].forEach(r=>{let h=r+".quaternion";this.poseTarget.props[h].copy(this.getPoseTemplateProp(h)),this.poseTarget.props[h].t=this.animClock,this.poseTarget.props[h].d=1e3});return o.length>0}speakWithHands(t=0,e=.5){if(this.mixer||this.gesture||!this.poseTarget.template.standing||this.poseTarget.template.bend||Math.random()>e)return;this.ikSolve({root:"LeftShoulder",effector:"LeftHandMiddle1",links:[{link:"LeftHand",minx:-.5,maxx:.5,miny:-1,maxy:1,minz:-.5,maxz:.5},{link:"LeftForeArm",minx:-.5,maxx:1.5,miny:-1.5,maxy:1.5,minz:-.5,maxz:3},{link:"LeftArm",minx:-1.5,maxx:1.5,miny:-1.5,maxy:1.5,minz:-1,maxz:3}]},new y.Vector3(this.gaussianRandom(0,.5),this.gaussianRandom(-.8,-.2),this.gaussianRandom(0,.5)),!0),this.ikSolve({root:"RightShoulder",effector:"RightHandMiddle1",links:[{link:"RightHand",minx:-.5,maxx:.5,miny:-1,maxy:1,minz:-.5,maxz:.5},{link:"RightForeArm",minx:-.5,maxx:1.5,miny:-1.5,maxy:1.5,minz:-3,maxz:.5},{link:"RightArm"}]},new y.Vector3(this.gaussianRandom(-.5,0),this.gaussianRandom(-.8,-.2),this.gaussianRandom(0,.5)),!0);const i=[],n=[];i.push(100+Math.round(Math.random()*500)),n.push({duration:1e3,props:{"LeftHand.quaternion":new y.Quaternion().setFromEuler(new y.Euler(0,-1-Math.random(),0)),"RightHand.quaternion":new y.Quaternion().setFromEuler(new y.Euler(0,1+Math.random(),0))}}),["LeftArm","LeftForeArm","RightArm","RightForeArm"].forEach(o=>{n[0].props[o+".quaternion"]=this.ikMesh.getObjectByName(o).quaternion.clone()}),i.push(1e3+Math.round(Math.random()*500)),n.push({duration:2e3,props:{}}),["LeftArm","LeftForeArm","RightArm","RightForeArm","LeftHand","RightHand"].forEach(o=>{n[1].props[o+".quaternion"]=null});const s=this.animFactory({name:"talkinghands",delay:t,dt:i,vs:{moveto:n}});this.animQueue.push(s)}getSlowdownRate(t){return this.animSlowdownRate}setSlowdownRate(t){this.animSlowdownRate=t,this.audioSpeechSource.playbackRate.value=1/this.animSlowdownRate,this.audioBackgroundSource.playbackRate.value=1/this.animSlowdownRate}getAutoRotateSpeed(t){return this.controls.autoRotateSpeed}setAutoRotateSpeed(t){this.controls.autoRotateSpeed=t,this.controls.autoRotate=t>0}start(){this.armature&&this.isRunning===!1&&(this.audioCtx.resume(),this.animTimeLast=performance.now(),this.isRunning=!0,this.isAvatarOnly||requestAnimationFrame(this.animate.bind(this)))}stop(){this.isRunning=!1,this.audioCtx.suspend()}startListening(t,e={},i=null){this.listeningAnalyzer=t,this.listeningAnalyzer.fftSize=256,this.listeningAnalyzer.smoothingTimeConstant=.1,this.listeningAnalyzer.minDecibels=-70,this.listeningAnalyzer.maxDecibels=-10,this.listeningOnchange=i&&typeof i=="function"?i:null,this.listeningSilenceThresholdLevel=e?.hasOwnProperty("listeningSilenceThresholdLevel")?e.listeningSilenceThresholdLevel:this.opt.listeningSilenceThresholdLevel,this.listeningSilenceThresholdMs=e?.hasOwnProperty("listeningSilenceThresholdMs")?e.listeningSilenceThresholdMs:this.opt.listeningSilenceThresholdMs,this.listeningSilenceDurationMax=e?.hasOwnProperty("listeningSilenceDurationMax")?e.listeningSilenceDurationMax:this.opt.listeningSilenceDurationMax,this.listeningActiveThresholdLevel=e?.hasOwnProperty("listeningActiveThresholdLevel")?e.listeningActiveThresholdLevel:this.opt.listeningActiveThresholdLevel,this.listeningActiveThresholdMs=e?.hasOwnProperty("listeningActiveThresholdMs")?e.listeningActiveThresholdMs:this.opt.listeningActiveThresholdMs,this.listeningActiveDurationMax=e?.hasOwnProperty("listeningActiveDurationMax")?e.listeningActiveDurationMax:this.opt.listeningActiveDurationMax,this.listeningActive=!1,this.listeningVolume=0,this.listeningTimer=0,this.listeningTimerTotal=0,this.isListening=!0}stopListening(){this.isListening=!1}async playAnimation(t,e=null,i=10,n=0,s=.01,o=!1){if(!this.armature)return;this.positionWasLocked=!o,o?console.log("Position locking disabled for FBX animation:",t):(this.lockAvatarPosition(),console.log("Position locked immediately before FBX animation:",t));let r=this.animClips.find(h=>h.url===t+"-"+n);if(r){let h=this.animQueue.find(l=>l.template.name==="pose");h&&(h.ts[0]=1/0),Object.entries(r.pose.props).forEach(l=>{this.poseBase.props[l[0]]=l[1].clone(),this.poseTarget.props[l[0]]=l[1].clone(),this.poseTarget.props[l[0]].t=0,this.poseTarget.props[l[0]].d=1e3}),this.mixer?console.log("Using existing mixer for FBX animation, preserving morph targets"):(this.mixer=new y.AnimationMixer(this.armature),console.log("Created new mixer for FBX animation")),this.mixer.addEventListener("finished",this.stopAnimation.bind(this),{once:!0});const a=Math.ceil(i/r.clip.duration),u=this.mixer.clipAction(r.clip);u.setLoop(y.LoopRepeat,a),u.clampWhenFinished=!0,this.currentFBXAction=u;try{u.fadeIn(.5).play(),console.log("FBX animation started successfully:",t)}catch(l){console.warn("FBX animation failed to start:",l),this.stopAnimation();return}if(u.getClip().tracks.length===0){console.warn("FBX animation has no valid tracks, stopping"),this.stopAnimation();return}}else{if(t.split(".").pop().toLowerCase()!=="fbx"){console.error(`Invalid file type for FBX animation: ${t}. Expected .fbx file.`);return}let a=!1;try{const c=await fetch(t,{method:"HEAD"});if(a=c.ok,!a){console.error(`FBX file not found at ${t}. Status: ${c.status}`),console.error("Please check:"),console.error("1. File path is correct (note: path is case-sensitive)"),console.error("2. File exists in your public folder"),console.error("3. File is accessible (not blocked by server)");return}}catch(c){console.warn(`Could not verify file existence for ${t}, attempting to load anyway:`,c)}const u=new fe.FBXLoader;let l;try{l=await u.loadAsync(t,e)}catch(c){console.error(`Failed to load FBX animation from ${t}:`,c),console.error("Error details:",{message:c.message,url:t,suggestion:"Make sure the file is a valid FBX file and the path is correct"}),c.message&&c.message.includes("version number")&&(console.error("FBX Loader Error: Cannot find version number"),console.error("This error usually means:"),console.error("1. The file is not a valid FBX file (might be GLB, corrupted, or wrong format)"),console.error("2. The file might be corrupted"),console.error("3. The file path might be incorrect"),console.error("4. The server returned an HTML error page instead of the FBX file"),console.error("5. The file might not exist at that path"),console.error(""),console.error("Solution: Please verify:"),console.error(` - File exists at: ${t}`),console.error(" - File is a valid FBX binary file"),console.error(" - File path matches your public folder structure"),console.error(" - File is not corrupted"));try{const d=await fetch(t),g=d.headers.get("content-type"),f=await d.text();console.error("Response details:",{status:d.status,contentType:g,firstBytes:f.substring(0,100),isHTML:f.trim().startsWith("<!DOCTYPE")||f.trim().startsWith("<html")}),(f.trim().startsWith("<!DOCTYPE")||f.trim().startsWith("<html"))&&console.error("The server returned an HTML page instead of an FBX file. The file path is likely incorrect.")}catch(d){console.error("Could not fetch file for debugging:",d)}return}if(l&&l.animations&&l.animations[n]){let c=l.animations[n];const d={};c.tracks.forEach(f=>{f.name=f.name.replaceAll("mixamorig","");const x=f.name.split(".");if(x[1]==="position"){for(let I=0;I<f.values.length;I++)f.values[I]=f.values[I]*s;d[f.name]=new y.Vector3(f.values[0],f.values[1],f.values[2])}else x[1]==="quaternion"?d[f.name]=new y.Quaternion(f.values[0],f.values[1],f.values[2],f.values[3]):x[1]==="rotation"&&(d[x[0]+".quaternion"]=new y.Quaternion().setFromEuler(new y.Euler(f.values[0],f.values[1],f.values[2],"XYZ")).normalize())});const g={props:d};d["Hips.position"]&&(d["Hips.position"].y<.5?g.lying=!0:g.standing=!0),this.animClips.push({url:t+"-"+n,clip:c,pose:g}),this.playAnimation(t,e,i,n,s)}else{const c="Animation "+t+" (ndx="+n+") not found";console.error(c),l&&l.animations?console.error(`FBX file loaded but has ${l.animations.length} animation(s), requested index ${n}`):console.error(l?"FBX file loaded but contains no animations":"FBX file failed to load or is invalid")}}}stopAnimation(){if(this.currentFBXAction&&(this.currentFBXAction.stop(),this.currentFBXAction=null,console.log("FBX animation action stopped, mixer preserved for lip-sync")),this.mixer&&this.mixer._actions.length===0&&(this.mixer=null,console.log("Mixer destroyed as no actions remain")),this.positionWasLocked?(this.unlockAvatarPosition(),console.log("Position unlocked after FBX animation stopped")):console.log("Position was not locked, no unlock needed"),this.gesture)for(let[e,i]of Object.entries(this.gesture))i.t=this.animClock,i.d=1e3,this.poseTarget.props.hasOwnProperty(e)&&(this.poseTarget.props[e].copy(i),this.poseTarget.props[e].t=this.animClock,this.poseTarget.props[e].d=1e3);let t=this.animQueue.find(e=>e.template.name==="pose");t&&(t.ts[0]=this.animClock),this.setPoseFromTemplate(null)}async playPose(t,e=null,i=5,n=0,s=.01){if(!this.armature)return;let o=this.poseTemplates[t];if(!o){const r=this.animPoses.find(h=>h.url===t+"-"+n);r&&(o=r.pose)}if(o){this.poseName=t,this.mixer=null;let r=this.animQueue.find(h=>h.template.name==="pose");r&&(r.ts[0]=this.animClock+i*1e3+2e3),this.setPoseFromTemplate(o)}else{let h=await new fe.FBXLoader().loadAsync(t,e);if(h&&h.animations&&h.animations[n]){let a=h.animations[n];const u={};a.tracks.forEach(c=>{c.name=c.name.replaceAll("mixamorig","");const d=c.name.split(".");d[1]==="position"?u[c.name]=new y.Vector3(c.values[0]*s,c.values[1]*s,c.values[2]*s):d[1]==="quaternion"?u[c.name]=new y.Quaternion(c.values[0],c.values[1],c.values[2],c.values[3]):d[1]==="rotation"&&(u[d[0]+".quaternion"]=new y.Quaternion().setFromEuler(new y.Euler(c.values[0],c.values[1],c.values[2],"XYZ")).normalize())});const l={props:u};u["Hips.position"]&&(u["Hips.position"].y<.5?l.lying=!0:l.standing=!0),this.animPoses.push({url:t+"-"+n,pose:l}),this.playPose(t,e,i,n,s)}else{const a="Pose "+t+" (ndx="+n+") not found";console.error(a)}}}stopPose(){this.stopAnimation()}playGesture(t,e=3,i=!1,n=1e3){if(!this.armature)return;let s=this.gestureTemplates[t];if(s){this.gestureTimeout&&(clearTimeout(this.gestureTimeout),this.gestureTimeout=null);let r=this.animQueue.findIndex(h=>h.template.name==="talkinghands");r!==-1&&(this.animQueue[r].ts=this.animQueue[r].ts.map(h=>0)),this.gesture=this.propsToThreeObjects(s),i&&(this.gesture=this.mirrorPose(this.gesture)),t==="namaste"&&this.avatar.body==="M"&&(this.gesture["RightArm.quaternion"].rotateTowards(new y.Quaternion(0,1,0,0),-.25),this.gesture["LeftArm.quaternion"].rotateTowards(new y.Quaternion(0,1,0,0),-.25));for(let[h,a]of Object.entries(this.gesture))a.t=this.animClock,a.d=n,this.poseTarget.props.hasOwnProperty(h)&&(this.poseTarget.props[h].copy(a),this.poseTarget.props[h].t=this.animClock,this.poseTarget.props[h].d=n);e&&Number.isFinite(e)&&(this.gestureTimeout=setTimeout(this.stopGesture.bind(this,n),1e3*e))}let o=this.animEmojis[t];if(o&&(o&&o.link&&(o=this.animEmojis[o.link]),o)){this.lookAtCamera(500);const r=this.animFactory(o);if(r.gesture=!0,e&&Number.isFinite(e)){const h=r.ts[0],u=r.ts[r.ts.length-1]-h;if(e*1e3-u>0){const c=[];for(let f=1;f<r.ts.length;f++)c.push(r.ts[f]-r.ts[f-1]);const d=o.template?.rescale||c.map(f=>f/u),g=e*1e3-u;r.ts=r.ts.map((f,x,I)=>x===0?h:I[x-1]+c[x-1]+d[x-1]*g)}else{const c=e*1e3/u;r.ts=r.ts.map(d=>h+c*(d-h))}}this.animQueue.push(r)}}stopGesture(t=1e3){if(this.gestureTimeout&&(clearTimeout(this.gestureTimeout),this.gestureTimeout=null),this.gesture){const i=Object.entries(this.gesture);this.gesture=null;for(const[n,s]of i)this.poseTarget.props.hasOwnProperty(n)&&(this.poseTarget.props[n].copy(this.getPoseTemplateProp(n)),this.poseTarget.props[n].t=this.animClock,this.poseTarget.props[n].d=t)}let e=this.animQueue.findIndex(i=>i.gesture);e!==-1&&this.animQueue.splice(e,1)}ikSolve(t,e=null,i=!1,n=null){const s=new y.Vector3,o=new y.Vector3,r=new y.Vector3,h=new y.Vector3,a=new y.Quaternion,u=new y.Vector3,l=new y.Vector3,c=new y.Vector3,d=this.ikMesh.getObjectByName(t.root);d.position.setFromMatrixPosition(this.armature.getObjectByName(t.root).matrixWorld),d.quaternion.setFromRotationMatrix(this.armature.getObjectByName(t.root).matrixWorld),e&&i&&e.applyQuaternion(this.armature.quaternion).add(d.position);const g=this.ikMesh.getObjectByName(t.effector),f=t.links;f.forEach(I=>{I.bone=this.ikMesh.getObjectByName(I.link),I.bone.quaternion.copy(this.getPoseTemplateProp(I.link+".quaternion"))}),d.updateMatrixWorld(!0);const x=t.iterations||10;if(e)for(let I=0;I<x;I++){let M=!1;for(let p=0,F=f.length;p<F;p++){const C=f[p].bone;C.matrixWorld.decompose(h,a,u),a.invert(),o.setFromMatrixPosition(g.matrixWorld),r.subVectors(o,h),r.applyQuaternion(a),r.normalize(),s.subVectors(e,h),s.applyQuaternion(a),s.normalize();let v=s.dot(r);v>1?v=1:v<-1&&(v=-1),v=Math.acos(v),!(v<1e-5)&&(f[p].minAngle!==void 0&&v<f[p].minAngle&&(v=f[p].minAngle),f[p].maxAngle!==void 0&&v>f[p].maxAngle&&(v=f[p].maxAngle),l.crossVectors(r,s),l.normalize(),N.setFromAxisAngle(l,v),C.quaternion.multiply(N),C.rotation.setFromVector3(c.setFromEuler(C.rotation).clamp(new y.Vector3(f[p].minx!==void 0?f[p].minx:-1/0,f[p].miny!==void 0?f[p].miny:-1/0,f[p].minz!==void 0?f[p].minz:-1/0),new y.Vector3(f[p].maxx!==void 0?f[p].maxx:1/0,f[p].maxy!==void 0?f[p].maxy:1/0,f[p].maxz!==void 0?f[p].maxz:1/0))),C.updateMatrixWorld(!0),M=!0)}if(!M)break}n&&f.forEach(I=>{this.poseTarget.props[I.link+".quaternion"].copy(I.bone.quaternion),this.poseTarget.props[I.link+".quaternion"].t=this.animClock,this.poseTarget.props[I.link+".quaternion"].d=n})}dispose(){this.isRunning=!1,this.stop(),this.stopSpeaking(),this.streamStop(),this.isAvatarOnly?this.armature&&(this.armature.parent&&this.armature.parent.remove(this.armature),this.clearThree(this.armature)):(this.clearThree(this.scene),this.resizeobserver.disconnect(),this.renderer&&(this.renderer.dispose(),this.renderer.domElement&&this.renderer.domElement.parentNode&&this.renderer.domElement.parentNode.removeChild(this.renderer.domElement),this.renderer=null)),this.clearThree(this.ikMesh),this.dynamicbones.dispose()}}const se={apiKey:"sk_ace57ef3ef65a92b9d3bee2a00183b78ca790bc3e10964f2",endpoint:"https://api.elevenlabs.io/v1/text-to-speech",defaultVoice:"21m00Tcm4TlvDq8ikWAM",voices:{rachel:"21m00Tcm4TlvDq8ikWAM",drew:"29vD33N1CtxCmqQRPOHJ",bella:"EXAVITQu4vr4xnSDxMaL",antoni:"ErXwobaYiN019PkySvjV",elli:"MF3mGyEYCl7XYWbV9V6O",josh:"VR6AewLTigWG4xSOukaG"}};function ue(){return{service:"elevenlabs",endpoint:se.endpoint,apiKey:se.apiKey,defaultVoice:se.defaultVoice,voices:se.voices}}function _e(){const T=ue(),t=[];return Object.entries(T.voices).forEach(([e,i])=>{t.push({value:i,label:`${e.charAt(0).toUpperCase()+e.slice(1)} (${T.service})`})}),t}const ge=A.forwardRef(({avatarUrl:T="/avatars/brunette.glb",avatarBody:t="F",mood:e="neutral",ttsLang:i="en",ttsService:n=null,ttsVoice:s=null,ttsApiKey:o=null,bodyMovement:r="idle",movementIntensity:h=.5,showFullAvatar:a=!0,cameraView:u="upper",onReady:l=()=>{},onLoading:c=()=>{},onError:d=()=>{},className:g="",style:f={},animations:x={}},I)=>{const M=A.useRef(null),p=A.useRef(null),[F,C]=A.useState(!0),[v,O]=A.useState(null),[B,_]=A.useState(!1),K=ue(),S=n||K.service;let P;S==="browser"?P={service:"browser",endpoint:"",apiKey:null,defaultVoice:"Google US English"}:S==="elevenlabs"?P={service:"elevenlabs",endpoint:"https://api.elevenlabs.io/v1/text-to-speech",apiKey:o||K.apiKey,defaultVoice:s||K.defaultVoice||se.defaultVoice,voices:K.voices||se.voices}:P={...K,apiKey:o!==null?o:K.apiKey};const W={url:T,body:t,avatarMood:e,ttsLang:S==="browser"?"en-US":i,ttsVoice:s||P.defaultVoice,lipsyncLang:"en",showFullAvatar:a,bodyMovement:r,movementIntensity:h},V={ttsEndpoint:P.endpoint,ttsApikey:P.apiKey,ttsService:S,lipsyncModules:["en"],cameraView:u},Y=A.useCallback(async()=>{if(!(!M.current||p.current))try{if(C(!0),O(null),p.current=new ve(M.current,V),p.current.controls&&(p.current.controls.enableRotate=!1,p.current.controls.enableZoom=!1,p.current.controls.enablePan=!1,p.current.controls.enableDamping=!1),x&&Object.keys(x).length>0&&(p.current.customAnimations=x),await p.current.showAvatar(W,U=>{if(U.lengthComputable){const D=Math.min(100,Math.round(U.loaded/U.total*100));c(D)}}),await new Promise(U=>{const D=()=>{p.current.lipsync&&Object.keys(p.current.lipsync).length>0?U():setTimeout(D,100)};D()}),p.current&&p.current.setShowFullAvatar)try{p.current.setShowFullAvatar(a)}catch(U){console.warn("Error setting full body mode on initialization:",U)}p.current&&p.current.controls&&(p.current.controls.enableRotate=!1,p.current.controls.enableZoom=!1,p.current.controls.enablePan=!1,p.current.controls.enableDamping=!1,p.current.controls.update()),C(!1),_(!0),l(p.current);const H=()=>{document.visibilityState==="visible"?p.current?.start():p.current?.stop()};return document.addEventListener("visibilitychange",H),()=>{document.removeEventListener("visibilitychange",H)}}catch(k){console.error("Error initializing TalkingHead:",k),O(k.message||"Failed to initialize avatar"),C(!1),d(k)}},[T,t,e,i,n,s,o,a,r,h,u]);A.useEffect(()=>(Y(),()=>{p.current&&(p.current.stop(),p.current.dispose(),p.current=null)}),[Y]),A.useEffect(()=>{if(!M.current||!p.current)return;const k=new ResizeObserver(U=>{for(const D of U)p.current&&p.current.onResize&&p.current.onResize()});k.observe(M.current);const H=()=>{p.current&&p.current.onResize&&p.current.onResize()};return window.addEventListener("resize",H),()=>{k.disconnect(),window.removeEventListener("resize",H)}},[B]);const j=A.useCallback(async()=>{if(p.current&&p.current.audioCtx)try{(p.current.audioCtx.state==="suspended"||p.current.audioCtx.state==="interrupted")&&(await p.current.audioCtx.resume(),console.log("Audio context resumed"))}catch(k){console.warn("Failed to resume audio context:",k)}},[]),ae=A.useCallback(async(k,H={})=>{if(p.current&&B)try{await j();const U={...H,lipsyncLang:H.lipsyncLang||W.lipsyncLang||"en"};if(H.onSpeechEnd&&p.current){const D=p.current,ne=D.onAudioEnd;let J=null,ce=0;const Le=600,ke=()=>{if(ce++,ce>Le){J&&(clearInterval(J),J=null);try{H.onSpeechEnd()}catch(me){console.error("Error in onSpeechEnd callback:",me)}return}D&&(!D.isSpeaking||D.isSpeaking===!1)&&(!D.audioPlaylist||D.audioPlaylist.length===0)&&(!D.isAudioPlaying||D.isAudioPlaying===!1)&&(J&&(clearInterval(J),J=null),setTimeout(()=>{try{H.onSpeechEnd()}catch(me){console.error("Error in onSpeechEnd callback:",me)}},100))};setTimeout(()=>{J=setInterval(ke,100)},500)}p.current.lipsync&&Object.keys(p.current.lipsync).length>0?(p.current.setSlowdownRate&&p.current.setSlowdownRate(1.05),p.current.speakText(k,U)):setTimeout(async()=>{await j(),p.current&&p.current.lipsync&&(p.current.setSlowdownRate&&p.current.setSlowdownRate(1.05),p.current.speakText(k,U))},500)}catch(U){console.error("Error speaking text:",U),O(U.message||"Failed to speak text")}},[B,j,W.lipsyncLang]),de=A.useCallback(()=>{p.current&&(p.current.stopSpeaking(),p.current.setSlowdownRate&&p.current.setSlowdownRate(1))},[]),Q=A.useCallback(k=>{p.current&&p.current.setMood(k)},[]),b=A.useCallback(k=>{p.current&&p.current.setSlowdownRate&&p.current.setSlowdownRate(k)},[]),R=A.useCallback((k,H=!1)=>{if(p.current&&p.current.playAnimation){if(x&&x[k]&&(k=x[k]),p.current.setShowFullAvatar)try{p.current.setShowFullAvatar(!0)}catch(D){console.warn("Error setting full body mode:",D)}if(k.includes("."))try{p.current.playAnimation(k,null,10,0,.01,H)}catch(D){console.warn(`Failed to play ${k}:`,D);try{p.current.setBodyMovement("idle")}catch(ne){console.warn("Fallback animation also failed:",ne)}}else{const D=[".fbx",".glb",".gltf"];let ne=!1;for(const J of D)try{p.current.playAnimation(k+J,null,10,0,.01,H),ne=!0;break}catch{}if(!ne){console.warn("Animation not found:",k);try{p.current.setBodyMovement("idle")}catch(J){console.warn("Fallback animation also failed:",J)}}}}},[x]),z=A.useCallback(()=>{p.current&&p.current.onResize&&p.current.onResize()},[]);return A.useImperativeHandle(I,()=>({speakText:ae,stopSpeaking:de,resumeAudioContext:j,setMood:Q,setTimingAdjustment:b,playAnimation:R,isReady:B,talkingHead:p.current,handleResize:z,setBodyMovement:k=>{if(p.current&&p.current.setShowFullAvatar&&p.current.setBodyMovement)try{p.current.setShowFullAvatar(!0),p.current.setBodyMovement(k)}catch(H){console.warn("Error setting body movement:",H)}},setMovementIntensity:k=>p.current?.setMovementIntensity(k),playRandomDance:()=>{if(p.current&&p.current.setShowFullAvatar&&p.current.playRandomDance)try{p.current.setShowFullAvatar(!0),p.current.playRandomDance()}catch(k){console.warn("Error playing random dance:",k)}},playReaction:k=>{if(p.current&&p.current.setShowFullAvatar&&p.current.playReaction)try{p.current.setShowFullAvatar(!0),p.current.playReaction(k)}catch(H){console.warn("Error playing reaction:",H)}},playCelebration:()=>{if(p.current&&p.current.setShowFullAvatar&&p.current.playCelebration)try{p.current.setShowFullAvatar(!0),p.current.playCelebration()}catch(k){console.warn("Error playing celebration:",k)}},setShowFullAvatar:k=>{if(p.current&&p.current.setShowFullAvatar)try{p.current.setShowFullAvatar(k)}catch(H){console.warn("Error setting showFullAvatar:",H)}},lockAvatarPosition:()=>{if(p.current&&p.current.lockAvatarPosition)try{p.current.lockAvatarPosition()}catch(k){console.warn("Error locking avatar position:",k)}},unlockAvatarPosition:()=>{if(p.current&&p.current.unlockAvatarPosition)try{p.current.unlockAvatarPosition()}catch(k){console.warn("Error unlocking avatar position:",k)}}})),te.jsxs("div",{className:`talking-head-avatar ${g}`,style:{width:"100%",height:"100%",position:"relative",...f},children:[te.jsx("div",{ref:M,className:"talking-head-viewer",style:{width:"100%",height:"100%",minHeight:"400px"}}),F&&te.jsx("div",{className:"loading-overlay",style:{position:"absolute",top:"50%",left:"50%",transform:"translate(-50%, -50%)",color:"white",fontSize:"18px",zIndex:10},children:"Loading avatar..."}),v&&te.jsx("div",{className:"error-overlay",style:{position:"absolute",top:"50%",left:"50%",transform:"translate(-50%, -50%)",color:"#ff6b6b",fontSize:"16px",textAlign:"center",zIndex:10,padding:"20px",borderRadius:"8px"},children:v})]})});ge.displayName="TalkingHeadAvatar";const Ie=A.forwardRef(({text:T="Hello! I'm a talking avatar. How are you today?",onLoading:t=()=>{},onError:e=()=>{},onReady:i=()=>{},className:n="",style:s={},avatarConfig:o={}},r)=>{const h=A.useRef(null),a=A.useRef(null),[u,l]=A.useState(!0),[c,d]=A.useState(null),[g,f]=A.useState(!1),x=ue(),I=o.ttsService||x.service,M=I==="browser"?{endpoint:"",apiKey:null,defaultVoice:"Google US English"}:{...x,apiKey:o.ttsApiKey!==void 0&&o.ttsApiKey!==null?o.ttsApiKey:x.apiKey,endpoint:I==="elevenlabs"&&o.ttsApiKey?"https://api.elevenlabs.io/v1/text-to-speech":x.endpoint},p={url:"/avatars/brunette.glb",body:"F",avatarMood:"neutral",ttsLang:I==="browser"?"en-US":"en",ttsVoice:o.ttsVoice||M.defaultVoice,lipsyncLang:"en",showFullAvatar:!0,bodyMovement:"idle",movementIntensity:.5,...o},F={ttsEndpoint:M.endpoint,ttsApikey:M.apiKey,ttsService:I,lipsyncModules:["en"],cameraView:"upper"},C=A.useCallback(async()=>{if(!(!h.current||a.current))try{if(l(!0),d(null),a.current=new ve(h.current,F),await a.current.showAvatar(p,W=>{if(W.lengthComputable){const V=Math.min(100,Math.round(W.loaded/W.total*100));t(V)}}),a.current.morphs&&a.current.morphs.length>0){const W=a.current.morphs[0].morphTargetDictionary;console.log("Available morph targets:",Object.keys(W));const V=Object.keys(W).filter(Y=>Y.startsWith("viseme_"));console.log("Viseme morph targets found:",V),V.length===0&&(console.warn("No viseme morph targets found! Lip-sync will not work properly."),console.log("Expected viseme targets: viseme_aa, viseme_E, viseme_I, viseme_O, viseme_U, viseme_PP, viseme_SS, viseme_TH, viseme_DD, viseme_FF, viseme_kk, viseme_nn, viseme_RR, viseme_CH, viseme_sil"))}if(await new Promise(W=>{const V=()=>{a.current.lipsync&&Object.keys(a.current.lipsync).length>0?(console.log("Lip-sync modules loaded:",Object.keys(a.current.lipsync)),W()):(console.log("Waiting for lip-sync modules to load..."),setTimeout(V,100))};V()}),a.current&&a.current.setShowFullAvatar)try{a.current.setShowFullAvatar(!0),console.log("Avatar initialized in full body mode")}catch(W){console.warn("Error setting full body mode on initialization:",W)}l(!1),f(!0),i(a.current);const P=()=>{document.visibilityState==="visible"?a.current?.start():a.current?.stop()};return document.addEventListener("visibilitychange",P),()=>{document.removeEventListener("visibilitychange",P)}}catch(S){console.error("Error initializing TalkingHead:",S),d(S.message||"Failed to initialize avatar"),l(!1),e(S)}},[]);A.useEffect(()=>(C(),()=>{a.current&&(a.current.stop(),a.current.dispose(),a.current=null)}),[C]);const v=A.useCallback(S=>{if(a.current&&g)try{console.log("Speaking text:",S),console.log("Avatar config:",p),console.log("TalkingHead instance:",a.current),a.current.lipsync&&Object.keys(a.current.lipsync).length>0?(console.log("Lip-sync modules loaded:",Object.keys(a.current.lipsync)),a.current.setSlowdownRate&&(a.current.setSlowdownRate(1.05),console.log("Applied timing adjustment for better lip-sync")),a.current.speakText(S)):(console.warn("Lip-sync modules not ready, waiting..."),setTimeout(()=>{a.current&&a.current.lipsync?(console.log("Lip-sync now ready, speaking..."),a.current.setSlowdownRate&&(a.current.setSlowdownRate(1.05),console.log("Applied timing adjustment for better lip-sync")),a.current.speakText(S)):console.error("Lip-sync still not ready after waiting")},500))}catch(P){console.error("Error speaking text:",P),d(P.message||"Failed to speak text")}else console.warn("Avatar not ready for speaking. isReady:",g,"talkingHeadRef:",!!a.current)},[g,p]),O=A.useCallback(()=>{a.current&&(a.current.stopSpeaking(),a.current.setSlowdownRate&&(a.current.setSlowdownRate(1),console.log("Reset timing to normal")))},[]),B=A.useCallback(S=>{a.current&&a.current.setMood(S)},[]),_=A.useCallback(S=>{a.current&&a.current.setSlowdownRate&&(a.current.setSlowdownRate(S),console.log("Timing adjustment set to:",S))},[]),K=A.useCallback((S,P=!1)=>{if(a.current&&a.current.playAnimation){if(a.current.setShowFullAvatar)try{a.current.setShowFullAvatar(!0)}catch(V){console.warn("Error setting full body mode:",V)}if(S.includes("."))try{a.current.playAnimation(S,null,10,0,.01,P),console.log("Playing animation:",S)}catch(V){console.log(`Failed to play ${S}:`,V);try{a.current.setBodyMovement("idle"),console.log("Fallback to idle animation")}catch(Y){console.warn("Fallback animation also failed:",Y)}}else{const V=[".fbx",".glb",".gltf"];let Y=!1;for(const j of V)try{a.current.playAnimation(S+j,null,10,0,.01,P),console.log("Playing animation:",S+j),Y=!0;break}catch{console.log(`Failed to play ${S}${j}, trying next format...`)}if(!Y){console.warn("Animation system not available or animation not found:",S);try{a.current.setBodyMovement("idle"),console.log("Fallback to idle animation")}catch(j){console.warn("Fallback animation also failed:",j)}}}}else console.warn("Animation system not available or animation not found:",S)},[]);return A.useImperativeHandle(r,()=>({speakText:v,stopSpeaking:O,setMood:B,setTimingAdjustment:_,playAnimation:K,isReady:g,talkingHead:a.current,setBodyMovement:S=>{if(a.current&&a.current.setShowFullAvatar&&a.current.setBodyMovement)try{a.current.setShowFullAvatar(!0),a.current.setBodyMovement(S),console.log("Body movement set with full body mode:",S)}catch(P){console.warn("Error setting body movement:",P)}},setMovementIntensity:S=>a.current?.setMovementIntensity(S),playRandomDance:()=>{if(a.current&&a.current.setShowFullAvatar&&a.current.playRandomDance)try{a.current.setShowFullAvatar(!0),a.current.playRandomDance(),console.log("Random dance played with full body mode")}catch(S){console.warn("Error playing random dance:",S)}},playReaction:S=>{if(a.current&&a.current.setShowFullAvatar&&a.current.playReaction)try{a.current.setShowFullAvatar(!0),a.current.playReaction(S),console.log("Reaction played with full body mode:",S)}catch(P){console.warn("Error playing reaction:",P)}},playCelebration:()=>{if(a.current&&a.current.setShowFullAvatar&&a.current.playCelebration)try{a.current.setShowFullAvatar(!0),a.current.playCelebration(),console.log("Celebration played with full body mode")}catch(S){console.warn("Error playing celebration:",S)}},setShowFullAvatar:S=>{if(a.current&&a.current.setShowFullAvatar)try{a.current.setShowFullAvatar(S),console.log("Show full avatar set to:",S)}catch(P){console.warn("Error setting showFullAvatar:",P)}},lockAvatarPosition:()=>{if(a.current&&a.current.lockAvatarPosition)try{a.current.lockAvatarPosition()}catch(S){console.warn("Error locking avatar position:",S)}},unlockAvatarPosition:()=>{if(a.current&&a.current.unlockAvatarPosition)try{a.current.unlockAvatarPosition()}catch(S){console.warn("Error unlocking avatar position:",S)}}})),te.jsxs("div",{className:`talking-head-container ${n}`,style:s,children:[te.jsx("div",{ref:h,className:"talking-head-viewer",style:{width:"100%",height:"100%",minHeight:"400px"}}),u&&te.jsx("div",{className:"loading-overlay",style:{position:"absolute",top:"50%",left:"50%",transform:"translate(-50%, -50%)",color:"white",fontSize:"18px",zIndex:10},children:"Loading avatar..."}),c&&te.jsx("div",{className:"error-overlay",style:{position:"absolute",top:"50%",left:"50%",transform:"translate(-50%, -50%)",color:"#ff6b6b",fontSize:"16px",textAlign:"center",zIndex:10,padding:"20px",borderRadius:"8px"},children:c})]})});Ie.displayName="TalkingHeadComponent";const Ae=A.forwardRef(({curriculumData:T=null,avatarConfig:t={},animations:e={},onLessonStart:i=()=>{},onLessonComplete:n=()=>{},onQuestionAnswer:s=()=>{},onCurriculumComplete:o=()=>{},onCustomAction:r=()=>{},autoStart:h=!1},a)=>{const u=A.useRef(null),l=A.useRef({currentModuleIndex:0,currentLessonIndex:0,currentQuestionIndex:0,isTeaching:!1,isQuestionMode:!1,lessonCompleted:!1,curriculumCompleted:!1,score:0,totalQuestions:0}),c=A.useRef({onLessonStart:i,onLessonComplete:n,onQuestionAnswer:s,onCurriculumComplete:o,onCustomAction:r}),d=A.useRef(null),g=A.useRef(null),f=A.useRef(null),x=A.useRef(null),I=A.useRef(null),M=A.useRef(null),p=A.useRef(null),F=A.useRef(T?.curriculum||{title:"Default Curriculum",description:"No curriculum data provided",language:"en",modules:[]}),C=A.useRef({avatarUrl:t.avatarUrl||"/avatars/brunette.glb",avatarBody:t.avatarBody||"F",mood:t.mood||"happy",ttsLang:t.ttsLang||"en",ttsService:t.ttsService||null,ttsVoice:t.ttsVoice||null,ttsApiKey:t.ttsApiKey||null,bodyMovement:t.bodyMovement||"gesturing",movementIntensity:t.movementIntensity||.7,showFullAvatar:t.showFullAvatar!==void 0?t.showFullAvatar:!0,animations:e,lipsyncLang:"en"});A.useEffect(()=>{c.current={onLessonStart:i,onLessonComplete:n,onQuestionAnswer:s,onCurriculumComplete:o,onCustomAction:r}},[i,n,s,o,r]),A.useEffect(()=>{F.current=T?.curriculum||{title:"Default Curriculum",description:"No curriculum data provided",language:"en",modules:[]},C.current={avatarUrl:t.avatarUrl||"/avatars/brunette.glb",avatarBody:t.avatarBody||"F",mood:t.mood||"happy",ttsLang:t.ttsLang||"en",ttsService:t.ttsService||null,ttsVoice:t.ttsVoice||null,ttsApiKey:t.ttsApiKey||null,bodyMovement:t.bodyMovement||"gesturing",movementIntensity:t.movementIntensity||.7,showFullAvatar:t.showFullAvatar!==void 0?t.showFullAvatar:!0,animations:e,lipsyncLang:"en"}},[T,t,e]);const v=A.useCallback(()=>(F.current||{modules:[]}).modules[l.current.currentModuleIndex]?.lessons[l.current.currentLessonIndex],[]),O=A.useCallback(()=>v()?.questions[l.current.currentQuestionIndex],[v]),B=A.useCallback((b,R)=>R.type==="multiple_choice"||R.type==="true_false"?b===R.answer:R.type==="code_test"&&typeof b=="object"&&b!==null?b.passed===!0:!1,[]),_=A.useCallback(()=>{l.current.lessonCompleted=!0,l.current.isQuestionMode=!1;const b=l.current.totalQuestions>0?Math.round(l.current.score/l.current.totalQuestions*100):100;let R="Congratulations! You've completed this lesson";if(l.current.totalQuestions>0?R+=` with a score of ${l.current.score} out of ${l.current.totalQuestions} (${b}%). `:R+="! ",b>=80?R+="Excellent work! You have a great understanding of this topic.":b>=60?R+="Good job! You understand most of the concepts.":R+="Keep practicing! You're making progress.",c.current.onLessonComplete({moduleIndex:l.current.currentModuleIndex,lessonIndex:l.current.currentLessonIndex,score:l.current.score,totalQuestions:l.current.totalQuestions,percentage:b}),c.current.onCustomAction({type:"lessonComplete",moduleIndex:l.current.currentModuleIndex,lessonIndex:l.current.currentLessonIndex,score:l.current.score,totalQuestions:l.current.totalQuestions,percentage:b}),u.current){if(u.current.setMood("happy"),e.lessonComplete)try{u.current.playAnimation(e.lessonComplete,!0)}catch{u.current.playCelebration()}const z=F.current||{modules:[]},k=z.modules[l.current.currentModuleIndex],H=l.current.currentLessonIndex<(k?.lessons?.length||0)-1,U=l.current.currentModuleIndex<(z.modules?.length||0)-1,D=H||U,ne=C.current||{lipsyncLang:"en"};D?u.current.speakText(R,{lipsyncLang:ne.lipsyncLang,onSpeechEnd:()=>{g.current&&g.current()}}):u.current.speakText(R,{lipsyncLang:ne.lipsyncLang,onSpeechEnd:()=>{I.current&&I.current()}})}},[e.lessonComplete]),K=A.useCallback(()=>{l.current.curriculumCompleted=!0;const b=F.current||{modules:[]};if(c.current.onCurriculumComplete({modules:b.modules.length,totalLessons:b.modules.reduce((R,z)=>R+z.lessons.length,0)}),u.current){if(u.current.setMood("celebrating"),e.curriculumComplete)try{u.current.playAnimation(e.curriculumComplete,!0)}catch{u.current.playCelebration()}const R=C.current||{lipsyncLang:"en"};u.current.speakText("Amazing! You've completed the entire curriculum! You're now ready to move on to more advanced topics. Well done!",{lipsyncLang:R.lipsyncLang})}},[e.curriculumComplete]),S=A.useCallback(()=>{const b=v();l.current.isQuestionMode=!0,l.current.currentQuestionIndex=0,l.current.totalQuestions=b?.questions?.length||0;const R=O();if(R&&c.current.onCustomAction({type:"questionStart",moduleIndex:l.current.currentModuleIndex,lessonIndex:l.current.currentLessonIndex,questionIndex:l.current.currentQuestionIndex,totalQuestions:l.current.totalQuestions,question:R}),u.current&&R){if(u.current.setMood("curious"),e.questionStart)try{u.current.playAnimation(e.questionStart,!0)}catch(k){console.warn("Failed to play questionStart animation:",k)}const z=C.current||{lipsyncLang:"en"};R.type==="code_test"?u.current.speakText(`Let's test your coding skills! Here's your first challenge: ${R.question}`,{lipsyncLang:z.lipsyncLang}):R.type==="multiple_choice"?u.current.speakText(`Now let me ask you some questions. Here's the first one: ${R.question}`,{lipsyncLang:z.lipsyncLang}):R.type==="true_false"?u.current.speakText(`Let's start with some true or false questions. First question: ${R.question}`,{lipsyncLang:z.lipsyncLang}):u.current.speakText(`Now let me ask you some questions. Here's the first one: ${R.question}`,{lipsyncLang:z.lipsyncLang})}else if(u.current){const z=C.current||{lipsyncLang:"en"};u.current.speakText("Now let me ask you some questions to test your understanding.",{lipsyncLang:z.lipsyncLang})}},[e.questionStart,v,O]),P=A.useCallback(()=>{const b=v();if(l.current.currentQuestionIndex<(b?.questions?.length||0)-1){l.current.currentQuestionIndex+=1;const R=O();if(R&&c.current.onCustomAction({type:"nextQuestion",moduleIndex:l.current.currentModuleIndex,lessonIndex:l.current.currentLessonIndex,questionIndex:l.current.currentQuestionIndex,totalQuestions:l.current.totalQuestions,question:R}),u.current&&R){if(u.current.setMood("happy"),u.current.setBodyMovement("idle"),e.nextQuestion)try{u.current.playAnimation(e.nextQuestion,!0)}catch(k){console.warn("Failed to play nextQuestion animation:",k)}const z=C.current||{lipsyncLang:"en"};R.type==="code_test"?u.current.speakText(`Great! Now let's move on to your next coding challenge: ${R.question}`,{lipsyncLang:z.lipsyncLang}):R.type==="multiple_choice"?u.current.speakText(`Alright! Here's your next question: ${R.question}`,{lipsyncLang:z.lipsyncLang}):R.type==="true_false"?u.current.speakText(`Now let's try this one: ${R.question}`,{lipsyncLang:z.lipsyncLang}):u.current.speakText(`Here's the next question: ${R.question}`,{lipsyncLang:z.lipsyncLang})}}else f.current&&f.current()},[e.nextQuestion,v,O]),W=A.useCallback(()=>{const b=F.current||{modules:[]},R=b.modules[l.current.currentModuleIndex];l.current.currentLessonIndex<(R?.lessons?.length||0)-1?(l.current.currentLessonIndex+=1,l.current.currentQuestionIndex=0,l.current.lessonCompleted=!1,l.current.isQuestionMode=!1,l.current.isTeaching=!1,l.current.score=0,l.current.totalQuestions=0,c.current.onCustomAction({type:"lessonStart",moduleIndex:l.current.currentModuleIndex,lessonIndex:l.current.currentLessonIndex}),u.current&&(u.current.setMood("happy"),u.current.setBodyMovement("idle"),d.current&&d.current())):l.current.currentModuleIndex<(b.modules?.length||0)-1?(l.current.currentModuleIndex+=1,l.current.currentLessonIndex=0,l.current.currentQuestionIndex=0,l.current.lessonCompleted=!1,l.current.isQuestionMode=!1,l.current.isTeaching=!1,l.current.score=0,l.current.totalQuestions=0,c.current.onCustomAction({type:"lessonStart",moduleIndex:l.current.currentModuleIndex,lessonIndex:l.current.currentLessonIndex}),u.current&&(u.current.setMood("happy"),u.current.setBodyMovement("idle"),d.current&&d.current())):I.current&&I.current()},[]),V=A.useCallback(()=>{const b=v();let R=null;if(b?.avatar_script&&b?.body){const z=b.avatar_script.trim(),k=b.body.trim(),H=z.match(/[.!?]$/)?" ":". ";R=`${z}${H}${k}`}else R=b?.avatar_script||b?.body||null;if(u.current&&u.current.isReady&&R){l.current.isTeaching=!0,l.current.isQuestionMode=!1,u.current.setMood("happy");let z=!1;if(e.teaching)try{u.current.playAnimation(e.teaching,!0),z=!0}catch(H){console.warn("Failed to play teaching animation:",H)}z||u.current.setBodyMovement("gesturing");const k=C.current||{lipsyncLang:"en"};c.current.onLessonStart({moduleIndex:l.current.currentModuleIndex,lessonIndex:l.current.currentLessonIndex,lesson:b}),c.current.onCustomAction({type:"teachingStart",moduleIndex:l.current.currentModuleIndex,lessonIndex:l.current.currentLessonIndex,lesson:b}),u.current.speakText(R,{lipsyncLang:k.lipsyncLang,onSpeechEnd:()=>{l.current.isTeaching=!1,b.questions&&b.questions.length>0?M.current&&M.current():f.current&&f.current()}})}},[e.teaching,v]),Y=A.useCallback(b=>{const R=O(),z=B(b,R);if(z&&(l.current.score+=1),c.current.onQuestionAnswer({moduleIndex:l.current.currentModuleIndex,lessonIndex:l.current.currentLessonIndex,questionIndex:l.current.currentQuestionIndex,answer:b,isCorrect:z,question:R}),u.current)if(z){if(u.current.setMood("happy"),e.correct)try{u.current.playReaction("happy")}catch{u.current.setBodyMovement("happy")}u.current.setBodyMovement("gesturing");const k=R.type==="code_test"?`Great job! Your code passed all the tests! ${R.explanation||""}`:`Excellent! That's correct! ${R.explanation||""}`,H=C.current||{lipsyncLang:"en"};u.current.speakText(k,{lipsyncLang:H.lipsyncLang,onSpeechEnd:()=>{x.current&&x.current()}})}else{if(u.current.setMood("sad"),e.incorrect)try{u.current.playAnimation(e.incorrect,!0)}catch{u.current.setBodyMovement("idle")}u.current.setBodyMovement("gesturing");const k=R.type==="code_test"?`Your code didn't pass all the tests. ${R.explanation||"Try again!"}`:`Not quite right, but don't worry! ${R.explanation||""} Let's move on to the next question.`,H=C.current||{lipsyncLang:"en"};u.current.speakText(k,{lipsyncLang:H.lipsyncLang,onSpeechEnd:()=>{x.current&&x.current()}})}else x.current&&x.current()},[e.correct,e.incorrect,O,B]),j=A.useCallback(b=>{const R=O();if(!b||typeof b!="object"){console.error("Invalid code test result format. Expected object with {passed: boolean, ...}");return}if(R?.type!=="code_test"){console.warn("Current question is not a code test. Use handleAnswerSelect for other question types.");return}const z={passed:b.passed===!0,results:b.results||[],output:b.output||"",error:b.error||null,executionTime:b.executionTime||null,testCount:b.testCount||0,passedCount:b.passedCount||0,failedCount:b.failedCount||0};c.current.onCustomAction({type:"codeTestSubmitted",moduleIndex:l.current.currentModuleIndex,lessonIndex:l.current.currentLessonIndex,questionIndex:l.current.currentQuestionIndex,testResult:z,question:R}),p.current&&p.current(z)},[O,B]),ae=A.useCallback(()=>{l.current.currentModuleIndex=0,l.current.currentLessonIndex=0,l.current.currentQuestionIndex=0,l.current.isTeaching=!1,l.current.isQuestionMode=!1,l.current.lessonCompleted=!1,l.current.curriculumCompleted=!1,l.current.score=0,l.current.totalQuestions=0},[]),de=A.useCallback(b=>{console.log("Avatar is ready!",b);const R=v(),z=R?.avatar_script||R?.body;h&&z&&setTimeout(()=>{d.current&&d.current()},50)},[h,v]);A.useLayoutEffect(()=>{d.current=V,g.current=W,f.current=_,x.current=P,I.current=K,M.current=S,p.current=Y}),A.useImperativeHandle(a,()=>({startTeaching:V,startQuestions:S,handleAnswerSelect:Y,handleCodeTestResult:j,nextQuestion:P,nextLesson:W,completeLesson:_,completeCurriculum:K,resetCurriculum:ae,getState:()=>({...l.current}),getCurrentQuestion:()=>O(),getCurrentLesson:()=>v(),getAvatarRef:()=>u.current,speakText:async(b,R={})=>{await u.current?.resumeAudioContext?.();const z=C.current||{lipsyncLang:"en"};u.current?.speakText(b,{...R,lipsyncLang:R.lipsyncLang||z.lipsyncLang})},resumeAudioContext:async()=>{if(u.current?.resumeAudioContext)return await u.current.resumeAudioContext();const b=u.current?.talkingHead;if(b?.audioCtx){const R=b.audioCtx;if(R.state==="suspended"||R.state==="interrupted")try{await R.resume(),console.log("Audio context resumed via talkingHead")}catch(z){console.warn("Failed to resume audio context:",z)}}else console.warn("Audio context not available yet")},stopSpeaking:()=>u.current?.stopSpeaking(),setMood:b=>u.current?.setMood(b),playAnimation:(b,R)=>u.current?.playAnimation(b,R),setBodyMovement:b=>u.current?.setBodyMovement(b),setMovementIntensity:b=>u.current?.setMovementIntensity(b),playRandomDance:()=>u.current?.playRandomDance(),playReaction:b=>u.current?.playReaction(b),playCelebration:()=>u.current?.playCelebration(),setShowFullAvatar:b=>u.current?.setShowFullAvatar(b),setTimingAdjustment:b=>u.current?.setTimingAdjustment(b),lockAvatarPosition:()=>u.current?.lockAvatarPosition(),unlockAvatarPosition:()=>u.current?.unlockAvatarPosition(),triggerCustomAction:(b,R)=>{c.current.onCustomAction({type:b,...R,state:{...l.current}})},handleResize:()=>u.current?.handleResize(),isAvatarReady:()=>u.current?.isReady||!1}),[V,S,Y,j,P,W,_,K,ae,O,v]);const Q=C.current||{avatarUrl:"/avatars/brunette.glb",avatarBody:"F",mood:"happy",ttsLang:"en",ttsService:null,ttsVoice:null,ttsApiKey:null,bodyMovement:"gesturing",movementIntensity:.7,showFullAvatar:!0,animations:e};return te.jsx("div",{style:{width:"100%",height:"100%"},children:te.jsx(ge,{ref:u,avatarUrl:Q.avatarUrl,avatarBody:Q.avatarBody,mood:Q.mood,ttsLang:Q.ttsLang,ttsService:Q.ttsService,ttsVoice:Q.ttsVoice,ttsApiKey:Q.ttsApiKey,bodyMovement:Q.bodyMovement,movementIntensity:Q.movementIntensity,showFullAvatar:Q.showFullAvatar,cameraView:"upper",animations:Q.animations,onReady:de,onLoading:()=>{},onError:b=>{console.error("Avatar error:",b)}})})});Ae.displayName="CurriculumLearning";const ye={dance:{name:"dance",type:"code-based",variations:["dancing","dancing2","dancing3"],loop:!0,duration:1e4,description:"Celebration dance animation with multiple variations"},happy:{name:"happy",type:"code-based",loop:!0,duration:5e3,description:"Happy, upbeat body movement"},surprised:{name:"surprised",type:"code-based",loop:!1,duration:3e3,description:"Surprised reaction with quick movements"},thinking:{name:"thinking",type:"code-based",loop:!0,duration:8e3,description:"Thoughtful, contemplative movement"},nodding:{name:"nodding",type:"code-based",loop:!1,duration:2e3,description:"Nodding agreement gesture"},shaking:{name:"shaking",type:"code-based",loop:!1,duration:2e3,description:"Shaking head disagreement gesture"},celebration:{name:"celebration",type:"code-based",loop:!1,duration:3e3,description:"Celebration animation for achievements"},energetic:{name:"energetic",type:"code-based",loop:!0,duration:6e3,description:"High-energy, enthusiastic movement"},swaying:{name:"swaying",type:"code-based",loop:!0,duration:8e3,description:"Gentle swaying motion"},bouncing:{name:"bouncing",type:"code-based",loop:!0,duration:4e3,description:"Bouncy, playful movement"},gesturing:{name:"gesturing",type:"code-based",loop:!0,duration:5e3,description:"Teaching gesture animation"},walking:{name:"walking",type:"code-based",loop:!0,duration:6e3,description:"Walking motion"},prancing:{name:"prancing",type:"code-based",loop:!0,duration:4e3,description:"Playful prancing movement"},excited:{name:"excited",type:"code-based",loop:!0,duration:5e3,description:"Excited, energetic movement"}},Ke=T=>ye[T]||null,Je=T=>ye.hasOwnProperty(T);exports.CurriculumLearning=Ae;exports.TalkingHeadAvatar=ge;exports.TalkingHeadComponent=Ie;exports.animations=ye;exports.getActiveTTSConfig=ue;exports.getAnimation=Ke;exports.getVoiceOptions=_e;exports.hasAnimation=Je;
|
package/dist/index.js
CHANGED
|
@@ -7206,16 +7206,12 @@ const $e = ye(({
|
|
|
7206
7206
|
D ? u.current.speakText(R, {
|
|
7207
7207
|
lipsyncLang: ne.lipsyncLang,
|
|
7208
7208
|
onSpeechEnd: () => {
|
|
7209
|
-
|
|
7210
|
-
g.current && g.current();
|
|
7211
|
-
}, 100);
|
|
7209
|
+
g.current && g.current();
|
|
7212
7210
|
}
|
|
7213
7211
|
}) : u.current.speakText(R, {
|
|
7214
7212
|
lipsyncLang: ne.lipsyncLang,
|
|
7215
7213
|
onSpeechEnd: () => {
|
|
7216
|
-
|
|
7217
|
-
I.current && I.current();
|
|
7218
|
-
}, 100);
|
|
7214
|
+
I.current && I.current();
|
|
7219
7215
|
}
|
|
7220
7216
|
});
|
|
7221
7217
|
}
|
|
@@ -7297,15 +7293,11 @@ const $e = ye(({
|
|
|
7297
7293
|
type: "lessonStart",
|
|
7298
7294
|
moduleIndex: l.current.currentModuleIndex,
|
|
7299
7295
|
lessonIndex: l.current.currentLessonIndex
|
|
7300
|
-
}), u.current && (u.current.setMood("happy"), u.current.setBodyMovement("idle"),
|
|
7301
|
-
d.current && d.current();
|
|
7302
|
-
}, 100))) : l.current.currentModuleIndex < (b.modules?.length || 0) - 1 ? (l.current.currentModuleIndex += 1, l.current.currentLessonIndex = 0, l.current.currentQuestionIndex = 0, l.current.lessonCompleted = !1, l.current.isQuestionMode = !1, l.current.isTeaching = !1, l.current.score = 0, l.current.totalQuestions = 0, c.current.onCustomAction({
|
|
7296
|
+
}), u.current && (u.current.setMood("happy"), u.current.setBodyMovement("idle"), d.current && d.current())) : l.current.currentModuleIndex < (b.modules?.length || 0) - 1 ? (l.current.currentModuleIndex += 1, l.current.currentLessonIndex = 0, l.current.currentQuestionIndex = 0, l.current.lessonCompleted = !1, l.current.isQuestionMode = !1, l.current.isTeaching = !1, l.current.score = 0, l.current.totalQuestions = 0, c.current.onCustomAction({
|
|
7303
7297
|
type: "lessonStart",
|
|
7304
7298
|
moduleIndex: l.current.currentModuleIndex,
|
|
7305
7299
|
lessonIndex: l.current.currentLessonIndex
|
|
7306
|
-
}), u.current && (u.current.setMood("happy"), u.current.setBodyMovement("idle"),
|
|
7307
|
-
d.current && d.current();
|
|
7308
|
-
}, 100))) : I.current && I.current();
|
|
7300
|
+
}), u.current && (u.current.setMood("happy"), u.current.setBodyMovement("idle"), d.current && d.current())) : I.current && I.current();
|
|
7309
7301
|
}, []), V = M(() => {
|
|
7310
7302
|
const b = v();
|
|
7311
7303
|
let R = null;
|
|
@@ -7337,9 +7329,7 @@ const $e = ye(({
|
|
|
7337
7329
|
}), u.current.speakText(R, {
|
|
7338
7330
|
lipsyncLang: L.lipsyncLang,
|
|
7339
7331
|
onSpeechEnd: () => {
|
|
7340
|
-
l.current.isTeaching = !1,
|
|
7341
|
-
b.questions && b.questions.length > 0 ? H.current && H.current() : f.current && f.current();
|
|
7342
|
-
}, 50);
|
|
7332
|
+
l.current.isTeaching = !1, b.questions && b.questions.length > 0 ? H.current && H.current() : f.current && f.current();
|
|
7343
7333
|
}
|
|
7344
7334
|
});
|
|
7345
7335
|
}
|
|
@@ -7365,9 +7355,7 @@ const $e = ye(({
|
|
|
7365
7355
|
u.current.speakText(L, {
|
|
7366
7356
|
lipsyncLang: C.lipsyncLang,
|
|
7367
7357
|
onSpeechEnd: () => {
|
|
7368
|
-
|
|
7369
|
-
x.current && x.current();
|
|
7370
|
-
}, 50);
|
|
7358
|
+
x.current && x.current();
|
|
7371
7359
|
}
|
|
7372
7360
|
});
|
|
7373
7361
|
} else {
|
|
@@ -7382,9 +7370,7 @@ const $e = ye(({
|
|
|
7382
7370
|
u.current.speakText(L, {
|
|
7383
7371
|
lipsyncLang: C.lipsyncLang,
|
|
7384
7372
|
onSpeechEnd: () => {
|
|
7385
|
-
|
|
7386
|
-
x.current && x.current();
|
|
7387
|
-
}, 50);
|
|
7373
|
+
x.current && x.current();
|
|
7388
7374
|
}
|
|
7389
7375
|
});
|
|
7390
7376
|
}
|
|
@@ -7425,7 +7411,7 @@ const $e = ye(({
|
|
|
7425
7411
|
const R = v(), w = R?.avatar_script || R?.body;
|
|
7426
7412
|
h && w && setTimeout(() => {
|
|
7427
7413
|
d.current && d.current();
|
|
7428
|
-
},
|
|
7414
|
+
}, 50);
|
|
7429
7415
|
}, [h, v]);
|
|
7430
7416
|
Ce(() => {
|
|
7431
7417
|
d.current = V, g.current = W, f.current = K, x.current = F, I.current = J, H.current = S, p.current = j;
|
package/package.json
CHANGED
|
@@ -213,13 +213,10 @@ const CurriculumLearning = forwardRef(({
|
|
|
213
213
|
avatarRef.current.speakText(feedbackMessage, {
|
|
214
214
|
lipsyncLang: config.lipsyncLang,
|
|
215
215
|
onSpeechEnd: () => {
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
nextLessonRef.current();
|
|
221
|
-
}
|
|
222
|
-
}, 100);
|
|
216
|
+
// Immediately transition to next lesson with no delay
|
|
217
|
+
if (nextLessonRef.current) {
|
|
218
|
+
nextLessonRef.current();
|
|
219
|
+
}
|
|
223
220
|
}
|
|
224
221
|
});
|
|
225
222
|
} else {
|
|
@@ -227,12 +224,10 @@ const CurriculumLearning = forwardRef(({
|
|
|
227
224
|
avatarRef.current.speakText(feedbackMessage, {
|
|
228
225
|
lipsyncLang: config.lipsyncLang,
|
|
229
226
|
onSpeechEnd: () => {
|
|
230
|
-
//
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
}
|
|
235
|
-
}, 100);
|
|
227
|
+
// Immediately complete curriculum with no delay
|
|
228
|
+
if (completeCurriculumRef.current) {
|
|
229
|
+
completeCurriculumRef.current();
|
|
230
|
+
}
|
|
236
231
|
}
|
|
237
232
|
});
|
|
238
233
|
}
|
|
@@ -405,12 +400,10 @@ const CurriculumLearning = forwardRef(({
|
|
|
405
400
|
avatarRef.current.setMood("happy");
|
|
406
401
|
avatarRef.current.setBodyMovement("idle");
|
|
407
402
|
|
|
408
|
-
//
|
|
409
|
-
setTimeout(() => {
|
|
403
|
+
// Immediately start teaching the next lesson
|
|
410
404
|
if (startTeachingRef.current) {
|
|
411
405
|
startTeachingRef.current();
|
|
412
406
|
}
|
|
413
|
-
}, 100);
|
|
414
407
|
}
|
|
415
408
|
} else {
|
|
416
409
|
// No more lessons in current module - check if there's a next module
|
|
@@ -438,12 +431,10 @@ const CurriculumLearning = forwardRef(({
|
|
|
438
431
|
avatarRef.current.setMood("happy");
|
|
439
432
|
avatarRef.current.setBodyMovement("idle");
|
|
440
433
|
|
|
441
|
-
//
|
|
442
|
-
setTimeout(() => {
|
|
434
|
+
// Immediately start teaching the next lesson
|
|
443
435
|
if (startTeachingRef.current) {
|
|
444
436
|
startTeachingRef.current();
|
|
445
437
|
}
|
|
446
|
-
}, 100);
|
|
447
438
|
}
|
|
448
439
|
} else {
|
|
449
440
|
// No more modules or lessons - complete curriculum
|
|
@@ -514,20 +505,18 @@ const CurriculumLearning = forwardRef(({
|
|
|
514
505
|
lipsyncLang: config.lipsyncLang,
|
|
515
506
|
onSpeechEnd: () => {
|
|
516
507
|
stateRef.current.isTeaching = false;
|
|
517
|
-
//
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
startQuestionsRef.current();
|
|
508
|
+
// Immediately transition to next step with no delay
|
|
509
|
+
if (currentLesson.questions && currentLesson.questions.length > 0) {
|
|
510
|
+
// Use ref to avoid circular dependency
|
|
511
|
+
if (startQuestionsRef.current) {
|
|
512
|
+
startQuestionsRef.current();
|
|
523
513
|
}
|
|
524
514
|
} else {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
}
|
|
515
|
+
// No questions, complete the lesson using ref to avoid circular dependency
|
|
516
|
+
if (completeLessonRef.current) {
|
|
517
|
+
completeLessonRef.current();
|
|
529
518
|
}
|
|
530
|
-
}
|
|
519
|
+
}
|
|
531
520
|
}
|
|
532
521
|
});
|
|
533
522
|
}
|
|
@@ -572,12 +561,10 @@ const CurriculumLearning = forwardRef(({
|
|
|
572
561
|
avatarRef.current.speakText(successMessage, {
|
|
573
562
|
lipsyncLang: config.lipsyncLang,
|
|
574
563
|
onSpeechEnd: () => {
|
|
575
|
-
//
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
}
|
|
580
|
-
}, 50);
|
|
564
|
+
// Immediately move to next question with no delay
|
|
565
|
+
if (nextQuestionRef.current) {
|
|
566
|
+
nextQuestionRef.current();
|
|
567
|
+
}
|
|
581
568
|
}
|
|
582
569
|
});
|
|
583
570
|
} else {
|
|
@@ -600,12 +587,10 @@ const CurriculumLearning = forwardRef(({
|
|
|
600
587
|
avatarRef.current.speakText(failureMessage, {
|
|
601
588
|
lipsyncLang: config.lipsyncLang,
|
|
602
589
|
onSpeechEnd: () => {
|
|
603
|
-
//
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
}
|
|
608
|
-
}, 50);
|
|
590
|
+
// Immediately move to next question with no delay
|
|
591
|
+
if (nextQuestionRef.current) {
|
|
592
|
+
nextQuestionRef.current();
|
|
593
|
+
}
|
|
609
594
|
}
|
|
610
595
|
});
|
|
611
596
|
}
|
|
@@ -681,11 +666,12 @@ const CurriculumLearning = forwardRef(({
|
|
|
681
666
|
// Check if there's teaching content (either avatar_script or body)
|
|
682
667
|
const hasTeachingContent = currentLesson?.avatar_script || currentLesson?.body;
|
|
683
668
|
if (autoStart && hasTeachingContent) {
|
|
669
|
+
// Immediate start with minimal delay for initialization
|
|
684
670
|
setTimeout(() => {
|
|
685
671
|
if (startTeachingRef.current) {
|
|
686
672
|
startTeachingRef.current();
|
|
687
673
|
}
|
|
688
|
-
},
|
|
674
|
+
}, 50);
|
|
689
675
|
}
|
|
690
676
|
}, [autoStart, getCurrentLesson]);
|
|
691
677
|
|