@sage-rsc/talking-head-react 1.0.71 → 1.0.72
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
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:i});if(!s.ok)throw new Error(`Azure TTS error: ${s.status} ${s.statusText}`);const o=await s.arrayBuffer(),l=await this.audioCtx.decodeAudioData(o);console.log("Analyzing audio for precise lip-sync...");const h=await this.audioAnalyzer.analyzeAudio(l,e);console.log("Azure TTS Audio Analysis:",{text:e,audioDuration:l.duration,visemeCount:h.visemes.length,wordCount:h.words.length,features:{onsets:h.features.onsets.length,boundaries:h.features.phonemeBoundaries.length}});const r=[];for(let a=0;a<h.visemes.length;a++){const c=h.visemes[a],d=c.startTime*1e3,g=c.duration*1e3,y=c.intensity;r.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,y,0]}})}const u=[...t.anim,...r];this.audioPlaylist.push({anim:u,audio:l}),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,l)=>{l>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 n={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"&&(n.headers.Authorization="Bearer "+await this.opt.jwtGet());const i=await fetch(this.opt.ttsEndpoint+(this.opt.ttsApikey?"?key="+this.opt.ttsApikey:""),n),s=await i.json();if(i.status===200&&s&&s.audioContent){const o=this.b64ToArrayBuffer(s.audioContent),l=await this.audioCtx.decodeAudioData(o);this.speakWithHands();const h=[0];let r=0;t.text.forEach((c,d)=>{if(d>0){let g=h[h.length-1];s.timepoints[r]&&(g=s.timepoints[r].timeSeconds*1e3,s.timepoints[r].markName===""+c.mark&&r++),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 a=1e3*l.duration;a>this.opt.ttsTrimEnd&&(a=a-this.opt.ttsTrimEnd),u[u.length-1].duration=a-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:l}),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 n=e.emoji.dt.reduce((i,s)=>i+s,0);this.animQueue.push(this.animFactory(e.emoji)),setTimeout(this.startSpeaking.bind(this),n,!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==="deepgram"?await this.synthesizeWithDeepgramTTS(e):this.opt.ttsService==="azure"?await this.synthesizeWithAzureTTS(e):await this.synthesizeWithExternalTTS(e)}catch(n){console.error("Error:",n),this.startSpeaking(!0)}}else e.anim?(this.onSubtitles=e.onSubtitles||null,this.resetLips(),e.mood&&this.setMood(e.mood),e.anim.forEach((n,i)=>{for(let s=0;s<n.ts.length;s++)n.ts[s]=this.animClock+10*i;this.animQueue.push(n)}),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,n=null,i=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=n,this.onMetrics=s,t.sampleRate!==void 0){const l=t.sampleRate;typeof l=="number"&&l>=8e3&&l<=96e3?l!==this.audioCtx.sampleRate&&this.initAudioGraph(l):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 l=this.audioCtx.audioWorklet.addModule(ct.href),h=new Promise((r,u)=>setTimeout(()=>u(new Error("Worklet loading timed out")),5e3));await Promise.race([l,h]),this.workletLoaded=!0}catch(l){throw console.error("Failed to load audio worklet:",l),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=l=>{if(l.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(l.data.type==="playback-ended"&&(this._streamPause(),this.onAudioEnd))try{this.onAudioEnd()}catch{}if(this.onMetrics&&l.data.type==="metrics")try{this.onMetrics(l.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=i||null,this.audioCtx.state==="suspended"||this.audioCtx.state==="interrupted"){const l=this.audioCtx.resume(),h=new Promise((r,u)=>setTimeout(()=>u("p2"),1e3));try{await Promise.race([l,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 n=0;n<t.visemes.length;n++){const i=t.visemes[n],s=e+t.vtimes[n],o=t.vdurations[n],l={template:{name:"viseme"},ts:[s-2*o/3,s+o/2,s+o+o/2],vs:{["viseme_"+i]:[null,i==="PP"||i==="FF"?.9:.6,0]}};this.animQueue.push(l)}if(t.words&&(this.onSubtitles||this.streamLipsyncType=="words"))for(let n=0;n<t.words.length;n++){const i=t.words[n],s=t.wtimes[n];let o=t.wdurations[n];if(i.length&&(this.onSubtitles&&this.animQueue.push({template:{name:"subtitles"},ts:[e+s],vs:{subtitles:[" "+i]}}),this.streamLipsyncType=="words")){const l=this.streamLipsyncLang||this.avatar.lipsyncLang||this.opt.lipsyncLang,h=this.lipsyncPreProcessText(i,l),r=this.lipsyncWordsToVisemes(h,l);if(r&&r.visemes&&r.visemes.length){const u=r.times[r.visemes.length-1]+r.durations[r.visemes.length-1],a=Math.min(o,Math.max(0,o-r.visemes.length*150));let c=.6+this.convertRange(a,[0,o],[0,.4]);if(o=Math.min(o,r.visemes.length*200),u>0)for(let d=0;d<r.visemes.length;d++){const g=e+s+r.times[d]/u*o,y=r.durations[d]/u*o;this.animQueue.push({template:{name:"viseme"},ts:[g-Math.min(60,2*y/3),g+Math.min(25,y/2),g+y+Math.min(60,y/2)],vs:{["viseme_"+r.visemes[d]]:[null,r.visemes[d]==="PP"||r.visemes[d]==="FF"?.9:c,0]}})}}}}if(t.anims&&this.streamLipsyncType=="blendshapes")for(let n=0;n<t.anims.length;n++){let i=t.anims[n];i.delay+=e;let s=this.animFactory(i,!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 n=t.audio.buffer.slice(t.audio.byteOffset,t.audio.byteOffset+t.audio.byteLength);e.data=n,this.streamWorkletNode.port.postMessage(e,[e.data])}else if(t.audio instanceof Float32Array){const n=new Int16Array(t.audio.length);for(let i=0;i<t.audio.length;i++){let s=Math.max(-1,Math.min(1,t.audio[i]));n[i]=s<0?s*32768:s*32767}e.data=n.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,n=(Math.random()-.5)/4,i=this.animQueue.findIndex(o=>o.template.name==="lookat");i!==-1&&this.animQueue.splice(i,1);const s={name:"lookat",dt:[750,t],vs:{bodyRotateX:[e],bodyRotateY:[n],eyesRotateX:[-3*e+.1],eyesRotateY:[-5*n],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 x.Vector3,this.speakTo.objectLeftEye?.isObject3D?(this.speakTo.armature.objectHead,this.speakTo.objectLeftEye.updateMatrixWorld(!0),this.speakTo.objectRightEye.updateMatrixWorld(!0),fe.setFromMatrixPosition(this.speakTo.objectLeftEye.matrixWorld),xe.setFromMatrixPosition(this.speakTo.objectRightEye.matrixWorld),e.addVectors(fe,xe).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),fe.setFromMatrixPosition(this.objectLeftEye.matrixWorld),xe.setFromMatrixPosition(this.objectRightEye.matrixWorld),fe.add(xe).divideScalar(2),Y.copy(this.armature.quaternion),Y.multiply(this.poseTarget.props["Hips.quaternion"]),Y.multiply(this.poseTarget.props["Spine.quaternion"]),Y.multiply(this.poseTarget.props["Spine1.quaternion"]),Y.multiply(this.poseTarget.props["Spine2.quaternion"]),Y.multiply(this.poseTarget.props["Neck.quaternion"]),Y.multiply(this.poseTarget.props["Head.quaternion"]);const n=new x.Vector3().subVectors(e,fe).normalize(),i=Math.atan2(n.x,n.z),s=Math.asin(-n.y);W.set(s,i,0,"YXZ");const l=new x.Quaternion().setFromEuler(W),h=new x.Quaternion().copy(l).multiply(Y.clone().invert());W.setFromQuaternion(h,"YXZ");let r=W.x/(40/24)+.2,u=W.y/(9/4),a=Math.min(.6,Math.max(-.3,r)),c=Math.min(.8,Math.max(-.8,u)),d=(Math.random()-.5)/4,g=(Math.random()-.5)/4;if(t){let y=this.animQueue.findIndex(L=>L.template.name==="lookat");y!==-1&&this.animQueue.splice(y,1);const b={name:"lookat",dt:[750,t],vs:{bodyRotateX:[a+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(b))}}lookAt(t,e,n){if(!this.camera)return;const i=this.nodeAvatar.getBoundingClientRect();this.objectLeftEye.updateMatrixWorld(!0),this.objectRightEye.updateMatrixWorld(!0);const s=new x.Vector3().setFromMatrixPosition(this.objectLeftEye.matrixWorld),o=new x.Vector3().setFromMatrixPosition(this.objectRightEye.matrixWorld),l=new x.Vector3().addVectors(s,o).divideScalar(2);l.project(this.camera);let h=(l.x+1)/2*i.width+i.left,r=-(l.y-1)/2*i.height+i.top;t===null&&(t=h),e===null&&(e=r),Y.copy(this.armature.quaternion),Y.multiply(this.poseTarget.props["Hips.quaternion"]),Y.multiply(this.poseTarget.props["Spine.quaternion"]),Y.multiply(this.poseTarget.props["Spine1.quaternion"]),Y.multiply(this.poseTarget.props["Spine2.quaternion"]),Y.multiply(this.poseTarget.props["Neck.quaternion"]),Y.multiply(this.poseTarget.props["Head.quaternion"]),W.setFromQuaternion(Y);let u=W.x/(40/24),a=W.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),y=Math.max(window.innerHeight-r,r),b=this.convertRange(e,[r-y,r+y],[-.3,.6])-u+c,L=this.convertRange(t,[h-g,h+g],[-.8,.8])-a+d;b=Math.min(.6,Math.max(-.3,b)),L=Math.min(.8,Math.max(-.8,L));let V=(Math.random()-.5)/4,p=(Math.random()-.5)/4;if(n){let M=this.animQueue.findIndex(f=>f.template.name==="lookat");M!==-1&&this.animQueue.splice(M,1);const C={name:"lookat",dt:[750,n],vs:{bodyRotateX:[b+V],bodyRotateY:[L+p],eyesRotateX:[-3*V+.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 n=this.nodeAvatar.getBoundingClientRect(),i=new x.Vector2((t-n.left)/n.width*2-1,-((e-n.top)/n.height)*2+1),s=new x.Raycaster;s.setFromCamera(i,this.camera);const o=s.intersectObject(this.armature);if(o.length>0){const l=o[0].point,h=new x.Vector3,r=new x.Vector3;this.objectLeftArm.getWorldPosition(h),this.objectRightArm.getWorldPosition(r);const u=h.distanceToSquared(l),a=r.distanceToSquared(l);u<a?(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}]},l,!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}]},l,!1,1e3),this.setValue("handFistRight",0))}else["LeftArm","LeftForeArm","LeftHand","RightArm","RightForeArm","RightHand"].forEach(l=>{let h=l+".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 x.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 x.Vector3(this.gaussianRandom(-.5,0),this.gaussianRandom(-.8,-.2),this.gaussianRandom(0,.5)),!0);const n=[],i=[];n.push(100+Math.round(Math.random()*500)),i.push({duration:1e3,props:{"LeftHand.quaternion":new x.Quaternion().setFromEuler(new x.Euler(0,-1-Math.random(),0)),"RightHand.quaternion":new x.Quaternion().setFromEuler(new x.Euler(0,1+Math.random(),0))}}),["LeftArm","LeftForeArm","RightArm","RightForeArm"].forEach(o=>{i[0].props[o+".quaternion"]=this.ikMesh.getObjectByName(o).quaternion.clone()}),n.push(1e3+Math.round(Math.random()*500)),i.push({duration:2e3,props:{}}),["LeftArm","LeftForeArm","RightArm","RightForeArm","LeftHand","RightHand"].forEach(o=>{i[1].props[o+".quaternion"]=null});const s=this.animFactory({name:"talkinghands",delay:t,dt:n,vs:{moveto:i}});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={},n=null){this.listeningAnalyzer=t,this.listeningAnalyzer.fftSize=256,this.listeningAnalyzer.smoothingTimeConstant=.1,this.listeningAnalyzer.minDecibels=-70,this.listeningAnalyzer.maxDecibels=-10,this.listeningOnchange=n&&typeof n=="function"?n: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,n=10,i=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 l=this.animClips.find(h=>h.url===t+"-"+i);if(l){let h=this.animQueue.find(a=>a.template.name==="pose");h&&(h.ts[0]=1/0),Object.entries(l.pose.props).forEach(a=>{this.poseBase.props[a[0]]=a[1].clone(),this.poseTarget.props[a[0]]=a[1].clone(),this.poseTarget.props[a[0]].t=0,this.poseTarget.props[a[0]].d=1e3}),this.mixer?console.log("Using existing mixer for FBX animation, preserving morph targets"):(this.mixer=new x.AnimationMixer(this.armature),console.log("Created new mixer for FBX animation")),this.mixer.addEventListener("finished",this.stopAnimation.bind(this),{once:!0});const r=Math.ceil(n/l.clip.duration),u=this.mixer.clipAction(l.clip);u.setLoop(x.LoopRepeat,r),u.clampWhenFinished=!0,this.currentFBXAction=u;try{u.fadeIn(.5).play(),console.log("FBX animation started successfully:",t)}catch(a){console.warn("FBX animation failed to start:",a),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 r=!1;try{const c=await fetch(t,{method:"HEAD"});if(r=c.ok,!r){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 Pe.FBXLoader;let a;try{a=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"),y=await d.text();console.error("Response details:",{status:d.status,contentType:g,firstBytes:y.substring(0,100),isHTML:y.trim().startsWith("<!DOCTYPE")||y.trim().startsWith("<html")}),(y.trim().startsWith("<!DOCTYPE")||y.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(a&&a.animations&&a.animations[i]){let c=a.animations[i];const d={};c.tracks.forEach(y=>{y.name=y.name.replaceAll("mixamorig","");const b=y.name.split(".");if(b[1]==="position"){for(let L=0;L<y.values.length;L++)y.values[L]=y.values[L]*s;d[y.name]=new x.Vector3(y.values[0],y.values[1],y.values[2])}else b[1]==="quaternion"?d[y.name]=new x.Quaternion(y.values[0],y.values[1],y.values[2],y.values[3]):b[1]==="rotation"&&(d[b[0]+".quaternion"]=new x.Quaternion().setFromEuler(new x.Euler(y.values[0],y.values[1],y.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+"-"+i,clip:c,pose:g}),this.playAnimation(t,e,n,i,s)}else{const c="Animation "+t+" (ndx="+i+") not found";console.error(c),a&&a.animations?console.error(`FBX file loaded but has ${a.animations.length} animation(s), requested index ${i}`):console.error(a?"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,n]of Object.entries(this.gesture))n.t=this.animClock,n.d=1e3,this.poseTarget.props.hasOwnProperty(e)&&(this.poseTarget.props[e].copy(n),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,n=5,i=0,s=.01){if(!this.armature)return;let o=this.poseTemplates[t];if(!o){const l=this.animPoses.find(h=>h.url===t+"-"+i);l&&(o=l.pose)}if(o){this.poseName=t,this.mixer=null;let l=this.animQueue.find(h=>h.template.name==="pose");l&&(l.ts[0]=this.animClock+n*1e3+2e3),this.setPoseFromTemplate(o)}else{let h=await new Pe.FBXLoader().loadAsync(t,e);if(h&&h.animations&&h.animations[i]){let r=h.animations[i];const u={};r.tracks.forEach(c=>{c.name=c.name.replaceAll("mixamorig","");const d=c.name.split(".");d[1]==="position"?u[c.name]=new x.Vector3(c.values[0]*s,c.values[1]*s,c.values[2]*s):d[1]==="quaternion"?u[c.name]=new x.Quaternion(c.values[0],c.values[1],c.values[2],c.values[3]):d[1]==="rotation"&&(u[d[0]+".quaternion"]=new x.Quaternion().setFromEuler(new x.Euler(c.values[0],c.values[1],c.values[2],"XYZ")).normalize())});const a={props:u};u["Hips.position"]&&(u["Hips.position"].y<.5?a.lying=!0:a.standing=!0),this.animPoses.push({url:t+"-"+i,pose:a}),this.playPose(t,e,n,i,s)}else{const r="Pose "+t+" (ndx="+i+") not found";console.error(r)}}}stopPose(){this.stopAnimation()}playGesture(t,e=3,n=!1,i=1e3){if(!this.armature)return;let s=this.gestureTemplates[t];if(s){this.gestureTimeout&&(clearTimeout(this.gestureTimeout),this.gestureTimeout=null);let l=this.animQueue.findIndex(h=>h.template.name==="talkinghands");l!==-1&&(this.animQueue[l].ts=this.animQueue[l].ts.map(h=>0)),this.gesture=this.propsToThreeObjects(s),n&&(this.gesture=this.mirrorPose(this.gesture)),t==="namaste"&&this.avatar.body==="M"&&(this.gesture["RightArm.quaternion"].rotateTowards(new x.Quaternion(0,1,0,0),-.25),this.gesture["LeftArm.quaternion"].rotateTowards(new x.Quaternion(0,1,0,0),-.25));for(let[h,r]of Object.entries(this.gesture))r.t=this.animClock,r.d=i,this.poseTarget.props.hasOwnProperty(h)&&(this.poseTarget.props[h].copy(r),this.poseTarget.props[h].t=this.animClock,this.poseTarget.props[h].d=i);e&&Number.isFinite(e)&&(this.gestureTimeout=setTimeout(this.stopGesture.bind(this,i),1e3*e))}let o=this.animEmojis[t];if(o&&(o&&o.link&&(o=this.animEmojis[o.link]),o)){this.lookAtCamera(500);const l=this.animFactory(o);if(l.gesture=!0,e&&Number.isFinite(e)){const h=l.ts[0],u=l.ts[l.ts.length-1]-h;if(e*1e3-u>0){const c=[];for(let y=1;y<l.ts.length;y++)c.push(l.ts[y]-l.ts[y-1]);const d=o.template?.rescale||c.map(y=>y/u),g=e*1e3-u;l.ts=l.ts.map((y,b,L)=>b===0?h:L[b-1]+c[b-1]+d[b-1]*g)}else{const c=e*1e3/u;l.ts=l.ts.map(d=>h+c*(d-h))}}this.animQueue.push(l)}}stopGesture(t=1e3){if(this.gestureTimeout&&(clearTimeout(this.gestureTimeout),this.gestureTimeout=null),this.gesture){const n=Object.entries(this.gesture);this.gesture=null;for(const[i,s]of n)this.poseTarget.props.hasOwnProperty(i)&&(this.poseTarget.props[i].copy(this.getPoseTemplateProp(i)),this.poseTarget.props[i].t=this.animClock,this.poseTarget.props[i].d=t)}let e=this.animQueue.findIndex(n=>n.gesture);e!==-1&&this.animQueue.splice(e,1)}ikSolve(t,e=null,n=!1,i=null){const s=new x.Vector3,o=new x.Vector3,l=new x.Vector3,h=new x.Vector3,r=new x.Quaternion,u=new x.Vector3,a=new x.Vector3,c=new x.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&&n&&e.applyQuaternion(this.armature.quaternion).add(d.position);const g=this.ikMesh.getObjectByName(t.effector),y=t.links;y.forEach(L=>{L.bone=this.ikMesh.getObjectByName(L.link),L.bone.quaternion.copy(this.getPoseTemplateProp(L.link+".quaternion"))}),d.updateMatrixWorld(!0);const b=t.iterations||10;if(e)for(let L=0;L<b;L++){let V=!1;for(let p=0,M=y.length;p<M;p++){const C=y[p].bone;C.matrixWorld.decompose(h,r,u),r.invert(),o.setFromMatrixPosition(g.matrixWorld),l.subVectors(o,h),l.applyQuaternion(r),l.normalize(),s.subVectors(e,h),s.applyQuaternion(r),s.normalize();let f=s.dot(l);f>1?f=1:f<-1&&(f=-1),f=Math.acos(f),!(f<1e-5)&&(y[p].minAngle!==void 0&&f<y[p].minAngle&&(f=y[p].minAngle),y[p].maxAngle!==void 0&&f>y[p].maxAngle&&(f=y[p].maxAngle),a.crossVectors(l,s),a.normalize(),Y.setFromAxisAngle(a,f),C.quaternion.multiply(Y),C.rotation.setFromVector3(c.setFromEuler(C.rotation).clamp(new x.Vector3(y[p].minx!==void 0?y[p].minx:-1/0,y[p].miny!==void 0?y[p].miny:-1/0,y[p].minz!==void 0?y[p].minz:-1/0),new x.Vector3(y[p].maxx!==void 0?y[p].maxx:1/0,y[p].maxy!==void 0?y[p].maxy:1/0,y[p].maxz!==void 0?y[p].maxz:1/0))),C.updateMatrixWorld(!0),V=!0)}if(!V)break}i&&y.forEach(L=>{this.poseTarget.props[L.link+".quaternion"].copy(L.bone.quaternion),this.poseTarget.props[L.link+".quaternion"].t=this.animClock,this.poseTarget.props[L.link+".quaternion"].d=i})}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 be={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"}},ze={defaultVoice:"aura-2-thalia-en",voices:{thalia:"aura-2-thalia-en",asteria:"aura-2-asteria-en",orion:"aura-2-orion-en",stella:"aura-2-stella-en",athena:"aura-2-athena-en",hera:"aura-2-hera-en",zeus:"aura-2-zeus-en"}};function Ae(){return{service:"elevenlabs",endpoint:be.endpoint,apiKey:be.apiKey,defaultVoice:be.defaultVoice,voices:be.voices}}function mt(){const B=Ae(),t=[];return Object.entries(B.voices).forEach(([e,n])=>{t.push({value:n,label:`${e.charAt(0).toUpperCase()+e.slice(1)} (${B.service})`})}),t}const Me=R.forwardRef(({avatarUrl:B="/avatars/brunette.glb",avatarBody:t="F",mood:e="neutral",ttsLang:n="en",ttsService:i=null,ttsVoice:s=null,ttsApiKey:o=null,bodyMovement:l="idle",movementIntensity:h=.5,showFullAvatar:r=!0,cameraView:u="upper",onReady:a=()=>{},onLoading:c=()=>{},onError:d=()=>{},className:g="",style:y={},animations:b={}},L)=>{const V=R.useRef(null),p=R.useRef(null),M=R.useRef(r),C=R.useRef(null),f=R.useRef(null),E=R.useRef(!1),P=R.useRef({remainingText:null,originalText:null,options:null}),U=R.useRef([]),ie=R.useRef(0),[S,G]=R.useState(!0),[q,Z]=R.useState(null),[J,oe]=R.useState(!1),[se,ce]=R.useState(!1);R.useEffect(()=>{E.current=se},[se]),R.useEffect(()=>{M.current=r},[r]);const $=Ae(),le=i||$.service;let D;le==="browser"?D={service:"browser",endpoint:"",apiKey:null,defaultVoice:"Google US English"}:le==="elevenlabs"?D={service:"elevenlabs",endpoint:"https://api.elevenlabs.io/v1/text-to-speech",apiKey:o||$.apiKey,defaultVoice:s||$.defaultVoice||be.defaultVoice,voices:$.voices||be.voices}:le==="deepgram"?D={service:"deepgram",endpoint:"https://api.deepgram.com/v1/speak",apiKey:o||$.apiKey,defaultVoice:s||$.defaultVoice||ze.defaultVoice,voices:$.voices||ze.voices}:D={...$,apiKey:o!==null?o:$.apiKey};const v={url:B,body:t,avatarMood:e,ttsLang:le==="browser"?"en-US":n,ttsVoice:s||D.defaultVoice,lipsyncLang:"en",showFullAvatar:r,bodyMovement:l,movementIntensity:h},I={ttsEndpoint:D.endpoint,ttsApikey:D.apiKey,ttsService:le,lipsyncModules:["en"],cameraView:u},z=R.useCallback(async()=>{if(!(!V.current||p.current))try{if(G(!0),Z(null),p.current=new Te(V.current,I),p.current.controls&&(p.current.controls.enableRotate=!1,p.current.controls.enableZoom=!1,p.current.controls.enablePan=!1,p.current.controls.enableDamping=!1),b&&Object.keys(b).length>0&&(p.current.customAnimations=b),await p.current.showAvatar(v,O=>{if(O.lengthComputable){const K=Math.min(100,Math.round(O.loaded/O.total*100));c(K)}}),await new Promise(O=>{const K=()=>{p.current.lipsync&&Object.keys(p.current.lipsync).length>0?O():setTimeout(K,100)};K()}),p.current&&p.current.setShowFullAvatar)try{p.current.setShowFullAvatar(r)}catch(O){console.warn("Error setting full body mode on initialization:",O)}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()),G(!1),oe(!0),a(p.current);const F=()=>{document.visibilityState==="visible"?p.current?.start():p.current?.stop()};return document.addEventListener("visibilitychange",F),()=>{document.removeEventListener("visibilitychange",F)}}catch(A){console.error("Error initializing TalkingHead:",A),Z(A.message||"Failed to initialize avatar"),G(!1),d(A)}},[B,t,e,n,i,s,o,r,l,h,u]);R.useEffect(()=>(z(),()=>{p.current&&(p.current.stop(),p.current.dispose(),p.current=null)}),[z]),R.useEffect(()=>{if(!V.current||!p.current)return;const A=new ResizeObserver(O=>{for(const K of O)p.current&&p.current.onResize&&p.current.onResize()});A.observe(V.current);const F=()=>{p.current&&p.current.onResize&&p.current.onResize()};return window.addEventListener("resize",F),()=>{A.disconnect(),window.removeEventListener("resize",F)}},[J]);const T=R.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(A){console.warn("Failed to resume audio context:",A)}},[]),N=R.useCallback(async(A,F={})=>{if(p.current&&J)try{f.current&&(clearInterval(f.current),f.current=null),C.current={text:A,options:F},P.current={remainingText:null,originalText:null,options:null};const O=/[!\.\?\n\p{Extended_Pictographic}]/ug,K=A.split(O).map(X=>X.trim()).filter(X=>X.length>0);U.current=K,ie.current=0,ce(!1),E.current=!1,await T();const de={...F,lipsyncLang:F.lipsyncLang||v.lipsyncLang||"en"};if(F.onSpeechEnd&&p.current){const X=p.current;let he=null,Ie=0;const Re=1200;let ye=!1;he=setInterval(()=>{if(Ie++,E.current)return;if(Ie>Re){if(he&&(clearInterval(he),he=null,f.current=null),!ye&&!E.current){ye=!0;try{F.onSpeechEnd()}catch(Fe){console.error("Error in onSpeechEnd callback (timeout):",Fe)}}return}const me=!X.speechQueue||X.speechQueue.length===0,Le=!X.audioPlaylist||X.audioPlaylist.length===0;X&&X.isSpeaking===!1&&me&&Le&&X.isAudioPlaying===!1&&!ye&&!E.current&&setTimeout(()=>{if(X&&!E.current&&X.isSpeaking===!1&&(!X.speechQueue||X.speechQueue.length===0)&&(!X.audioPlaylist||X.audioPlaylist.length===0)&&X.isAudioPlaying===!1&&!ye&&!E.current){ye=!0,he&&(clearInterval(he),he=null,f.current=null);try{F.onSpeechEnd()}catch(Ve){console.error("Error in onSpeechEnd callback:",Ve)}}},100)},100),f.current=he}p.current.lipsync&&Object.keys(p.current.lipsync).length>0?(p.current.setSlowdownRate&&p.current.setSlowdownRate(1.05),p.current.speakText(A,de)):setTimeout(async()=>{await T(),p.current&&p.current.lipsync&&(p.current.setSlowdownRate&&p.current.setSlowdownRate(1.05),p.current.speakText(A,de))},100)}catch(O){console.error("Error speaking text:",O),Z(O.message||"Failed to speak text")}},[J,T,v.lipsyncLang]),_=R.useCallback(()=>{p.current&&(p.current.stopSpeaking(),p.current.setSlowdownRate&&p.current.setSlowdownRate(1),C.current=null,ce(!1))},[]),j=R.useCallback(()=>{if(p.current&&p.current.pauseSpeaking){const A=p.current;if(A.isSpeaking||A.audioPlaylist&&A.audioPlaylist.length>0||A.speechQueue&&A.speechQueue.length>0){f.current&&(clearInterval(f.current),f.current=null);let O="";if(C.current&&U.current.length>0){const K=U.current.length,de=A.speechQueue?A.speechQueue.filter(Re=>Re&&Re.text&&Array.isArray(Re.text)&&Re.text.length>0).length:0,X=A.audioPlaylist&&A.audioPlaylist.length>0,he=de+(X?1:0),Ie=K-he;if(he>0&&Ie<K&&(O=U.current.slice(Ie).join(". ").trim(),!O&&de>0&&A.speechQueue)){const ye=A.speechQueue.filter(me=>me&&me.text&&Array.isArray(me.text)&&me.text.length>0).map(me=>me.text.map(Le=>Le.word||"").filter(Le=>Le.length>0).join(" ")).filter(me=>me.length>0).join(" ");ye&&ye.trim()&&(O=ye.trim())}}C.current&&(P.current={remainingText:O||null,originalText:C.current.text,options:C.current.options}),A.speechQueue&&(A.speechQueue.length=0),p.current.pauseSpeaking(),E.current=!0,ce(!0)}}},[]),Q=R.useCallback(async()=>{if(!p.current||!se)return;let A="",F={};if(P.current&&P.current.remainingText)A=P.current.remainingText,F=P.current.options||{},P.current={remainingText:null,originalText:null,options:null};else if(C.current&&C.current.text)A=C.current.text,F=C.current.options||{};else{console.warn("Resume called but no paused speech found"),ce(!1),E.current=!1;return}ce(!1),E.current=!1,await T();const O={...F,lipsyncLang:F.lipsyncLang||v.lipsyncLang||"en"};try{await N(A,O)}catch(K){console.error("Error resuming speech:",K),ce(!1),E.current=!1}},[T,se,N,v]),ve=R.useCallback(A=>{p.current&&p.current.setMood(A)},[]),ke=R.useCallback(A=>{p.current&&p.current.setSlowdownRate&&p.current.setSlowdownRate(A)},[]),H=R.useCallback((A,F=!1)=>{if(p.current&&p.current.playAnimation){if(b&&b[A]&&(A=b[A]),p.current.setShowFullAvatar)try{p.current.setShowFullAvatar(M.current)}catch(K){console.warn("Error setting full body mode:",K)}if(A.includes("."))try{p.current.playAnimation(A,null,10,0,.01,F)}catch(K){console.warn(`Failed to play ${A}:`,K);try{p.current.setBodyMovement("idle")}catch(de){console.warn("Fallback animation also failed:",de)}}else{const K=[".fbx",".glb",".gltf"];let de=!1;for(const X of K)try{p.current.playAnimation(A+X,null,10,0,.01,F),de=!0;break}catch{}if(!de){console.warn("Animation not found:",A);try{p.current.setBodyMovement("idle")}catch(X){console.warn("Fallback animation also failed:",X)}}}}},[b]),ee=R.useCallback(()=>{p.current&&p.current.onResize&&p.current.onResize()},[]);return R.useImperativeHandle(L,()=>({speakText:N,stopSpeaking:_,pauseSpeaking:j,resumeSpeaking:Q,resumeAudioContext:T,setMood:ve,setTimingAdjustment:ke,playAnimation:H,isReady:J,isPaused:se,talkingHead:p.current,handleResize:ee,setBodyMovement:A=>{if(p.current&&p.current.setShowFullAvatar&&p.current.setBodyMovement)try{p.current.setShowFullAvatar(M.current),p.current.setBodyMovement(A)}catch(F){console.warn("Error setting body movement:",F)}},setMovementIntensity:A=>p.current?.setMovementIntensity(A),playRandomDance:()=>{if(p.current&&p.current.setShowFullAvatar&&p.current.playRandomDance)try{p.current.setShowFullAvatar(M.current),p.current.playRandomDance()}catch(A){console.warn("Error playing random dance:",A)}},playReaction:A=>{if(p.current&&p.current.setShowFullAvatar&&p.current.playReaction)try{p.current.setShowFullAvatar(M.current),p.current.playReaction(A)}catch(F){console.warn("Error playing reaction:",F)}},playCelebration:()=>{if(p.current&&p.current.setShowFullAvatar&&p.current.playCelebration)try{p.current.setShowFullAvatar(M.current),p.current.playCelebration()}catch(A){console.warn("Error playing celebration:",A)}},setShowFullAvatar:A=>{if(p.current&&p.current.setShowFullAvatar)try{M.current=A,p.current.setShowFullAvatar(A)}catch(F){console.warn("Error setting showFullAvatar:",F)}},lockAvatarPosition:()=>{if(p.current&&p.current.lockAvatarPosition)try{p.current.lockAvatarPosition()}catch(A){console.warn("Error locking avatar position:",A)}},unlockAvatarPosition:()=>{if(p.current&&p.current.unlockAvatarPosition)try{p.current.unlockAvatarPosition()}catch(A){console.warn("Error unlocking avatar position:",A)}}})),re.jsxs("div",{className:`talking-head-avatar ${g}`,style:{width:"100%",height:"100%",position:"relative",...y},children:[re.jsx("div",{ref:V,className:"talking-head-viewer",style:{width:"100%",height:"100%",minHeight:"400px"}}),S&&re.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..."}),q&&re.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:q})]})});Me.displayName="TalkingHeadAvatar";const Ne=R.forwardRef(({text:B="Hello! I'm a talking avatar. How are you today?",onLoading:t=()=>{},onError:e=()=>{},onReady:n=()=>{},className:i="",style:s={},avatarConfig:o={}},l)=>{const h=R.useRef(null),r=R.useRef(null),[u,a]=R.useState(!0),[c,d]=R.useState(null),[g,y]=R.useState(!1),b=Ae(),L=o.ttsService||b.service,V=L==="browser"?{endpoint:"",apiKey:null,defaultVoice:"Google US English"}:{...b,apiKey:o.ttsApiKey!==void 0&&o.ttsApiKey!==null?o.ttsApiKey:b.apiKey,endpoint:L==="elevenlabs"&&o.ttsApiKey?"https://api.elevenlabs.io/v1/text-to-speech":b.endpoint},p={url:"/avatars/brunette.glb",body:"F",avatarMood:"neutral",ttsLang:L==="browser"?"en-US":"en",ttsVoice:o.ttsVoice||V.defaultVoice,lipsyncLang:"en",showFullAvatar:!0,bodyMovement:"idle",movementIntensity:.5,...o},M={ttsEndpoint:V.endpoint,ttsApikey:V.apiKey,ttsService:L,lipsyncModules:["en"],cameraView:"upper"},C=R.useCallback(async()=>{if(!(!h.current||r.current))try{if(a(!0),d(null),r.current=new Te(h.current,M),await r.current.showAvatar(p,q=>{if(q.lengthComputable){const Z=Math.min(100,Math.round(q.loaded/q.total*100));t(Z)}}),r.current.morphs&&r.current.morphs.length>0){const q=r.current.morphs[0].morphTargetDictionary;console.log("Available morph targets:",Object.keys(q));const Z=Object.keys(q).filter(J=>J.startsWith("viseme_"));console.log("Viseme morph targets found:",Z),Z.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(q=>{const Z=()=>{r.current.lipsync&&Object.keys(r.current.lipsync).length>0?(console.log("Lip-sync modules loaded:",Object.keys(r.current.lipsync)),q()):(console.log("Waiting for lip-sync modules to load..."),setTimeout(Z,100))};Z()}),r.current&&r.current.setShowFullAvatar)try{r.current.setShowFullAvatar(!0),console.log("Avatar initialized in full body mode")}catch(q){console.warn("Error setting full body mode on initialization:",q)}a(!1),y(!0),n(r.current);const G=()=>{document.visibilityState==="visible"?r.current?.start():r.current?.stop()};return document.addEventListener("visibilitychange",G),()=>{document.removeEventListener("visibilitychange",G)}}catch(S){console.error("Error initializing TalkingHead:",S),d(S.message||"Failed to initialize avatar"),a(!1),e(S)}},[]);R.useEffect(()=>(C(),()=>{r.current&&(r.current.stop(),r.current.dispose(),r.current=null)}),[C]);const f=R.useCallback(S=>{if(r.current&&g)try{console.log("Speaking text:",S),console.log("Avatar config:",p),console.log("TalkingHead instance:",r.current),r.current.lipsync&&Object.keys(r.current.lipsync).length>0?(console.log("Lip-sync modules loaded:",Object.keys(r.current.lipsync)),r.current.setSlowdownRate&&(r.current.setSlowdownRate(1.05),console.log("Applied timing adjustment for better lip-sync")),r.current.speakText(S)):(console.warn("Lip-sync modules not ready, waiting..."),setTimeout(()=>{r.current&&r.current.lipsync?(console.log("Lip-sync now ready, speaking..."),r.current.setSlowdownRate&&(r.current.setSlowdownRate(1.05),console.log("Applied timing adjustment for better lip-sync")),r.current.speakText(S)):console.error("Lip-sync still not ready after waiting")},500))}catch(G){console.error("Error speaking text:",G),d(G.message||"Failed to speak text")}else console.warn("Avatar not ready for speaking. isReady:",g,"talkingHeadRef:",!!r.current)},[g,p]),E=R.useCallback(()=>{r.current&&(r.current.stopSpeaking(),r.current.setSlowdownRate&&(r.current.setSlowdownRate(1),console.log("Reset timing to normal")))},[]),P=R.useCallback(S=>{r.current&&r.current.setMood(S)},[]),U=R.useCallback(S=>{r.current&&r.current.setSlowdownRate&&(r.current.setSlowdownRate(S),console.log("Timing adjustment set to:",S))},[]),ie=R.useCallback((S,G=!1)=>{if(r.current&&r.current.playAnimation){if(r.current.setShowFullAvatar)try{r.current.setShowFullAvatar(!0)}catch(Z){console.warn("Error setting full body mode:",Z)}if(S.includes("."))try{r.current.playAnimation(S,null,10,0,.01,G),console.log("Playing animation:",S)}catch(Z){console.log(`Failed to play ${S}:`,Z);try{r.current.setBodyMovement("idle"),console.log("Fallback to idle animation")}catch(J){console.warn("Fallback animation also failed:",J)}}else{const Z=[".fbx",".glb",".gltf"];let J=!1;for(const oe of Z)try{r.current.playAnimation(S+oe,null,10,0,.01,G),console.log("Playing animation:",S+oe),J=!0;break}catch{console.log(`Failed to play ${S}${oe}, trying next format...`)}if(!J){console.warn("Animation system not available or animation not found:",S);try{r.current.setBodyMovement("idle"),console.log("Fallback to idle animation")}catch(oe){console.warn("Fallback animation also failed:",oe)}}}}else console.warn("Animation system not available or animation not found:",S)},[]);return R.useImperativeHandle(l,()=>({speakText:f,stopSpeaking:E,setMood:P,setTimingAdjustment:U,playAnimation:ie,isReady:g,talkingHead:r.current,setBodyMovement:S=>{if(r.current&&r.current.setShowFullAvatar&&r.current.setBodyMovement)try{r.current.setShowFullAvatar(!0),r.current.setBodyMovement(S),console.log("Body movement set with full body mode:",S)}catch(G){console.warn("Error setting body movement:",G)}},setMovementIntensity:S=>r.current?.setMovementIntensity(S),playRandomDance:()=>{if(r.current&&r.current.setShowFullAvatar&&r.current.playRandomDance)try{r.current.setShowFullAvatar(!0),r.current.playRandomDance(),console.log("Random dance played with full body mode")}catch(S){console.warn("Error playing random dance:",S)}},playReaction:S=>{if(r.current&&r.current.setShowFullAvatar&&r.current.playReaction)try{r.current.setShowFullAvatar(!0),r.current.playReaction(S),console.log("Reaction played with full body mode:",S)}catch(G){console.warn("Error playing reaction:",G)}},playCelebration:()=>{if(r.current&&r.current.setShowFullAvatar&&r.current.playCelebration)try{r.current.setShowFullAvatar(!0),r.current.playCelebration(),console.log("Celebration played with full body mode")}catch(S){console.warn("Error playing celebration:",S)}},setShowFullAvatar:S=>{if(r.current&&r.current.setShowFullAvatar)try{r.current.setShowFullAvatar(S),console.log("Show full avatar set to:",S)}catch(G){console.warn("Error setting showFullAvatar:",G)}},lockAvatarPosition:()=>{if(r.current&&r.current.lockAvatarPosition)try{r.current.lockAvatarPosition()}catch(S){console.warn("Error locking avatar position:",S)}},unlockAvatarPosition:()=>{if(r.current&&r.current.unlockAvatarPosition)try{r.current.unlockAvatarPosition()}catch(S){console.warn("Error unlocking avatar position:",S)}}})),re.jsxs("div",{className:`talking-head-container ${i}`,style:s,children:[re.jsx("div",{ref:h,className:"talking-head-viewer",style:{width:"100%",height:"100%",minHeight:"400px"}}),u&&re.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&&re.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})]})});Ne.displayName="TalkingHeadComponent";const Ue=R.forwardRef(({text:B=null,avatarUrl:t="/avatars/brunette.glb",avatarBody:e="F",mood:n="neutral",ttsLang:i="en",ttsService:s=null,ttsVoice:o=null,ttsApiKey:l=null,bodyMovement:h="idle",movementIntensity:r=.5,showFullAvatar:u=!1,cameraView:a="upper",onReady:c=()=>{},onLoading:d=()=>{},onError:g=()=>{},onSpeechEnd:y=()=>{},className:b="",style:L={},animations:V={},autoSpeak:p=!1},M)=>{const C=R.useRef(null),f=R.useRef(null),E=R.useRef(u),P=R.useRef(null),U=R.useRef(null),ie=R.useRef(!1),S=R.useRef({remainingText:null,originalText:null,options:null}),G=R.useRef([]),[q,Z]=R.useState(!0),[J,oe]=R.useState(null),[se,ce]=R.useState(!1),[$,le]=R.useState(!1);R.useEffect(()=>{ie.current=$},[$]),R.useEffect(()=>{E.current=u},[u]);const D=Ae(),v=s||D.service;let I;v==="browser"?I={service:"browser",endpoint:"",apiKey:null,defaultVoice:"Google US English"}:v==="elevenlabs"?I={service:"elevenlabs",endpoint:"https://api.elevenlabs.io/v1/text-to-speech",apiKey:l||D.apiKey,defaultVoice:o||D.defaultVoice||be.defaultVoice,voices:D.voices||be.voices}:v==="deepgram"?I={service:"deepgram",endpoint:"https://api.deepgram.com/v1/speak",apiKey:l||D.apiKey,defaultVoice:o||D.defaultVoice||ze.defaultVoice,voices:D.voices||ze.voices}:I={...D,apiKey:l!==null?l:D.apiKey};const z={url:t,body:e,avatarMood:n,ttsLang:v==="browser"?"en-US":i,ttsVoice:o||I.defaultVoice,lipsyncLang:"en",showFullAvatar:u,bodyMovement:h,movementIntensity:r},T={ttsEndpoint:I.endpoint,ttsApikey:I.apiKey,ttsService:v,lipsyncModules:["en"],cameraView:a},N=R.useCallback(async()=>{if(!(!C.current||f.current))try{Z(!0),oe(null),f.current=new Te(C.current,T),await f.current.showAvatar(z,ee=>{if(ee.lengthComputable){const A=Math.min(100,Math.round(ee.loaded/ee.total*100));d(A)}}),Z(!1),ce(!0),c(f.current);const H=()=>{document.visibilityState==="visible"?f.current?.start():f.current?.stop()};return document.addEventListener("visibilitychange",H),()=>{document.removeEventListener("visibilitychange",H)}}catch(H){console.error("Error initializing TalkingHead:",H),oe(H.message||"Failed to initialize avatar"),Z(!1),g(H)}},[]);R.useEffect(()=>(N(),()=>{f.current&&(f.current.stop(),f.current.dispose(),f.current=null)}),[N]);const _=R.useCallback(async()=>{if(f.current)try{const H=f.current.audioCtx||f.current.audioContext;H&&(H.state==="suspended"||H.state==="interrupted")&&(await H.resume(),console.log("Audio context resumed"))}catch(H){console.warn("Failed to resume audio context:",H)}},[]);R.useEffect(()=>{se&&B&&p&&f.current&&j(B)},[se,B,p,j]);const j=R.useCallback(async(H,ee={})=>{if(!f.current||!se){console.warn("Avatar not ready for speaking");return}if(!H||H.trim()===""){console.warn("No text provided to speak");return}await _(),S.current={remainingText:null,originalText:null,options:null},G.current=[],P.current={text:H,options:ee},U.current&&(clearInterval(U.current),U.current=null),le(!1),ie.current=!1;const A=H.split(/[.!?]+/).filter(O=>O.trim().length>0);G.current=A;const F={lipsyncLang:ee.lipsyncLang||"en",onSpeechEnd:()=>{U.current&&(clearInterval(U.current),U.current=null),ee.onSpeechEnd&&ee.onSpeechEnd(),y()}};try{f.current.speakText(H,F)}catch(O){console.error("Error speaking text:",O),oe(O.message||"Failed to speak text")}},[se,y,_]),Q=R.useCallback(()=>{if(f.current)try{const H=f.current.isSpeaking||!1,ee=f.current.audioPlaylist||[],A=f.current.speechQueue||[];if(H||ee.length>0||A.length>0){U.current&&(clearInterval(U.current),U.current=null);let F="";A.length>0&&(F=A.map(O=>O.text&&Array.isArray(O.text)?O.text.map(K=>K.word).join(" "):O.text||"").join(" ")),S.current={remainingText:F||null,originalText:P.current?.text||null,options:P.current?.options||null},f.current.speechQueue.length=0,f.current.pauseSpeaking(),le(!0),ie.current=!0}}catch(H){console.warn("Error pausing speech:",H)}},[]),ve=R.useCallback(async()=>{if(!(!f.current||!$))try{await _(),le(!1),ie.current=!1;const H=S.current?.remainingText,ee=S.current?.originalText||P.current?.text,A=S.current?.options||P.current?.options||{},F=H||ee;F&&j(F,A)}catch(H){console.warn("Error resuming speech:",H),le(!1),ie.current=!1}},[$,j,_]),ke=R.useCallback(()=>{f.current&&(f.current.stopSpeaking(),U.current&&(clearInterval(U.current),U.current=null),le(!1),ie.current=!1)},[]);return R.useImperativeHandle(M,()=>({speakText:j,pauseSpeaking:Q,resumeSpeaking:ve,stopSpeaking:ke,resumeAudioContext:_,isPaused:()=>$,setMood:H=>f.current?.setMood(H),setBodyMovement:H=>{f.current&&f.current.setBodyMovement(H)},playAnimation:(H,ee=!1)=>{f.current&&f.current.playAnimation&&f.current.playAnimation(H,null,10,0,.01,ee)},playReaction:H=>f.current?.playReaction(H),playCelebration:()=>f.current?.playCelebration(),setShowFullAvatar:H=>{f.current&&(E.current=H,f.current.setShowFullAvatar(H))},isReady:se,talkingHead:f.current})),re.jsxs("div",{className:`simple-talking-avatar-container ${b}`,style:L,children:[re.jsx("div",{ref:C,className:"talking-head-viewer",style:{width:"100%",height:"100%",minHeight:"400px"}}),q&&re.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..."}),J&&re.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:J})]})});Ue.displayName="SimpleTalkingAvatar";const We=R.forwardRef(({curriculumData:B=null,avatarConfig:t={},animations:e={},onLessonStart:n=()=>{},onLessonComplete:i=()=>{},onQuestionAnswer:s=()=>{},onCurriculumComplete:o=()=>{},onCustomAction:l=()=>{},autoStart:h=!1},r)=>{const u=R.useRef(null),a=R.useRef({currentModuleIndex:0,currentLessonIndex:0,currentQuestionIndex:0,isTeaching:!1,isQuestionMode:!1,lessonCompleted:!1,curriculumCompleted:!1,score:0,totalQuestions:0}),c=R.useRef({onLessonStart:n,onLessonComplete:i,onQuestionAnswer:s,onCurriculumComplete:o,onCustomAction:l}),d=R.useRef(null),g=R.useRef(null),y=R.useRef(null),b=R.useRef(null),L=R.useRef(null),V=R.useRef(null),p=R.useRef(null),M=R.useRef(B?.curriculum||{title:"Default Curriculum",description:"No curriculum data provided",language:"en",modules:[]}),C=R.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:!1,animations:e,lipsyncLang:"en"});R.useEffect(()=>{c.current={onLessonStart:n,onLessonComplete:i,onQuestionAnswer:s,onCurriculumComplete:o,onCustomAction:l}},[n,i,s,o,l]),R.useEffect(()=>{M.current=B?.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:!1,animations:e,lipsyncLang:"en"}},[B,t,e]);const f=R.useCallback(()=>(M.current||{modules:[]}).modules[a.current.currentModuleIndex]?.lessons[a.current.currentLessonIndex],[]),E=R.useCallback(()=>f()?.questions[a.current.currentQuestionIndex],[f]),P=R.useCallback((v,I)=>I.type==="multiple_choice"||I.type==="true_false"?v===I.answer:I.type==="code_test"&&typeof v=="object"&&v!==null?v.passed===!0:!1,[]),U=R.useCallback(()=>{a.current.lessonCompleted=!0,a.current.isQuestionMode=!1;const v=a.current.totalQuestions>0?Math.round(a.current.score/a.current.totalQuestions*100):100;let I="Congratulations! You've completed this lesson";if(a.current.totalQuestions>0?I+=` You got ${a.current.score} correct out of ${a.current.totalQuestions} question${a.current.totalQuestions===1?"":"s"}, achieving a score of ${v} percent. `:I+="! ",v>=80?I+="Excellent work! You have a great understanding of this topic.":v>=60?I+="Good job! You understand most of the concepts.":I+="Keep practicing! You're making progress.",c.current.onLessonComplete({moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,score:a.current.score,totalQuestions:a.current.totalQuestions,percentage:v}),c.current.onCustomAction({type:"lessonComplete",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,score:a.current.score,totalQuestions:a.current.totalQuestions,percentage:v}),u.current){if(u.current.setMood("happy"),e.lessonComplete)try{u.current.playAnimation(e.lessonComplete,!0)}catch{u.current.playCelebration()}const z=M.current||{modules:[]},T=z.modules[a.current.currentModuleIndex],N=a.current.currentLessonIndex<(T?.lessons?.length||0)-1,_=a.current.currentModuleIndex<(z.modules?.length||0)-1,j=N||_,Q=C.current||{lipsyncLang:"en"};u.current.speakText(I,{lipsyncLang:Q.lipsyncLang,onSpeechEnd:()=>{c.current.onCustomAction({type:"lessonCompleteFeedbackDone",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,score:a.current.score,totalQuestions:a.current.totalQuestions,percentage:v,hasNextLesson:j})}})}},[e.lessonComplete]),ie=R.useCallback(()=>{a.current.curriculumCompleted=!0;const v=M.current||{modules:[]};if(c.current.onCurriculumComplete({modules:v.modules.length,totalLessons:v.modules.reduce((I,z)=>I+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 I=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:I.lipsyncLang})}},[e.curriculumComplete]),S=R.useCallback(()=>{const v=f();a.current.isQuestionMode=!0,a.current.currentQuestionIndex=0,a.current.totalQuestions=v?.questions?.length||0,a.current.score=0;const I=E();I&&c.current.onCustomAction({type:"questionStart",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,questionIndex:a.current.currentQuestionIndex,totalQuestions:a.current.totalQuestions,question:I,score:a.current.score});const z=()=>{if(!u.current||!I)return;if(u.current.setMood("happy"),e.questionStart)try{u.current.playAnimation(e.questionStart,!0)}catch(N){console.warn("Failed to play questionStart animation:",N)}const T=C.current||{lipsyncLang:"en"};I.type==="code_test"?u.current.speakText(`Let's test your coding skills! Here's your first challenge: ${I.question}`,{lipsyncLang:T.lipsyncLang}):I.type==="multiple_choice"?u.current.speakText(`Now let me ask you some questions. Here's the first one: ${I.question}`,{lipsyncLang:T.lipsyncLang}):I.type==="true_false"?u.current.speakText(`Let's start with some true or false questions. First question: ${I.question}`,{lipsyncLang:T.lipsyncLang}):u.current.speakText(`Now let me ask you some questions. Here's the first one: ${I.question}`,{lipsyncLang:T.lipsyncLang})};if(u.current&&u.current.isReady&&I)z();else if(u.current&&u.current.isReady){const T=C.current||{lipsyncLang:"en"};u.current.speakText("Now let me ask you some questions to test your understanding.",{lipsyncLang:T.lipsyncLang})}else{const T=setInterval(()=>{u.current&&u.current.isReady&&(clearInterval(T),I&&z())},100);setTimeout(()=>{clearInterval(T)},5e3)}},[e.questionStart,f,E]),G=R.useCallback(()=>{const v=f();if(a.current.currentQuestionIndex<(v?.questions?.length||0)-1){u.current&&u.current.stopSpeaking&&u.current.stopSpeaking(),a.current.currentQuestionIndex+=1;const I=E();I&&c.current.onCustomAction({type:"nextQuestion",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,questionIndex:a.current.currentQuestionIndex,totalQuestions:a.current.totalQuestions,question:I,score:a.current.score});const z=()=>{if(!u.current||!I)return;if(u.current.setMood("happy"),u.current.setBodyMovement("idle"),e.nextQuestion)try{u.current.playAnimation(e.nextQuestion,!0)}catch(Q){console.warn("Failed to play nextQuestion animation:",Q)}const T=C.current||{lipsyncLang:"en"},_=f()?.questions?.length||0,j=a.current.currentQuestionIndex>=_-1;if(I.type==="code_test"){const Q=j?`Great! Here's your final coding challenge: ${I.question}`:`Great! Now let's move on to your next coding challenge: ${I.question}`;u.current.speakText(Q,{lipsyncLang:T.lipsyncLang})}else if(I.type==="multiple_choice"){const Q=j?`Alright! Here's your final question: ${I.question}`:`Alright! Here's your next question: ${I.question}`;u.current.speakText(Q,{lipsyncLang:T.lipsyncLang})}else if(I.type==="true_false"){const Q=j?`Now let's try this final one: ${I.question}`:`Now let's try this one: ${I.question}`;u.current.speakText(Q,{lipsyncLang:T.lipsyncLang})}else{const Q=j?`Here's your final question: ${I.question}`:`Here's the next question: ${I.question}`;u.current.speakText(Q,{lipsyncLang:T.lipsyncLang})}};if(u.current&&u.current.isReady&&I)z();else if(I){const T=setInterval(()=>{u.current&&u.current.isReady&&(clearInterval(T),z())},100);setTimeout(()=>{clearInterval(T)},5e3)}}else c.current.onCustomAction({type:"allQuestionsComplete",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,totalQuestions:a.current.totalQuestions,score:a.current.score})},[e.nextQuestion,f,E]),q=R.useCallback(()=>{const v=M.current||{modules:[]},I=v.modules[a.current.currentModuleIndex];if(a.current.currentLessonIndex<(I?.lessons?.length||0)-1){a.current.currentLessonIndex+=1,a.current.currentQuestionIndex=0,a.current.lessonCompleted=!1,a.current.isQuestionMode=!1,a.current.isTeaching=!1,a.current.score=0,a.current.totalQuestions=0;const T=v.modules[a.current.currentModuleIndex],N=a.current.currentLessonIndex<(T?.lessons?.length||0)-1,_=a.current.currentModuleIndex<(v.modules?.length||0)-1,j=N||_;c.current.onCustomAction({type:"lessonStart",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,hasNextLesson:j}),c.current.onLessonStart({moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,lesson:f()}),u.current&&(u.current.setMood("happy"),u.current.setBodyMovement("idle"))}else if(a.current.currentModuleIndex<(v.modules?.length||0)-1){a.current.currentModuleIndex+=1,a.current.currentLessonIndex=0,a.current.currentQuestionIndex=0,a.current.lessonCompleted=!1,a.current.isQuestionMode=!1,a.current.isTeaching=!1,a.current.score=0,a.current.totalQuestions=0;const N=v.modules[a.current.currentModuleIndex],_=a.current.currentLessonIndex<(N?.lessons?.length||0)-1,j=a.current.currentModuleIndex<(v.modules?.length||0)-1,Q=_||j;c.current.onCustomAction({type:"lessonStart",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,hasNextLesson:Q}),c.current.onLessonStart({moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,lesson:f()}),u.current&&(u.current.setMood("happy"),u.current.setBodyMovement("idle"))}else L.current&&L.current()},[]),Z=R.useCallback(()=>{const v=f();let I=null;if(v?.avatar_script&&v?.body){const z=v.avatar_script.trim(),T=v.body.trim(),N=z.match(/[.!?]$/)?" ":". ";I=`${z}${N}${T}`}else I=v?.avatar_script||v?.body||null;if(u.current&&u.current.isReady&&I){a.current.isTeaching=!0,a.current.isQuestionMode=!1,a.current.score=0,a.current.totalQuestions=0,u.current.setMood("happy");let z=!1;if(e.teaching)try{u.current.playAnimation(e.teaching,!0),z=!0}catch(N){console.warn("Failed to play teaching animation:",N)}z||u.current.setBodyMovement("gesturing");const T=C.current||{lipsyncLang:"en"};c.current.onLessonStart({moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,lesson:v}),c.current.onCustomAction({type:"teachingStart",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,lesson:v}),u.current.speakText(I,{lipsyncLang:T.lipsyncLang,onSpeechEnd:()=>{a.current.isTeaching=!1,c.current.onCustomAction({type:"teachingComplete",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,lesson:v,hasQuestions:v.questions&&v.questions.length>0}),v?.code_example&&c.current.onCustomAction({type:"codeExampleReady",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,lesson:v,codeExample:v.code_example})}})}},[e.teaching,f]),J=R.useCallback(v=>{const I=E(),z=P(v,I);if(z&&(a.current.score+=1),c.current.onQuestionAnswer({moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,questionIndex:a.current.currentQuestionIndex,answer:v,isCorrect:z,question:I}),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 N=f()?.questions?.length||0;a.current.currentQuestionIndex>=N-1;const _=a.current.currentQuestionIndex<N-1;console.log("[CurriculumLearning] Answer feedback - questionIndex:",a.current.currentQuestionIndex,"totalQuestions:",N,"hasNextQuestion:",_);const j=I.type==="code_test"?`Great job! Your code passed all the tests! ${I.explanation||""}`:`Excellent! That's correct! ${I.explanation||""}`,Q=C.current||{lipsyncLang:"en"};u.current.speakText(j,{lipsyncLang:Q.lipsyncLang,onSpeechEnd:()=>{c.current.onCustomAction({type:"answerFeedbackComplete",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,questionIndex:a.current.currentQuestionIndex,isCorrect:!0,hasNextQuestion:_,score:a.current.score,totalQuestions:a.current.totalQuestions})}})}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 N=f()?.questions?.length||0,_=a.current.currentQuestionIndex>=N-1,j=a.current.currentQuestionIndex<N-1;console.log("[CurriculumLearning] Answer feedback (incorrect) - questionIndex:",a.current.currentQuestionIndex,"totalQuestions:",N,"hasNextQuestion:",j);const Q=I.type==="code_test"?`Your code didn't pass all the tests. ${I.explanation||"Try again!"}`:`Not quite right, but don't worry! ${I.explanation||""}${_?"":" Let's move on to the next question."}`,ve=C.current||{lipsyncLang:"en"};u.current.speakText(Q,{lipsyncLang:ve.lipsyncLang,onSpeechEnd:()=>{c.current.onCustomAction({type:"answerFeedbackComplete",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,questionIndex:a.current.currentQuestionIndex,isCorrect:!1,hasNextQuestion:j,score:a.current.score,totalQuestions:a.current.totalQuestions})}})}else{const N=f()?.questions?.length||0;c.current.onCustomAction({type:"answerFeedbackComplete",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,questionIndex:a.current.currentQuestionIndex,isCorrect:z,hasNextQuestion:a.current.currentQuestionIndex<N-1,score:a.current.score,totalQuestions:a.current.totalQuestions,avatarNotReady:!0})}},[e.correct,e.incorrect,E,f,P]),oe=R.useCallback(v=>{const I=E();if(!v||typeof v!="object"){console.error("Invalid code test result format. Expected object with {passed: boolean, ...}");return}if(I?.type!=="code_test"){console.warn("Current question is not a code test. Use handleAnswerSelect for other question types.");return}const z={passed:v.passed===!0,results:v.results||[],output:v.output||"",error:v.error||null,executionTime:v.executionTime||null,testCount:v.testCount||0,passedCount:v.passedCount||0,failedCount:v.failedCount||0};c.current.onCustomAction({type:"codeTestSubmitted",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,questionIndex:a.current.currentQuestionIndex,testResult:z,question:I}),p.current&&p.current(z)},[E,P]),se=R.useCallback(()=>{if(a.current.currentQuestionIndex>0){a.current.currentQuestionIndex-=1;const v=E();v&&c.current.onCustomAction({type:"questionStart",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,questionIndex:a.current.currentQuestionIndex,totalQuestions:a.current.totalQuestions,question:v,score:a.current.score});const I=()=>{if(!u.current||!v)return;u.current.setMood("happy"),u.current.setBodyMovement("idle");const z=C.current||{lipsyncLang:"en"};v.type==="code_test"?u.current.speakText(`Let's go back to this coding challenge: ${v.question}`,{lipsyncLang:z.lipsyncLang}):u.current.speakText(`Going back to: ${v.question}`,{lipsyncLang:z.lipsyncLang})};if(u.current&&u.current.isReady&&v)I();else if(v){const z=setInterval(()=>{u.current&&u.current.isReady&&(clearInterval(z),I())},100);setTimeout(()=>{clearInterval(z)},5e3)}}},[E]),ce=R.useCallback(()=>{const v=M.current||{modules:[]};if(v.modules[a.current.currentModuleIndex],a.current.currentLessonIndex>0)a.current.currentLessonIndex-=1,a.current.currentQuestionIndex=0,a.current.lessonCompleted=!1,a.current.isQuestionMode=!1,a.current.isTeaching=!1,a.current.score=0,a.current.totalQuestions=0,c.current.onCustomAction({type:"lessonStart",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex}),c.current.onLessonStart({moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,lesson:f()}),u.current&&(u.current.setMood("happy"),u.current.setBodyMovement("idle"));else if(a.current.currentModuleIndex>0){const T=v.modules[a.current.currentModuleIndex-1];a.current.currentModuleIndex-=1,a.current.currentLessonIndex=(T?.lessons?.length||1)-1,a.current.currentQuestionIndex=0,a.current.lessonCompleted=!1,a.current.isQuestionMode=!1,a.current.isTeaching=!1,a.current.score=0,a.current.totalQuestions=0,c.current.onCustomAction({type:"lessonStart",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex}),c.current.onLessonStart({moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,lesson:f()}),u.current&&(u.current.setMood("happy"),u.current.setBodyMovement("idle"))}},[f]),$=R.useCallback(()=>{a.current.currentModuleIndex=0,a.current.currentLessonIndex=0,a.current.currentQuestionIndex=0,a.current.isTeaching=!1,a.current.isQuestionMode=!1,a.current.lessonCompleted=!1,a.current.curriculumCompleted=!1,a.current.score=0,a.current.totalQuestions=0},[]),le=R.useCallback(v=>{console.log("Avatar is ready!",v);const I=f(),z=I?.avatar_script||I?.body;h&&z&&setTimeout(()=>{d.current&&d.current()},10)},[h,f]);R.useLayoutEffect(()=>{d.current=Z,g.current=q,y.current=U,b.current=G,L.current=ie,V.current=S,p.current=J}),R.useImperativeHandle(r,()=>({startTeaching:Z,startQuestions:S,handleAnswerSelect:J,handleCodeTestResult:oe,nextQuestion:G,previousQuestion:se,nextLesson:q,previousLesson:ce,completeLesson:U,completeCurriculum:ie,resetCurriculum:$,getState:()=>({...a.current}),getCurrentQuestion:()=>E(),getCurrentLesson:()=>f(),getAvatarRef:()=>u.current,speakText:async(v,I={})=>{await u.current?.resumeAudioContext?.();const z=C.current||{lipsyncLang:"en"};u.current?.speakText(v,{...I,lipsyncLang:I.lipsyncLang||z.lipsyncLang})},resumeAudioContext:async()=>{if(u.current?.resumeAudioContext)return await u.current.resumeAudioContext();const v=u.current?.talkingHead;if(v?.audioCtx){const I=v.audioCtx;if(I.state==="suspended"||I.state==="interrupted")try{await I.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(),pauseSpeaking:()=>u.current?.pauseSpeaking(),resumeSpeaking:async()=>await u.current?.resumeSpeaking(),isPaused:()=>u.current&&typeof u.current.isPaused<"u"?u.current.isPaused:!1,setMood:v=>u.current?.setMood(v),playAnimation:(v,I)=>u.current?.playAnimation(v,I),setBodyMovement:v=>u.current?.setBodyMovement(v),setMovementIntensity:v=>u.current?.setMovementIntensity(v),playRandomDance:()=>u.current?.playRandomDance(),playReaction:v=>u.current?.playReaction(v),playCelebration:()=>u.current?.playCelebration(),setShowFullAvatar:v=>u.current?.setShowFullAvatar(v),setTimingAdjustment:v=>u.current?.setTimingAdjustment(v),lockAvatarPosition:()=>u.current?.lockAvatarPosition(),unlockAvatarPosition:()=>u.current?.unlockAvatarPosition(),triggerCustomAction:(v,I)=>{c.current.onCustomAction({type:v,...I,state:{...a.current}})},handleResize:()=>u.current?.handleResize(),isAvatarReady:()=>u.current?.isReady||!1}),[Z,S,J,oe,G,q,U,ie,$,E,f]);const D=C.current||{avatarUrl:"/avatars/brunette.glb",avatarBody:"F",mood:"happy",ttsLang:"en",ttsService:null,ttsVoice:null,ttsApiKey:null,bodyMovement:"gesturing",movementIntensity:.7,showFullAvatar:!1,animations:e};return re.jsx("div",{style:{width:"100%",height:"100%"},children:re.jsx(Me,{ref:u,avatarUrl:D.avatarUrl,avatarBody:D.avatarBody,mood:D.mood,ttsLang:D.ttsLang,ttsService:D.ttsService,ttsVoice:D.ttsVoice,ttsApiKey:D.ttsApiKey,bodyMovement:D.bodyMovement,movementIntensity:D.movementIntensity,showFullAvatar:D.showFullAvatar,cameraView:"upper",animations:D.animations,onReady:le,onLoading:()=>{},onError:v=>{console.error("Avatar error:",v)}})})});We.displayName="CurriculumLearning";const Ee={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"}},pt=B=>Ee[B]||null,gt=B=>Ee.hasOwnProperty(B);exports.CurriculumLearning=We;exports.SimpleTalkingAvatar=Ue;exports.TalkingHeadAvatar=Me;exports.TalkingHeadComponent=Ne;exports.animations=Ee;exports.getActiveTTSConfig=Ae;exports.getAnimation=pt;exports.getVoiceOptions=mt;exports.hasAnimation=gt;
|
|
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:i});if(!s.ok)throw new Error(`Azure TTS error: ${s.status} ${s.statusText}`);const o=await s.arrayBuffer(),l=await this.audioCtx.decodeAudioData(o);console.log("Analyzing audio for precise lip-sync...");const h=await this.audioAnalyzer.analyzeAudio(l,e);console.log("Azure TTS Audio Analysis:",{text:e,audioDuration:l.duration,visemeCount:h.visemes.length,wordCount:h.words.length,features:{onsets:h.features.onsets.length,boundaries:h.features.phonemeBoundaries.length}});const r=[];for(let a=0;a<h.visemes.length;a++){const c=h.visemes[a],d=c.startTime*1e3,g=c.duration*1e3,y=c.intensity;r.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,y,0]}})}const u=[...t.anim,...r];this.audioPlaylist.push({anim:u,audio:l}),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,l)=>{l>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 n={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"&&(n.headers.Authorization="Bearer "+await this.opt.jwtGet());const i=await fetch(this.opt.ttsEndpoint+(this.opt.ttsApikey?"?key="+this.opt.ttsApikey:""),n),s=await i.json();if(i.status===200&&s&&s.audioContent){const o=this.b64ToArrayBuffer(s.audioContent),l=await this.audioCtx.decodeAudioData(o);this.speakWithHands();const h=[0];let r=0;t.text.forEach((c,d)=>{if(d>0){let g=h[h.length-1];s.timepoints[r]&&(g=s.timepoints[r].timeSeconds*1e3,s.timepoints[r].markName===""+c.mark&&r++),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 a=1e3*l.duration;a>this.opt.ttsTrimEnd&&(a=a-this.opt.ttsTrimEnd),u[u.length-1].duration=a-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:l}),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 n=e.emoji.dt.reduce((i,s)=>i+s,0);this.animQueue.push(this.animFactory(e.emoji)),setTimeout(this.startSpeaking.bind(this),n,!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==="deepgram"?await this.synthesizeWithDeepgramTTS(e):this.opt.ttsService==="azure"?await this.synthesizeWithAzureTTS(e):await this.synthesizeWithExternalTTS(e)}catch(n){console.error("Error:",n),this.startSpeaking(!0)}}else e.anim?(this.onSubtitles=e.onSubtitles||null,this.resetLips(),e.mood&&this.setMood(e.mood),e.anim.forEach((n,i)=>{for(let s=0;s<n.ts.length;s++)n.ts[s]=this.animClock+10*i;this.animQueue.push(n)}),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,n=null,i=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=n,this.onMetrics=s,t.sampleRate!==void 0){const l=t.sampleRate;typeof l=="number"&&l>=8e3&&l<=96e3?l!==this.audioCtx.sampleRate&&this.initAudioGraph(l):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 l=this.audioCtx.audioWorklet.addModule(ct.href),h=new Promise((r,u)=>setTimeout(()=>u(new Error("Worklet loading timed out")),5e3));await Promise.race([l,h]),this.workletLoaded=!0}catch(l){throw console.error("Failed to load audio worklet:",l),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=l=>{if(l.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(l.data.type==="playback-ended"&&(this._streamPause(),this.onAudioEnd))try{this.onAudioEnd()}catch{}if(this.onMetrics&&l.data.type==="metrics")try{this.onMetrics(l.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=i||null,this.audioCtx.state==="suspended"||this.audioCtx.state==="interrupted"){const l=this.audioCtx.resume(),h=new Promise((r,u)=>setTimeout(()=>u("p2"),1e3));try{await Promise.race([l,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 n=0;n<t.visemes.length;n++){const i=t.visemes[n],s=e+t.vtimes[n],o=t.vdurations[n],l={template:{name:"viseme"},ts:[s-2*o/3,s+o/2,s+o+o/2],vs:{["viseme_"+i]:[null,i==="PP"||i==="FF"?.9:.6,0]}};this.animQueue.push(l)}if(t.words&&(this.onSubtitles||this.streamLipsyncType=="words"))for(let n=0;n<t.words.length;n++){const i=t.words[n],s=t.wtimes[n];let o=t.wdurations[n];if(i.length&&(this.onSubtitles&&this.animQueue.push({template:{name:"subtitles"},ts:[e+s],vs:{subtitles:[" "+i]}}),this.streamLipsyncType=="words")){const l=this.streamLipsyncLang||this.avatar.lipsyncLang||this.opt.lipsyncLang,h=this.lipsyncPreProcessText(i,l),r=this.lipsyncWordsToVisemes(h,l);if(r&&r.visemes&&r.visemes.length){const u=r.times[r.visemes.length-1]+r.durations[r.visemes.length-1],a=Math.min(o,Math.max(0,o-r.visemes.length*150));let c=.6+this.convertRange(a,[0,o],[0,.4]);if(o=Math.min(o,r.visemes.length*200),u>0)for(let d=0;d<r.visemes.length;d++){const g=e+s+r.times[d]/u*o,y=r.durations[d]/u*o;this.animQueue.push({template:{name:"viseme"},ts:[g-Math.min(60,2*y/3),g+Math.min(25,y/2),g+y+Math.min(60,y/2)],vs:{["viseme_"+r.visemes[d]]:[null,r.visemes[d]==="PP"||r.visemes[d]==="FF"?.9:c,0]}})}}}}if(t.anims&&this.streamLipsyncType=="blendshapes")for(let n=0;n<t.anims.length;n++){let i=t.anims[n];i.delay+=e;let s=this.animFactory(i,!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 n=t.audio.buffer.slice(t.audio.byteOffset,t.audio.byteOffset+t.audio.byteLength);e.data=n,this.streamWorkletNode.port.postMessage(e,[e.data])}else if(t.audio instanceof Float32Array){const n=new Int16Array(t.audio.length);for(let i=0;i<t.audio.length;i++){let s=Math.max(-1,Math.min(1,t.audio[i]));n[i]=s<0?s*32768:s*32767}e.data=n.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,n=(Math.random()-.5)/4,i=this.animQueue.findIndex(o=>o.template.name==="lookat");i!==-1&&this.animQueue.splice(i,1);const s={name:"lookat",dt:[750,t],vs:{bodyRotateX:[e],bodyRotateY:[n],eyesRotateX:[-3*e+.1],eyesRotateY:[-5*n],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 x.Vector3,this.speakTo.objectLeftEye?.isObject3D?(this.speakTo.armature.objectHead,this.speakTo.objectLeftEye.updateMatrixWorld(!0),this.speakTo.objectRightEye.updateMatrixWorld(!0),fe.setFromMatrixPosition(this.speakTo.objectLeftEye.matrixWorld),xe.setFromMatrixPosition(this.speakTo.objectRightEye.matrixWorld),e.addVectors(fe,xe).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),fe.setFromMatrixPosition(this.objectLeftEye.matrixWorld),xe.setFromMatrixPosition(this.objectRightEye.matrixWorld),fe.add(xe).divideScalar(2),Y.copy(this.armature.quaternion),Y.multiply(this.poseTarget.props["Hips.quaternion"]),Y.multiply(this.poseTarget.props["Spine.quaternion"]),Y.multiply(this.poseTarget.props["Spine1.quaternion"]),Y.multiply(this.poseTarget.props["Spine2.quaternion"]),Y.multiply(this.poseTarget.props["Neck.quaternion"]),Y.multiply(this.poseTarget.props["Head.quaternion"]);const n=new x.Vector3().subVectors(e,fe).normalize(),i=Math.atan2(n.x,n.z),s=Math.asin(-n.y);W.set(s,i,0,"YXZ");const l=new x.Quaternion().setFromEuler(W),h=new x.Quaternion().copy(l).multiply(Y.clone().invert());W.setFromQuaternion(h,"YXZ");let r=W.x/(40/24)+.2,u=W.y/(9/4),a=Math.min(.6,Math.max(-.3,r)),c=Math.min(.8,Math.max(-.8,u)),d=(Math.random()-.5)/4,g=(Math.random()-.5)/4;if(t){let y=this.animQueue.findIndex(L=>L.template.name==="lookat");y!==-1&&this.animQueue.splice(y,1);const b={name:"lookat",dt:[750,t],vs:{bodyRotateX:[a+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(b))}}lookAt(t,e,n){if(!this.camera)return;const i=this.nodeAvatar.getBoundingClientRect();this.objectLeftEye.updateMatrixWorld(!0),this.objectRightEye.updateMatrixWorld(!0);const s=new x.Vector3().setFromMatrixPosition(this.objectLeftEye.matrixWorld),o=new x.Vector3().setFromMatrixPosition(this.objectRightEye.matrixWorld),l=new x.Vector3().addVectors(s,o).divideScalar(2);l.project(this.camera);let h=(l.x+1)/2*i.width+i.left,r=-(l.y-1)/2*i.height+i.top;t===null&&(t=h),e===null&&(e=r),Y.copy(this.armature.quaternion),Y.multiply(this.poseTarget.props["Hips.quaternion"]),Y.multiply(this.poseTarget.props["Spine.quaternion"]),Y.multiply(this.poseTarget.props["Spine1.quaternion"]),Y.multiply(this.poseTarget.props["Spine2.quaternion"]),Y.multiply(this.poseTarget.props["Neck.quaternion"]),Y.multiply(this.poseTarget.props["Head.quaternion"]),W.setFromQuaternion(Y);let u=W.x/(40/24),a=W.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),y=Math.max(window.innerHeight-r,r),b=this.convertRange(e,[r-y,r+y],[-.3,.6])-u+c,L=this.convertRange(t,[h-g,h+g],[-.8,.8])-a+d;b=Math.min(.6,Math.max(-.3,b)),L=Math.min(.8,Math.max(-.8,L));let V=(Math.random()-.5)/4,p=(Math.random()-.5)/4;if(n){let M=this.animQueue.findIndex(f=>f.template.name==="lookat");M!==-1&&this.animQueue.splice(M,1);const C={name:"lookat",dt:[750,n],vs:{bodyRotateX:[b+V],bodyRotateY:[L+p],eyesRotateX:[-3*V+.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 n=this.nodeAvatar.getBoundingClientRect(),i=new x.Vector2((t-n.left)/n.width*2-1,-((e-n.top)/n.height)*2+1),s=new x.Raycaster;s.setFromCamera(i,this.camera);const o=s.intersectObject(this.armature);if(o.length>0){const l=o[0].point,h=new x.Vector3,r=new x.Vector3;this.objectLeftArm.getWorldPosition(h),this.objectRightArm.getWorldPosition(r);const u=h.distanceToSquared(l),a=r.distanceToSquared(l);u<a?(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}]},l,!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}]},l,!1,1e3),this.setValue("handFistRight",0))}else["LeftArm","LeftForeArm","LeftHand","RightArm","RightForeArm","RightHand"].forEach(l=>{let h=l+".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 x.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 x.Vector3(this.gaussianRandom(-.5,0),this.gaussianRandom(-.8,-.2),this.gaussianRandom(0,.5)),!0);const n=[],i=[];n.push(100+Math.round(Math.random()*500)),i.push({duration:1e3,props:{"LeftHand.quaternion":new x.Quaternion().setFromEuler(new x.Euler(0,-1-Math.random(),0)),"RightHand.quaternion":new x.Quaternion().setFromEuler(new x.Euler(0,1+Math.random(),0))}}),["LeftArm","LeftForeArm","RightArm","RightForeArm"].forEach(o=>{i[0].props[o+".quaternion"]=this.ikMesh.getObjectByName(o).quaternion.clone()}),n.push(1e3+Math.round(Math.random()*500)),i.push({duration:2e3,props:{}}),["LeftArm","LeftForeArm","RightArm","RightForeArm","LeftHand","RightHand"].forEach(o=>{i[1].props[o+".quaternion"]=null});const s=this.animFactory({name:"talkinghands",delay:t,dt:n,vs:{moveto:i}});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={},n=null){this.listeningAnalyzer=t,this.listeningAnalyzer.fftSize=256,this.listeningAnalyzer.smoothingTimeConstant=.1,this.listeningAnalyzer.minDecibels=-70,this.listeningAnalyzer.maxDecibels=-10,this.listeningOnchange=n&&typeof n=="function"?n: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,n=10,i=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 l=this.animClips.find(h=>h.url===t+"-"+i);if(l){let h=this.animQueue.find(a=>a.template.name==="pose");h&&(h.ts[0]=1/0),Object.entries(l.pose.props).forEach(a=>{this.poseBase.props[a[0]]=a[1].clone(),this.poseTarget.props[a[0]]=a[1].clone(),this.poseTarget.props[a[0]].t=0,this.poseTarget.props[a[0]].d=1e3}),this.mixer?console.log("Using existing mixer for FBX animation, preserving morph targets"):(this.mixer=new x.AnimationMixer(this.armature),console.log("Created new mixer for FBX animation")),this.mixer.addEventListener("finished",this.stopAnimation.bind(this),{once:!0});const r=Math.ceil(n/l.clip.duration),u=this.mixer.clipAction(l.clip);u.setLoop(x.LoopRepeat,r),u.clampWhenFinished=!0,this.currentFBXAction=u;try{u.fadeIn(.5).play(),console.log("FBX animation started successfully:",t)}catch(a){console.warn("FBX animation failed to start:",a),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 r=!1;try{const c=await fetch(t,{method:"HEAD"});if(r=c.ok,!r){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 Pe.FBXLoader;let a;try{a=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"),y=await d.text();console.error("Response details:",{status:d.status,contentType:g,firstBytes:y.substring(0,100),isHTML:y.trim().startsWith("<!DOCTYPE")||y.trim().startsWith("<html")}),(y.trim().startsWith("<!DOCTYPE")||y.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(a&&a.animations&&a.animations[i]){let c=a.animations[i];const d={};c.tracks.forEach(y=>{y.name=y.name.replaceAll("mixamorig","");const b=y.name.split(".");if(b[1]==="position"){for(let L=0;L<y.values.length;L++)y.values[L]=y.values[L]*s;d[y.name]=new x.Vector3(y.values[0],y.values[1],y.values[2])}else b[1]==="quaternion"?d[y.name]=new x.Quaternion(y.values[0],y.values[1],y.values[2],y.values[3]):b[1]==="rotation"&&(d[b[0]+".quaternion"]=new x.Quaternion().setFromEuler(new x.Euler(y.values[0],y.values[1],y.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+"-"+i,clip:c,pose:g}),this.playAnimation(t,e,n,i,s)}else{const c="Animation "+t+" (ndx="+i+") not found";console.error(c),a&&a.animations?console.error(`FBX file loaded but has ${a.animations.length} animation(s), requested index ${i}`):console.error(a?"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,n]of Object.entries(this.gesture))n.t=this.animClock,n.d=1e3,this.poseTarget.props.hasOwnProperty(e)&&(this.poseTarget.props[e].copy(n),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,n=5,i=0,s=.01){if(!this.armature)return;let o=this.poseTemplates[t];if(!o){const l=this.animPoses.find(h=>h.url===t+"-"+i);l&&(o=l.pose)}if(o){this.poseName=t,this.mixer=null;let l=this.animQueue.find(h=>h.template.name==="pose");l&&(l.ts[0]=this.animClock+n*1e3+2e3),this.setPoseFromTemplate(o)}else{let h=await new Pe.FBXLoader().loadAsync(t,e);if(h&&h.animations&&h.animations[i]){let r=h.animations[i];const u={};r.tracks.forEach(c=>{c.name=c.name.replaceAll("mixamorig","");const d=c.name.split(".");d[1]==="position"?u[c.name]=new x.Vector3(c.values[0]*s,c.values[1]*s,c.values[2]*s):d[1]==="quaternion"?u[c.name]=new x.Quaternion(c.values[0],c.values[1],c.values[2],c.values[3]):d[1]==="rotation"&&(u[d[0]+".quaternion"]=new x.Quaternion().setFromEuler(new x.Euler(c.values[0],c.values[1],c.values[2],"XYZ")).normalize())});const a={props:u};u["Hips.position"]&&(u["Hips.position"].y<.5?a.lying=!0:a.standing=!0),this.animPoses.push({url:t+"-"+i,pose:a}),this.playPose(t,e,n,i,s)}else{const r="Pose "+t+" (ndx="+i+") not found";console.error(r)}}}stopPose(){this.stopAnimation()}playGesture(t,e=3,n=!1,i=1e3){if(!this.armature)return;let s=this.gestureTemplates[t];if(s){this.gestureTimeout&&(clearTimeout(this.gestureTimeout),this.gestureTimeout=null);let l=this.animQueue.findIndex(h=>h.template.name==="talkinghands");l!==-1&&(this.animQueue[l].ts=this.animQueue[l].ts.map(h=>0)),this.gesture=this.propsToThreeObjects(s),n&&(this.gesture=this.mirrorPose(this.gesture)),t==="namaste"&&this.avatar.body==="M"&&(this.gesture["RightArm.quaternion"].rotateTowards(new x.Quaternion(0,1,0,0),-.25),this.gesture["LeftArm.quaternion"].rotateTowards(new x.Quaternion(0,1,0,0),-.25));for(let[h,r]of Object.entries(this.gesture))r.t=this.animClock,r.d=i,this.poseTarget.props.hasOwnProperty(h)&&(this.poseTarget.props[h].copy(r),this.poseTarget.props[h].t=this.animClock,this.poseTarget.props[h].d=i);e&&Number.isFinite(e)&&(this.gestureTimeout=setTimeout(this.stopGesture.bind(this,i),1e3*e))}let o=this.animEmojis[t];if(o&&(o&&o.link&&(o=this.animEmojis[o.link]),o)){this.lookAtCamera(500);const l=this.animFactory(o);if(l.gesture=!0,e&&Number.isFinite(e)){const h=l.ts[0],u=l.ts[l.ts.length-1]-h;if(e*1e3-u>0){const c=[];for(let y=1;y<l.ts.length;y++)c.push(l.ts[y]-l.ts[y-1]);const d=o.template?.rescale||c.map(y=>y/u),g=e*1e3-u;l.ts=l.ts.map((y,b,L)=>b===0?h:L[b-1]+c[b-1]+d[b-1]*g)}else{const c=e*1e3/u;l.ts=l.ts.map(d=>h+c*(d-h))}}this.animQueue.push(l)}}stopGesture(t=1e3){if(this.gestureTimeout&&(clearTimeout(this.gestureTimeout),this.gestureTimeout=null),this.gesture){const n=Object.entries(this.gesture);this.gesture=null;for(const[i,s]of n)this.poseTarget.props.hasOwnProperty(i)&&(this.poseTarget.props[i].copy(this.getPoseTemplateProp(i)),this.poseTarget.props[i].t=this.animClock,this.poseTarget.props[i].d=t)}let e=this.animQueue.findIndex(n=>n.gesture);e!==-1&&this.animQueue.splice(e,1)}ikSolve(t,e=null,n=!1,i=null){const s=new x.Vector3,o=new x.Vector3,l=new x.Vector3,h=new x.Vector3,r=new x.Quaternion,u=new x.Vector3,a=new x.Vector3,c=new x.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&&n&&e.applyQuaternion(this.armature.quaternion).add(d.position);const g=this.ikMesh.getObjectByName(t.effector),y=t.links;y.forEach(L=>{L.bone=this.ikMesh.getObjectByName(L.link),L.bone.quaternion.copy(this.getPoseTemplateProp(L.link+".quaternion"))}),d.updateMatrixWorld(!0);const b=t.iterations||10;if(e)for(let L=0;L<b;L++){let V=!1;for(let p=0,M=y.length;p<M;p++){const C=y[p].bone;C.matrixWorld.decompose(h,r,u),r.invert(),o.setFromMatrixPosition(g.matrixWorld),l.subVectors(o,h),l.applyQuaternion(r),l.normalize(),s.subVectors(e,h),s.applyQuaternion(r),s.normalize();let f=s.dot(l);f>1?f=1:f<-1&&(f=-1),f=Math.acos(f),!(f<1e-5)&&(y[p].minAngle!==void 0&&f<y[p].minAngle&&(f=y[p].minAngle),y[p].maxAngle!==void 0&&f>y[p].maxAngle&&(f=y[p].maxAngle),a.crossVectors(l,s),a.normalize(),Y.setFromAxisAngle(a,f),C.quaternion.multiply(Y),C.rotation.setFromVector3(c.setFromEuler(C.rotation).clamp(new x.Vector3(y[p].minx!==void 0?y[p].minx:-1/0,y[p].miny!==void 0?y[p].miny:-1/0,y[p].minz!==void 0?y[p].minz:-1/0),new x.Vector3(y[p].maxx!==void 0?y[p].maxx:1/0,y[p].maxy!==void 0?y[p].maxy:1/0,y[p].maxz!==void 0?y[p].maxz:1/0))),C.updateMatrixWorld(!0),V=!0)}if(!V)break}i&&y.forEach(L=>{this.poseTarget.props[L.link+".quaternion"].copy(L.bone.quaternion),this.poseTarget.props[L.link+".quaternion"].t=this.animClock,this.poseTarget.props[L.link+".quaternion"].d=i})}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 be={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"}},ze={defaultVoice:"aura-2-thalia-en",voices:{thalia:"aura-2-thalia-en",asteria:"aura-2-asteria-en",orion:"aura-2-orion-en",stella:"aura-2-stella-en",athena:"aura-2-athena-en",hera:"aura-2-hera-en",zeus:"aura-2-zeus-en"}};function Ae(){return{service:"elevenlabs",endpoint:be.endpoint,apiKey:be.apiKey,defaultVoice:be.defaultVoice,voices:be.voices}}function mt(){const B=Ae(),t=[];return Object.entries(B.voices).forEach(([e,n])=>{t.push({value:n,label:`${e.charAt(0).toUpperCase()+e.slice(1)} (${B.service})`})}),t}const Me=R.forwardRef(({avatarUrl:B="/avatars/brunette.glb",avatarBody:t="F",mood:e="neutral",ttsLang:n="en",ttsService:i=null,ttsVoice:s=null,ttsApiKey:o=null,bodyMovement:l="idle",movementIntensity:h=.5,showFullAvatar:r=!0,cameraView:u="upper",onReady:a=()=>{},onLoading:c=()=>{},onError:d=()=>{},className:g="",style:y={},animations:b={}},L)=>{const V=R.useRef(null),p=R.useRef(null),M=R.useRef(r),C=R.useRef(null),f=R.useRef(null),E=R.useRef(!1),P=R.useRef({remainingText:null,originalText:null,options:null}),U=R.useRef([]),ie=R.useRef(0),[S,G]=R.useState(!0),[q,Z]=R.useState(null),[J,oe]=R.useState(!1),[se,ce]=R.useState(!1);R.useEffect(()=>{E.current=se},[se]),R.useEffect(()=>{M.current=r},[r]);const $=Ae(),le=i||$.service;let D;le==="browser"?D={service:"browser",endpoint:"",apiKey:null,defaultVoice:"Google US English"}:le==="elevenlabs"?D={service:"elevenlabs",endpoint:"https://api.elevenlabs.io/v1/text-to-speech",apiKey:o||$.apiKey,defaultVoice:s||$.defaultVoice||be.defaultVoice,voices:$.voices||be.voices}:le==="deepgram"?D={service:"deepgram",endpoint:"https://api.deepgram.com/v1/speak",apiKey:o||$.apiKey,defaultVoice:s||$.defaultVoice||ze.defaultVoice,voices:$.voices||ze.voices}:D={...$,apiKey:o!==null?o:$.apiKey};const v={url:B,body:t,avatarMood:e,ttsLang:le==="browser"?"en-US":n,ttsVoice:s||D.defaultVoice,lipsyncLang:"en",showFullAvatar:r,bodyMovement:l,movementIntensity:h},I={ttsEndpoint:D.endpoint,ttsApikey:D.apiKey,ttsService:le,lipsyncModules:["en"],cameraView:u},z=R.useCallback(async()=>{if(!(!V.current||p.current))try{if(G(!0),Z(null),p.current=new Te(V.current,I),p.current.controls&&(p.current.controls.enableRotate=!1,p.current.controls.enableZoom=!1,p.current.controls.enablePan=!1,p.current.controls.enableDamping=!1),b&&Object.keys(b).length>0&&(p.current.customAnimations=b),await p.current.showAvatar(v,O=>{if(O.lengthComputable){const K=Math.min(100,Math.round(O.loaded/O.total*100));c(K)}}),await new Promise(O=>{const K=()=>{p.current.lipsync&&Object.keys(p.current.lipsync).length>0?O():setTimeout(K,100)};K()}),p.current&&p.current.setShowFullAvatar)try{p.current.setShowFullAvatar(r)}catch(O){console.warn("Error setting full body mode on initialization:",O)}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()),G(!1),oe(!0),a(p.current);const F=()=>{document.visibilityState==="visible"?p.current?.start():p.current?.stop()};return document.addEventListener("visibilitychange",F),()=>{document.removeEventListener("visibilitychange",F)}}catch(A){console.error("Error initializing TalkingHead:",A),Z(A.message||"Failed to initialize avatar"),G(!1),d(A)}},[B,t,e,n,i,s,o,r,l,h,u]);R.useEffect(()=>(z(),()=>{p.current&&(p.current.stop(),p.current.dispose(),p.current=null)}),[z]),R.useEffect(()=>{if(!V.current||!p.current)return;const A=new ResizeObserver(O=>{for(const K of O)p.current&&p.current.onResize&&p.current.onResize()});A.observe(V.current);const F=()=>{p.current&&p.current.onResize&&p.current.onResize()};return window.addEventListener("resize",F),()=>{A.disconnect(),window.removeEventListener("resize",F)}},[J]);const T=R.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(A){console.warn("Failed to resume audio context:",A)}},[]),N=R.useCallback(async(A,F={})=>{if(p.current&&J)try{f.current&&(clearInterval(f.current),f.current=null),C.current={text:A,options:F},P.current={remainingText:null,originalText:null,options:null};const O=/[!\.\?\n\p{Extended_Pictographic}]/ug,K=A.split(O).map(X=>X.trim()).filter(X=>X.length>0);U.current=K,ie.current=0,ce(!1),E.current=!1,await T();const de={...F,lipsyncLang:F.lipsyncLang||v.lipsyncLang||"en"};if(F.onSpeechEnd&&p.current){const X=p.current;let he=null,Ie=0;const Re=1200;let ye=!1;he=setInterval(()=>{if(Ie++,E.current)return;if(Ie>Re){if(he&&(clearInterval(he),he=null,f.current=null),!ye&&!E.current){ye=!0;try{F.onSpeechEnd()}catch(Fe){console.error("Error in onSpeechEnd callback (timeout):",Fe)}}return}const me=!X.speechQueue||X.speechQueue.length===0,Le=!X.audioPlaylist||X.audioPlaylist.length===0;X&&X.isSpeaking===!1&&me&&Le&&X.isAudioPlaying===!1&&!ye&&!E.current&&setTimeout(()=>{if(X&&!E.current&&X.isSpeaking===!1&&(!X.speechQueue||X.speechQueue.length===0)&&(!X.audioPlaylist||X.audioPlaylist.length===0)&&X.isAudioPlaying===!1&&!ye&&!E.current){ye=!0,he&&(clearInterval(he),he=null,f.current=null);try{F.onSpeechEnd()}catch(Ve){console.error("Error in onSpeechEnd callback:",Ve)}}},100)},100),f.current=he}p.current.lipsync&&Object.keys(p.current.lipsync).length>0?(p.current.setSlowdownRate&&p.current.setSlowdownRate(1.05),p.current.speakText(A,de)):setTimeout(async()=>{await T(),p.current&&p.current.lipsync&&(p.current.setSlowdownRate&&p.current.setSlowdownRate(1.05),p.current.speakText(A,de))},100)}catch(O){console.error("Error speaking text:",O),Z(O.message||"Failed to speak text")}},[J,T,v.lipsyncLang]),_=R.useCallback(()=>{p.current&&(p.current.stopSpeaking(),p.current.setSlowdownRate&&p.current.setSlowdownRate(1),C.current=null,ce(!1))},[]),j=R.useCallback(()=>{if(p.current&&p.current.pauseSpeaking){const A=p.current;if(A.isSpeaking||A.audioPlaylist&&A.audioPlaylist.length>0||A.speechQueue&&A.speechQueue.length>0){f.current&&(clearInterval(f.current),f.current=null);let O="";if(C.current&&U.current.length>0){const K=U.current.length,de=A.speechQueue?A.speechQueue.filter(Re=>Re&&Re.text&&Array.isArray(Re.text)&&Re.text.length>0).length:0,X=A.audioPlaylist&&A.audioPlaylist.length>0,he=de+(X?1:0),Ie=K-he;if(he>0&&Ie<K&&(O=U.current.slice(Ie).join(". ").trim(),!O&&de>0&&A.speechQueue)){const ye=A.speechQueue.filter(me=>me&&me.text&&Array.isArray(me.text)&&me.text.length>0).map(me=>me.text.map(Le=>Le.word||"").filter(Le=>Le.length>0).join(" ")).filter(me=>me.length>0).join(" ");ye&&ye.trim()&&(O=ye.trim())}}C.current&&(P.current={remainingText:O||null,originalText:C.current.text,options:C.current.options}),A.speechQueue&&(A.speechQueue.length=0),p.current.pauseSpeaking(),E.current=!0,ce(!0)}}},[]),Q=R.useCallback(async()=>{if(!p.current||!se)return;let A="",F={};if(P.current&&P.current.remainingText)A=P.current.remainingText,F=P.current.options||{},P.current={remainingText:null,originalText:null,options:null};else if(C.current&&C.current.text)A=C.current.text,F=C.current.options||{};else{console.warn("Resume called but no paused speech found"),ce(!1),E.current=!1;return}ce(!1),E.current=!1,await T();const O={...F,lipsyncLang:F.lipsyncLang||v.lipsyncLang||"en"};try{await N(A,O)}catch(K){console.error("Error resuming speech:",K),ce(!1),E.current=!1}},[T,se,N,v]),ve=R.useCallback(A=>{p.current&&p.current.setMood(A)},[]),ke=R.useCallback(A=>{p.current&&p.current.setSlowdownRate&&p.current.setSlowdownRate(A)},[]),H=R.useCallback((A,F=!1)=>{if(p.current&&p.current.playAnimation){if(b&&b[A]&&(A=b[A]),p.current.setShowFullAvatar)try{p.current.setShowFullAvatar(M.current)}catch(K){console.warn("Error setting full body mode:",K)}if(A.includes("."))try{p.current.playAnimation(A,null,10,0,.01,F)}catch(K){console.warn(`Failed to play ${A}:`,K);try{p.current.setBodyMovement("idle")}catch(de){console.warn("Fallback animation also failed:",de)}}else{const K=[".fbx",".glb",".gltf"];let de=!1;for(const X of K)try{p.current.playAnimation(A+X,null,10,0,.01,F),de=!0;break}catch{}if(!de){console.warn("Animation not found:",A);try{p.current.setBodyMovement("idle")}catch(X){console.warn("Fallback animation also failed:",X)}}}}},[b]),ee=R.useCallback(()=>{p.current&&p.current.onResize&&p.current.onResize()},[]);return R.useImperativeHandle(L,()=>({speakText:N,stopSpeaking:_,pauseSpeaking:j,resumeSpeaking:Q,resumeAudioContext:T,setMood:ve,setTimingAdjustment:ke,playAnimation:H,isReady:J,isPaused:se,talkingHead:p.current,handleResize:ee,setBodyMovement:A=>{if(p.current&&p.current.setShowFullAvatar&&p.current.setBodyMovement)try{p.current.setShowFullAvatar(M.current),p.current.setBodyMovement(A)}catch(F){console.warn("Error setting body movement:",F)}},setMovementIntensity:A=>p.current?.setMovementIntensity(A),playRandomDance:()=>{if(p.current&&p.current.setShowFullAvatar&&p.current.playRandomDance)try{p.current.setShowFullAvatar(M.current),p.current.playRandomDance()}catch(A){console.warn("Error playing random dance:",A)}},playReaction:A=>{if(p.current&&p.current.setShowFullAvatar&&p.current.playReaction)try{p.current.setShowFullAvatar(M.current),p.current.playReaction(A)}catch(F){console.warn("Error playing reaction:",F)}},playCelebration:()=>{if(p.current&&p.current.setShowFullAvatar&&p.current.playCelebration)try{p.current.setShowFullAvatar(M.current),p.current.playCelebration()}catch(A){console.warn("Error playing celebration:",A)}},setShowFullAvatar:A=>{if(p.current&&p.current.setShowFullAvatar)try{M.current=A,p.current.setShowFullAvatar(A)}catch(F){console.warn("Error setting showFullAvatar:",F)}},lockAvatarPosition:()=>{if(p.current&&p.current.lockAvatarPosition)try{p.current.lockAvatarPosition()}catch(A){console.warn("Error locking avatar position:",A)}},unlockAvatarPosition:()=>{if(p.current&&p.current.unlockAvatarPosition)try{p.current.unlockAvatarPosition()}catch(A){console.warn("Error unlocking avatar position:",A)}}})),re.jsxs("div",{className:`talking-head-avatar ${g}`,style:{width:"100%",height:"100%",position:"relative",...y},children:[re.jsx("div",{ref:V,className:"talking-head-viewer",style:{width:"100%",height:"100%",minHeight:"400px"}}),S&&re.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..."}),q&&re.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:q})]})});Me.displayName="TalkingHeadAvatar";const Ne=R.forwardRef(({text:B="Hello! I'm a talking avatar. How are you today?",onLoading:t=()=>{},onError:e=()=>{},onReady:n=()=>{},className:i="",style:s={},avatarConfig:o={}},l)=>{const h=R.useRef(null),r=R.useRef(null),[u,a]=R.useState(!0),[c,d]=R.useState(null),[g,y]=R.useState(!1),b=Ae(),L=o.ttsService||b.service,V=L==="browser"?{endpoint:"",apiKey:null,defaultVoice:"Google US English"}:{...b,apiKey:o.ttsApiKey!==void 0&&o.ttsApiKey!==null?o.ttsApiKey:b.apiKey,endpoint:L==="elevenlabs"&&o.ttsApiKey?"https://api.elevenlabs.io/v1/text-to-speech":b.endpoint},p={url:"/avatars/brunette.glb",body:"F",avatarMood:"neutral",ttsLang:L==="browser"?"en-US":"en",ttsVoice:o.ttsVoice||V.defaultVoice,lipsyncLang:"en",showFullAvatar:!0,bodyMovement:"idle",movementIntensity:.5,...o},M={ttsEndpoint:V.endpoint,ttsApikey:V.apiKey,ttsService:L,lipsyncModules:["en"],cameraView:"upper"},C=R.useCallback(async()=>{if(!(!h.current||r.current))try{if(a(!0),d(null),r.current=new Te(h.current,M),await r.current.showAvatar(p,q=>{if(q.lengthComputable){const Z=Math.min(100,Math.round(q.loaded/q.total*100));t(Z)}}),r.current.morphs&&r.current.morphs.length>0){const q=r.current.morphs[0].morphTargetDictionary;console.log("Available morph targets:",Object.keys(q));const Z=Object.keys(q).filter(J=>J.startsWith("viseme_"));console.log("Viseme morph targets found:",Z),Z.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(q=>{const Z=()=>{r.current.lipsync&&Object.keys(r.current.lipsync).length>0?(console.log("Lip-sync modules loaded:",Object.keys(r.current.lipsync)),q()):(console.log("Waiting for lip-sync modules to load..."),setTimeout(Z,100))};Z()}),r.current&&r.current.setShowFullAvatar)try{r.current.setShowFullAvatar(!0),console.log("Avatar initialized in full body mode")}catch(q){console.warn("Error setting full body mode on initialization:",q)}a(!1),y(!0),n(r.current);const G=()=>{document.visibilityState==="visible"?r.current?.start():r.current?.stop()};return document.addEventListener("visibilitychange",G),()=>{document.removeEventListener("visibilitychange",G)}}catch(S){console.error("Error initializing TalkingHead:",S),d(S.message||"Failed to initialize avatar"),a(!1),e(S)}},[]);R.useEffect(()=>(C(),()=>{r.current&&(r.current.stop(),r.current.dispose(),r.current=null)}),[C]);const f=R.useCallback(S=>{if(r.current&&g)try{console.log("Speaking text:",S),console.log("Avatar config:",p),console.log("TalkingHead instance:",r.current),r.current.lipsync&&Object.keys(r.current.lipsync).length>0?(console.log("Lip-sync modules loaded:",Object.keys(r.current.lipsync)),r.current.setSlowdownRate&&(r.current.setSlowdownRate(1.05),console.log("Applied timing adjustment for better lip-sync")),r.current.speakText(S)):(console.warn("Lip-sync modules not ready, waiting..."),setTimeout(()=>{r.current&&r.current.lipsync?(console.log("Lip-sync now ready, speaking..."),r.current.setSlowdownRate&&(r.current.setSlowdownRate(1.05),console.log("Applied timing adjustment for better lip-sync")),r.current.speakText(S)):console.error("Lip-sync still not ready after waiting")},500))}catch(G){console.error("Error speaking text:",G),d(G.message||"Failed to speak text")}else console.warn("Avatar not ready for speaking. isReady:",g,"talkingHeadRef:",!!r.current)},[g,p]),E=R.useCallback(()=>{r.current&&(r.current.stopSpeaking(),r.current.setSlowdownRate&&(r.current.setSlowdownRate(1),console.log("Reset timing to normal")))},[]),P=R.useCallback(S=>{r.current&&r.current.setMood(S)},[]),U=R.useCallback(S=>{r.current&&r.current.setSlowdownRate&&(r.current.setSlowdownRate(S),console.log("Timing adjustment set to:",S))},[]),ie=R.useCallback((S,G=!1)=>{if(r.current&&r.current.playAnimation){if(r.current.setShowFullAvatar)try{r.current.setShowFullAvatar(!0)}catch(Z){console.warn("Error setting full body mode:",Z)}if(S.includes("."))try{r.current.playAnimation(S,null,10,0,.01,G),console.log("Playing animation:",S)}catch(Z){console.log(`Failed to play ${S}:`,Z);try{r.current.setBodyMovement("idle"),console.log("Fallback to idle animation")}catch(J){console.warn("Fallback animation also failed:",J)}}else{const Z=[".fbx",".glb",".gltf"];let J=!1;for(const oe of Z)try{r.current.playAnimation(S+oe,null,10,0,.01,G),console.log("Playing animation:",S+oe),J=!0;break}catch{console.log(`Failed to play ${S}${oe}, trying next format...`)}if(!J){console.warn("Animation system not available or animation not found:",S);try{r.current.setBodyMovement("idle"),console.log("Fallback to idle animation")}catch(oe){console.warn("Fallback animation also failed:",oe)}}}}else console.warn("Animation system not available or animation not found:",S)},[]);return R.useImperativeHandle(l,()=>({speakText:f,stopSpeaking:E,setMood:P,setTimingAdjustment:U,playAnimation:ie,isReady:g,talkingHead:r.current,setBodyMovement:S=>{if(r.current&&r.current.setShowFullAvatar&&r.current.setBodyMovement)try{r.current.setShowFullAvatar(!0),r.current.setBodyMovement(S),console.log("Body movement set with full body mode:",S)}catch(G){console.warn("Error setting body movement:",G)}},setMovementIntensity:S=>r.current?.setMovementIntensity(S),playRandomDance:()=>{if(r.current&&r.current.setShowFullAvatar&&r.current.playRandomDance)try{r.current.setShowFullAvatar(!0),r.current.playRandomDance(),console.log("Random dance played with full body mode")}catch(S){console.warn("Error playing random dance:",S)}},playReaction:S=>{if(r.current&&r.current.setShowFullAvatar&&r.current.playReaction)try{r.current.setShowFullAvatar(!0),r.current.playReaction(S),console.log("Reaction played with full body mode:",S)}catch(G){console.warn("Error playing reaction:",G)}},playCelebration:()=>{if(r.current&&r.current.setShowFullAvatar&&r.current.playCelebration)try{r.current.setShowFullAvatar(!0),r.current.playCelebration(),console.log("Celebration played with full body mode")}catch(S){console.warn("Error playing celebration:",S)}},setShowFullAvatar:S=>{if(r.current&&r.current.setShowFullAvatar)try{r.current.setShowFullAvatar(S),console.log("Show full avatar set to:",S)}catch(G){console.warn("Error setting showFullAvatar:",G)}},lockAvatarPosition:()=>{if(r.current&&r.current.lockAvatarPosition)try{r.current.lockAvatarPosition()}catch(S){console.warn("Error locking avatar position:",S)}},unlockAvatarPosition:()=>{if(r.current&&r.current.unlockAvatarPosition)try{r.current.unlockAvatarPosition()}catch(S){console.warn("Error unlocking avatar position:",S)}}})),re.jsxs("div",{className:`talking-head-container ${i}`,style:s,children:[re.jsx("div",{ref:h,className:"talking-head-viewer",style:{width:"100%",height:"100%",minHeight:"400px"}}),u&&re.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&&re.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})]})});Ne.displayName="TalkingHeadComponent";const Ue=R.forwardRef(({text:B=null,avatarUrl:t="/avatars/brunette.glb",avatarBody:e="F",mood:n="neutral",ttsLang:i="en",ttsService:s=null,ttsVoice:o=null,ttsApiKey:l=null,bodyMovement:h="idle",movementIntensity:r=.5,showFullAvatar:u=!1,cameraView:a="upper",onReady:c=()=>{},onLoading:d=()=>{},onError:g=()=>{},onSpeechEnd:y=()=>{},className:b="",style:L={},animations:V={},autoSpeak:p=!1},M)=>{const C=R.useRef(null),f=R.useRef(null),E=R.useRef(u),P=R.useRef(null),U=R.useRef(null),ie=R.useRef(!1),S=R.useRef({remainingText:null,originalText:null,options:null}),G=R.useRef([]),[q,Z]=R.useState(!0),[J,oe]=R.useState(null),[se,ce]=R.useState(!1),[$,le]=R.useState(!1);R.useEffect(()=>{ie.current=$},[$]),R.useEffect(()=>{E.current=u},[u]);const D=Ae(),v=s||D.service;let I;v==="browser"?I={service:"browser",endpoint:"",apiKey:null,defaultVoice:"Google US English"}:v==="elevenlabs"?I={service:"elevenlabs",endpoint:"https://api.elevenlabs.io/v1/text-to-speech",apiKey:l||D.apiKey,defaultVoice:o||D.defaultVoice||be.defaultVoice,voices:D.voices||be.voices}:v==="deepgram"?I={service:"deepgram",endpoint:"https://api.deepgram.com/v1/speak",apiKey:l||D.apiKey,defaultVoice:o||D.defaultVoice||ze.defaultVoice,voices:D.voices||ze.voices}:I={...D,apiKey:l!==null?l:D.apiKey};const z={url:t,body:e,avatarMood:n,ttsLang:v==="browser"?"en-US":i,ttsVoice:o||I.defaultVoice,lipsyncLang:"en",showFullAvatar:u,bodyMovement:h,movementIntensity:r},T={ttsEndpoint:I.endpoint,ttsApikey:I.apiKey,ttsService:v,lipsyncModules:["en"],cameraView:a},N=R.useCallback(async()=>{if(!(!C.current||f.current))try{Z(!0),oe(null),f.current=new Te(C.current,T),await f.current.showAvatar(z,ee=>{if(ee.lengthComputable){const A=Math.min(100,Math.round(ee.loaded/ee.total*100));d(A)}}),Z(!1),ce(!0),c(f.current);const H=()=>{document.visibilityState==="visible"?f.current?.start():f.current?.stop()};return document.addEventListener("visibilitychange",H),()=>{document.removeEventListener("visibilitychange",H)}}catch(H){console.error("Error initializing TalkingHead:",H),oe(H.message||"Failed to initialize avatar"),Z(!1),g(H)}},[]);R.useEffect(()=>(N(),()=>{f.current&&(f.current.stop(),f.current.dispose(),f.current=null)}),[N]);const _=R.useCallback(async()=>{if(f.current)try{const H=f.current.audioCtx||f.current.audioContext;H&&(H.state==="suspended"||H.state==="interrupted")&&(await H.resume(),console.log("Audio context resumed"))}catch(H){console.warn("Failed to resume audio context:",H)}},[]),j=R.useCallback(async(H,ee={})=>{if(!f.current||!se){console.warn("Avatar not ready for speaking");return}if(!H||H.trim()===""){console.warn("No text provided to speak");return}await _(),S.current={remainingText:null,originalText:null,options:null},G.current=[],P.current={text:H,options:ee},U.current&&(clearInterval(U.current),U.current=null),le(!1),ie.current=!1;const A=H.split(/[.!?]+/).filter(O=>O.trim().length>0);G.current=A;const F={lipsyncLang:ee.lipsyncLang||"en",onSpeechEnd:()=>{U.current&&(clearInterval(U.current),U.current=null),ee.onSpeechEnd&&ee.onSpeechEnd(),y()}};try{f.current.speakText(H,F)}catch(O){console.error("Error speaking text:",O),oe(O.message||"Failed to speak text")}},[se,y,_]);R.useEffect(()=>{se&&B&&p&&f.current&&j(B)},[se,B,p,j]);const Q=R.useCallback(()=>{if(f.current)try{const H=f.current.isSpeaking||!1,ee=f.current.audioPlaylist||[],A=f.current.speechQueue||[];if(H||ee.length>0||A.length>0){U.current&&(clearInterval(U.current),U.current=null);let F="";A.length>0&&(F=A.map(O=>O.text&&Array.isArray(O.text)?O.text.map(K=>K.word).join(" "):O.text||"").join(" ")),S.current={remainingText:F||null,originalText:P.current?.text||null,options:P.current?.options||null},f.current.speechQueue.length=0,f.current.pauseSpeaking(),le(!0),ie.current=!0}}catch(H){console.warn("Error pausing speech:",H)}},[]),ve=R.useCallback(async()=>{if(!(!f.current||!$))try{await _(),le(!1),ie.current=!1;const H=S.current?.remainingText,ee=S.current?.originalText||P.current?.text,A=S.current?.options||P.current?.options||{},F=H||ee;F&&j(F,A)}catch(H){console.warn("Error resuming speech:",H),le(!1),ie.current=!1}},[$,j,_]),ke=R.useCallback(()=>{f.current&&(f.current.stopSpeaking(),U.current&&(clearInterval(U.current),U.current=null),le(!1),ie.current=!1)},[]);return R.useImperativeHandle(M,()=>({speakText:j,pauseSpeaking:Q,resumeSpeaking:ve,stopSpeaking:ke,resumeAudioContext:_,isPaused:()=>$,setMood:H=>f.current?.setMood(H),setBodyMovement:H=>{f.current&&f.current.setBodyMovement(H)},playAnimation:(H,ee=!1)=>{f.current&&f.current.playAnimation&&f.current.playAnimation(H,null,10,0,.01,ee)},playReaction:H=>f.current?.playReaction(H),playCelebration:()=>f.current?.playCelebration(),setShowFullAvatar:H=>{f.current&&(E.current=H,f.current.setShowFullAvatar(H))},isReady:se,talkingHead:f.current})),re.jsxs("div",{className:`simple-talking-avatar-container ${b}`,style:L,children:[re.jsx("div",{ref:C,className:"talking-head-viewer",style:{width:"100%",height:"100%",minHeight:"400px"}}),q&&re.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..."}),J&&re.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:J})]})});Ue.displayName="SimpleTalkingAvatar";const We=R.forwardRef(({curriculumData:B=null,avatarConfig:t={},animations:e={},onLessonStart:n=()=>{},onLessonComplete:i=()=>{},onQuestionAnswer:s=()=>{},onCurriculumComplete:o=()=>{},onCustomAction:l=()=>{},autoStart:h=!1},r)=>{const u=R.useRef(null),a=R.useRef({currentModuleIndex:0,currentLessonIndex:0,currentQuestionIndex:0,isTeaching:!1,isQuestionMode:!1,lessonCompleted:!1,curriculumCompleted:!1,score:0,totalQuestions:0}),c=R.useRef({onLessonStart:n,onLessonComplete:i,onQuestionAnswer:s,onCurriculumComplete:o,onCustomAction:l}),d=R.useRef(null),g=R.useRef(null),y=R.useRef(null),b=R.useRef(null),L=R.useRef(null),V=R.useRef(null),p=R.useRef(null),M=R.useRef(B?.curriculum||{title:"Default Curriculum",description:"No curriculum data provided",language:"en",modules:[]}),C=R.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:!1,animations:e,lipsyncLang:"en"});R.useEffect(()=>{c.current={onLessonStart:n,onLessonComplete:i,onQuestionAnswer:s,onCurriculumComplete:o,onCustomAction:l}},[n,i,s,o,l]),R.useEffect(()=>{M.current=B?.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:!1,animations:e,lipsyncLang:"en"}},[B,t,e]);const f=R.useCallback(()=>(M.current||{modules:[]}).modules[a.current.currentModuleIndex]?.lessons[a.current.currentLessonIndex],[]),E=R.useCallback(()=>f()?.questions[a.current.currentQuestionIndex],[f]),P=R.useCallback((v,I)=>I.type==="multiple_choice"||I.type==="true_false"?v===I.answer:I.type==="code_test"&&typeof v=="object"&&v!==null?v.passed===!0:!1,[]),U=R.useCallback(()=>{a.current.lessonCompleted=!0,a.current.isQuestionMode=!1;const v=a.current.totalQuestions>0?Math.round(a.current.score/a.current.totalQuestions*100):100;let I="Congratulations! You've completed this lesson";if(a.current.totalQuestions>0?I+=` You got ${a.current.score} correct out of ${a.current.totalQuestions} question${a.current.totalQuestions===1?"":"s"}, achieving a score of ${v} percent. `:I+="! ",v>=80?I+="Excellent work! You have a great understanding of this topic.":v>=60?I+="Good job! You understand most of the concepts.":I+="Keep practicing! You're making progress.",c.current.onLessonComplete({moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,score:a.current.score,totalQuestions:a.current.totalQuestions,percentage:v}),c.current.onCustomAction({type:"lessonComplete",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,score:a.current.score,totalQuestions:a.current.totalQuestions,percentage:v}),u.current){if(u.current.setMood("happy"),e.lessonComplete)try{u.current.playAnimation(e.lessonComplete,!0)}catch{u.current.playCelebration()}const z=M.current||{modules:[]},T=z.modules[a.current.currentModuleIndex],N=a.current.currentLessonIndex<(T?.lessons?.length||0)-1,_=a.current.currentModuleIndex<(z.modules?.length||0)-1,j=N||_,Q=C.current||{lipsyncLang:"en"};u.current.speakText(I,{lipsyncLang:Q.lipsyncLang,onSpeechEnd:()=>{c.current.onCustomAction({type:"lessonCompleteFeedbackDone",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,score:a.current.score,totalQuestions:a.current.totalQuestions,percentage:v,hasNextLesson:j})}})}},[e.lessonComplete]),ie=R.useCallback(()=>{a.current.curriculumCompleted=!0;const v=M.current||{modules:[]};if(c.current.onCurriculumComplete({modules:v.modules.length,totalLessons:v.modules.reduce((I,z)=>I+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 I=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:I.lipsyncLang})}},[e.curriculumComplete]),S=R.useCallback(()=>{const v=f();a.current.isQuestionMode=!0,a.current.currentQuestionIndex=0,a.current.totalQuestions=v?.questions?.length||0,a.current.score=0;const I=E();I&&c.current.onCustomAction({type:"questionStart",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,questionIndex:a.current.currentQuestionIndex,totalQuestions:a.current.totalQuestions,question:I,score:a.current.score});const z=()=>{if(!u.current||!I)return;if(u.current.setMood("happy"),e.questionStart)try{u.current.playAnimation(e.questionStart,!0)}catch(N){console.warn("Failed to play questionStart animation:",N)}const T=C.current||{lipsyncLang:"en"};I.type==="code_test"?u.current.speakText(`Let's test your coding skills! Here's your first challenge: ${I.question}`,{lipsyncLang:T.lipsyncLang}):I.type==="multiple_choice"?u.current.speakText(`Now let me ask you some questions. Here's the first one: ${I.question}`,{lipsyncLang:T.lipsyncLang}):I.type==="true_false"?u.current.speakText(`Let's start with some true or false questions. First question: ${I.question}`,{lipsyncLang:T.lipsyncLang}):u.current.speakText(`Now let me ask you some questions. Here's the first one: ${I.question}`,{lipsyncLang:T.lipsyncLang})};if(u.current&&u.current.isReady&&I)z();else if(u.current&&u.current.isReady){const T=C.current||{lipsyncLang:"en"};u.current.speakText("Now let me ask you some questions to test your understanding.",{lipsyncLang:T.lipsyncLang})}else{const T=setInterval(()=>{u.current&&u.current.isReady&&(clearInterval(T),I&&z())},100);setTimeout(()=>{clearInterval(T)},5e3)}},[e.questionStart,f,E]),G=R.useCallback(()=>{const v=f();if(a.current.currentQuestionIndex<(v?.questions?.length||0)-1){u.current&&u.current.stopSpeaking&&u.current.stopSpeaking(),a.current.currentQuestionIndex+=1;const I=E();I&&c.current.onCustomAction({type:"nextQuestion",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,questionIndex:a.current.currentQuestionIndex,totalQuestions:a.current.totalQuestions,question:I,score:a.current.score});const z=()=>{if(!u.current||!I)return;if(u.current.setMood("happy"),u.current.setBodyMovement("idle"),e.nextQuestion)try{u.current.playAnimation(e.nextQuestion,!0)}catch(Q){console.warn("Failed to play nextQuestion animation:",Q)}const T=C.current||{lipsyncLang:"en"},_=f()?.questions?.length||0,j=a.current.currentQuestionIndex>=_-1;if(I.type==="code_test"){const Q=j?`Great! Here's your final coding challenge: ${I.question}`:`Great! Now let's move on to your next coding challenge: ${I.question}`;u.current.speakText(Q,{lipsyncLang:T.lipsyncLang})}else if(I.type==="multiple_choice"){const Q=j?`Alright! Here's your final question: ${I.question}`:`Alright! Here's your next question: ${I.question}`;u.current.speakText(Q,{lipsyncLang:T.lipsyncLang})}else if(I.type==="true_false"){const Q=j?`Now let's try this final one: ${I.question}`:`Now let's try this one: ${I.question}`;u.current.speakText(Q,{lipsyncLang:T.lipsyncLang})}else{const Q=j?`Here's your final question: ${I.question}`:`Here's the next question: ${I.question}`;u.current.speakText(Q,{lipsyncLang:T.lipsyncLang})}};if(u.current&&u.current.isReady&&I)z();else if(I){const T=setInterval(()=>{u.current&&u.current.isReady&&(clearInterval(T),z())},100);setTimeout(()=>{clearInterval(T)},5e3)}}else c.current.onCustomAction({type:"allQuestionsComplete",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,totalQuestions:a.current.totalQuestions,score:a.current.score})},[e.nextQuestion,f,E]),q=R.useCallback(()=>{const v=M.current||{modules:[]},I=v.modules[a.current.currentModuleIndex];if(a.current.currentLessonIndex<(I?.lessons?.length||0)-1){a.current.currentLessonIndex+=1,a.current.currentQuestionIndex=0,a.current.lessonCompleted=!1,a.current.isQuestionMode=!1,a.current.isTeaching=!1,a.current.score=0,a.current.totalQuestions=0;const T=v.modules[a.current.currentModuleIndex],N=a.current.currentLessonIndex<(T?.lessons?.length||0)-1,_=a.current.currentModuleIndex<(v.modules?.length||0)-1,j=N||_;c.current.onCustomAction({type:"lessonStart",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,hasNextLesson:j}),c.current.onLessonStart({moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,lesson:f()}),u.current&&(u.current.setMood("happy"),u.current.setBodyMovement("idle"))}else if(a.current.currentModuleIndex<(v.modules?.length||0)-1){a.current.currentModuleIndex+=1,a.current.currentLessonIndex=0,a.current.currentQuestionIndex=0,a.current.lessonCompleted=!1,a.current.isQuestionMode=!1,a.current.isTeaching=!1,a.current.score=0,a.current.totalQuestions=0;const N=v.modules[a.current.currentModuleIndex],_=a.current.currentLessonIndex<(N?.lessons?.length||0)-1,j=a.current.currentModuleIndex<(v.modules?.length||0)-1,Q=_||j;c.current.onCustomAction({type:"lessonStart",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,hasNextLesson:Q}),c.current.onLessonStart({moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,lesson:f()}),u.current&&(u.current.setMood("happy"),u.current.setBodyMovement("idle"))}else L.current&&L.current()},[]),Z=R.useCallback(()=>{const v=f();let I=null;if(v?.avatar_script&&v?.body){const z=v.avatar_script.trim(),T=v.body.trim(),N=z.match(/[.!?]$/)?" ":". ";I=`${z}${N}${T}`}else I=v?.avatar_script||v?.body||null;if(u.current&&u.current.isReady&&I){a.current.isTeaching=!0,a.current.isQuestionMode=!1,a.current.score=0,a.current.totalQuestions=0,u.current.setMood("happy");let z=!1;if(e.teaching)try{u.current.playAnimation(e.teaching,!0),z=!0}catch(N){console.warn("Failed to play teaching animation:",N)}z||u.current.setBodyMovement("gesturing");const T=C.current||{lipsyncLang:"en"};c.current.onLessonStart({moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,lesson:v}),c.current.onCustomAction({type:"teachingStart",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,lesson:v}),u.current.speakText(I,{lipsyncLang:T.lipsyncLang,onSpeechEnd:()=>{a.current.isTeaching=!1,c.current.onCustomAction({type:"teachingComplete",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,lesson:v,hasQuestions:v.questions&&v.questions.length>0}),v?.code_example&&c.current.onCustomAction({type:"codeExampleReady",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,lesson:v,codeExample:v.code_example})}})}},[e.teaching,f]),J=R.useCallback(v=>{const I=E(),z=P(v,I);if(z&&(a.current.score+=1),c.current.onQuestionAnswer({moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,questionIndex:a.current.currentQuestionIndex,answer:v,isCorrect:z,question:I}),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 N=f()?.questions?.length||0;a.current.currentQuestionIndex>=N-1;const _=a.current.currentQuestionIndex<N-1;console.log("[CurriculumLearning] Answer feedback - questionIndex:",a.current.currentQuestionIndex,"totalQuestions:",N,"hasNextQuestion:",_);const j=I.type==="code_test"?`Great job! Your code passed all the tests! ${I.explanation||""}`:`Excellent! That's correct! ${I.explanation||""}`,Q=C.current||{lipsyncLang:"en"};u.current.speakText(j,{lipsyncLang:Q.lipsyncLang,onSpeechEnd:()=>{c.current.onCustomAction({type:"answerFeedbackComplete",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,questionIndex:a.current.currentQuestionIndex,isCorrect:!0,hasNextQuestion:_,score:a.current.score,totalQuestions:a.current.totalQuestions})}})}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 N=f()?.questions?.length||0,_=a.current.currentQuestionIndex>=N-1,j=a.current.currentQuestionIndex<N-1;console.log("[CurriculumLearning] Answer feedback (incorrect) - questionIndex:",a.current.currentQuestionIndex,"totalQuestions:",N,"hasNextQuestion:",j);const Q=I.type==="code_test"?`Your code didn't pass all the tests. ${I.explanation||"Try again!"}`:`Not quite right, but don't worry! ${I.explanation||""}${_?"":" Let's move on to the next question."}`,ve=C.current||{lipsyncLang:"en"};u.current.speakText(Q,{lipsyncLang:ve.lipsyncLang,onSpeechEnd:()=>{c.current.onCustomAction({type:"answerFeedbackComplete",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,questionIndex:a.current.currentQuestionIndex,isCorrect:!1,hasNextQuestion:j,score:a.current.score,totalQuestions:a.current.totalQuestions})}})}else{const N=f()?.questions?.length||0;c.current.onCustomAction({type:"answerFeedbackComplete",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,questionIndex:a.current.currentQuestionIndex,isCorrect:z,hasNextQuestion:a.current.currentQuestionIndex<N-1,score:a.current.score,totalQuestions:a.current.totalQuestions,avatarNotReady:!0})}},[e.correct,e.incorrect,E,f,P]),oe=R.useCallback(v=>{const I=E();if(!v||typeof v!="object"){console.error("Invalid code test result format. Expected object with {passed: boolean, ...}");return}if(I?.type!=="code_test"){console.warn("Current question is not a code test. Use handleAnswerSelect for other question types.");return}const z={passed:v.passed===!0,results:v.results||[],output:v.output||"",error:v.error||null,executionTime:v.executionTime||null,testCount:v.testCount||0,passedCount:v.passedCount||0,failedCount:v.failedCount||0};c.current.onCustomAction({type:"codeTestSubmitted",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,questionIndex:a.current.currentQuestionIndex,testResult:z,question:I}),p.current&&p.current(z)},[E,P]),se=R.useCallback(()=>{if(a.current.currentQuestionIndex>0){a.current.currentQuestionIndex-=1;const v=E();v&&c.current.onCustomAction({type:"questionStart",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,questionIndex:a.current.currentQuestionIndex,totalQuestions:a.current.totalQuestions,question:v,score:a.current.score});const I=()=>{if(!u.current||!v)return;u.current.setMood("happy"),u.current.setBodyMovement("idle");const z=C.current||{lipsyncLang:"en"};v.type==="code_test"?u.current.speakText(`Let's go back to this coding challenge: ${v.question}`,{lipsyncLang:z.lipsyncLang}):u.current.speakText(`Going back to: ${v.question}`,{lipsyncLang:z.lipsyncLang})};if(u.current&&u.current.isReady&&v)I();else if(v){const z=setInterval(()=>{u.current&&u.current.isReady&&(clearInterval(z),I())},100);setTimeout(()=>{clearInterval(z)},5e3)}}},[E]),ce=R.useCallback(()=>{const v=M.current||{modules:[]};if(v.modules[a.current.currentModuleIndex],a.current.currentLessonIndex>0)a.current.currentLessonIndex-=1,a.current.currentQuestionIndex=0,a.current.lessonCompleted=!1,a.current.isQuestionMode=!1,a.current.isTeaching=!1,a.current.score=0,a.current.totalQuestions=0,c.current.onCustomAction({type:"lessonStart",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex}),c.current.onLessonStart({moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,lesson:f()}),u.current&&(u.current.setMood("happy"),u.current.setBodyMovement("idle"));else if(a.current.currentModuleIndex>0){const T=v.modules[a.current.currentModuleIndex-1];a.current.currentModuleIndex-=1,a.current.currentLessonIndex=(T?.lessons?.length||1)-1,a.current.currentQuestionIndex=0,a.current.lessonCompleted=!1,a.current.isQuestionMode=!1,a.current.isTeaching=!1,a.current.score=0,a.current.totalQuestions=0,c.current.onCustomAction({type:"lessonStart",moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex}),c.current.onLessonStart({moduleIndex:a.current.currentModuleIndex,lessonIndex:a.current.currentLessonIndex,lesson:f()}),u.current&&(u.current.setMood("happy"),u.current.setBodyMovement("idle"))}},[f]),$=R.useCallback(()=>{a.current.currentModuleIndex=0,a.current.currentLessonIndex=0,a.current.currentQuestionIndex=0,a.current.isTeaching=!1,a.current.isQuestionMode=!1,a.current.lessonCompleted=!1,a.current.curriculumCompleted=!1,a.current.score=0,a.current.totalQuestions=0},[]),le=R.useCallback(v=>{console.log("Avatar is ready!",v);const I=f(),z=I?.avatar_script||I?.body;h&&z&&setTimeout(()=>{d.current&&d.current()},10)},[h,f]);R.useLayoutEffect(()=>{d.current=Z,g.current=q,y.current=U,b.current=G,L.current=ie,V.current=S,p.current=J}),R.useImperativeHandle(r,()=>({startTeaching:Z,startQuestions:S,handleAnswerSelect:J,handleCodeTestResult:oe,nextQuestion:G,previousQuestion:se,nextLesson:q,previousLesson:ce,completeLesson:U,completeCurriculum:ie,resetCurriculum:$,getState:()=>({...a.current}),getCurrentQuestion:()=>E(),getCurrentLesson:()=>f(),getAvatarRef:()=>u.current,speakText:async(v,I={})=>{await u.current?.resumeAudioContext?.();const z=C.current||{lipsyncLang:"en"};u.current?.speakText(v,{...I,lipsyncLang:I.lipsyncLang||z.lipsyncLang})},resumeAudioContext:async()=>{if(u.current?.resumeAudioContext)return await u.current.resumeAudioContext();const v=u.current?.talkingHead;if(v?.audioCtx){const I=v.audioCtx;if(I.state==="suspended"||I.state==="interrupted")try{await I.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(),pauseSpeaking:()=>u.current?.pauseSpeaking(),resumeSpeaking:async()=>await u.current?.resumeSpeaking(),isPaused:()=>u.current&&typeof u.current.isPaused<"u"?u.current.isPaused:!1,setMood:v=>u.current?.setMood(v),playAnimation:(v,I)=>u.current?.playAnimation(v,I),setBodyMovement:v=>u.current?.setBodyMovement(v),setMovementIntensity:v=>u.current?.setMovementIntensity(v),playRandomDance:()=>u.current?.playRandomDance(),playReaction:v=>u.current?.playReaction(v),playCelebration:()=>u.current?.playCelebration(),setShowFullAvatar:v=>u.current?.setShowFullAvatar(v),setTimingAdjustment:v=>u.current?.setTimingAdjustment(v),lockAvatarPosition:()=>u.current?.lockAvatarPosition(),unlockAvatarPosition:()=>u.current?.unlockAvatarPosition(),triggerCustomAction:(v,I)=>{c.current.onCustomAction({type:v,...I,state:{...a.current}})},handleResize:()=>u.current?.handleResize(),isAvatarReady:()=>u.current?.isReady||!1}),[Z,S,J,oe,G,q,U,ie,$,E,f]);const D=C.current||{avatarUrl:"/avatars/brunette.glb",avatarBody:"F",mood:"happy",ttsLang:"en",ttsService:null,ttsVoice:null,ttsApiKey:null,bodyMovement:"gesturing",movementIntensity:.7,showFullAvatar:!1,animations:e};return re.jsx("div",{style:{width:"100%",height:"100%"},children:re.jsx(Me,{ref:u,avatarUrl:D.avatarUrl,avatarBody:D.avatarBody,mood:D.mood,ttsLang:D.ttsLang,ttsService:D.ttsService,ttsVoice:D.ttsVoice,ttsApiKey:D.ttsApiKey,bodyMovement:D.bodyMovement,movementIntensity:D.movementIntensity,showFullAvatar:D.showFullAvatar,cameraView:"upper",animations:D.animations,onReady:le,onLoading:()=>{},onError:v=>{console.error("Avatar error:",v)}})})});We.displayName="CurriculumLearning";const Ee={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"}},pt=B=>Ee[B]||null,gt=B=>Ee.hasOwnProperty(B);exports.CurriculumLearning=We;exports.SimpleTalkingAvatar=Ue;exports.TalkingHeadAvatar=Me;exports.TalkingHeadComponent=Ne;exports.animations=Ee;exports.getActiveTTSConfig=Ae;exports.getAnimation=pt;exports.getVoiceOptions=mt;exports.hasAnimation=gt;
|
package/dist/index.js
CHANGED
|
@@ -7382,11 +7382,7 @@ const gt = Me(({
|
|
|
7382
7382
|
} catch (C) {
|
|
7383
7383
|
console.warn("Failed to resume audio context:", C);
|
|
7384
7384
|
}
|
|
7385
|
-
}, [])
|
|
7386
|
-
de(() => {
|
|
7387
|
-
ae && G && p && f.current && j(G);
|
|
7388
|
-
}, [ae, G, p, j]);
|
|
7389
|
-
const j = T(async (C, te = {}) => {
|
|
7385
|
+
}, []), j = T(async (C, te = {}) => {
|
|
7390
7386
|
if (!f.current || !ae) {
|
|
7391
7387
|
console.warn("Avatar not ready for speaking");
|
|
7392
7388
|
return;
|
|
@@ -7409,7 +7405,11 @@ const gt = Me(({
|
|
|
7409
7405
|
} catch (B) {
|
|
7410
7406
|
console.error("Error speaking text:", B), se(B.message || "Failed to speak text");
|
|
7411
7407
|
}
|
|
7412
|
-
}, [ae, y, _])
|
|
7408
|
+
}, [ae, y, _]);
|
|
7409
|
+
de(() => {
|
|
7410
|
+
ae && G && p && f.current && j(G);
|
|
7411
|
+
}, [ae, G, p, j]);
|
|
7412
|
+
const q = T(() => {
|
|
7413
7413
|
if (f.current)
|
|
7414
7414
|
try {
|
|
7415
7415
|
const C = f.current.isSpeaking || !1, te = f.current.audioPlaylist || [], L = f.current.speechQueue || [];
|
package/package.json
CHANGED
|
@@ -208,13 +208,6 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
208
208
|
}
|
|
209
209
|
}, []);
|
|
210
210
|
|
|
211
|
-
// Auto-speak text when ready and autoSpeak is true
|
|
212
|
-
useEffect(() => {
|
|
213
|
-
if (isReady && text && autoSpeak && talkingHeadRef.current) {
|
|
214
|
-
speakText(text);
|
|
215
|
-
}
|
|
216
|
-
}, [isReady, text, autoSpeak, speakText]);
|
|
217
|
-
|
|
218
211
|
// Speak text with proper callback handling
|
|
219
212
|
const speakText = useCallback(async (textToSpeak, options = {}) => {
|
|
220
213
|
if (!talkingHeadRef.current || !isReady) {
|
|
@@ -276,6 +269,13 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
276
269
|
}
|
|
277
270
|
}, [isReady, onSpeechEnd, resumeAudioContext]);
|
|
278
271
|
|
|
272
|
+
// Auto-speak text when ready and autoSpeak is true
|
|
273
|
+
useEffect(() => {
|
|
274
|
+
if (isReady && text && autoSpeak && talkingHeadRef.current) {
|
|
275
|
+
speakText(text);
|
|
276
|
+
}
|
|
277
|
+
}, [isReady, text, autoSpeak, speakText]);
|
|
278
|
+
|
|
279
279
|
// Pause speaking
|
|
280
280
|
const pauseSpeaking = useCallback(() => {
|
|
281
281
|
if (!talkingHeadRef.current) return;
|