@streamoji/aitwin 0.1.1
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/README.md +102 -0
- package/dist/aitwin.cjs +3 -0
- package/dist/aitwin.js +1779 -0
- package/dist/assets/visemeDiffPreview.worker-B8Juk7ys.js +1 -0
- package/dist/index.d.ts +176 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# @streamoji/aitwin
|
|
2
|
+
|
|
3
|
+
Embeddable React component that renders an AI twin face (canvas + viseme diff lipsync) and exposes `speakText()` for parent-controlled speech.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @streamoji/aitwin
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Peer dependencies:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install react react-dom
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { useRef } from "react";
|
|
21
|
+
import { AiTwin, type AiTwinHandle } from "@streamoji/aitwin";
|
|
22
|
+
|
|
23
|
+
function Demo() {
|
|
24
|
+
const twinRef = useRef<AiTwinHandle>(null);
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<>
|
|
28
|
+
<AiTwin
|
|
29
|
+
ref={twinRef}
|
|
30
|
+
id="olivia"
|
|
31
|
+
authToken={optionalBearerToken}
|
|
32
|
+
onReady={() => console.log("face ready")}
|
|
33
|
+
onStatusChange={(s) => console.log("status", s)}
|
|
34
|
+
onError={(msg) => console.error(msg)}
|
|
35
|
+
/>
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
onClick={() => void twinRef.current?.speakText("Hi, how are you?")}
|
|
39
|
+
>
|
|
40
|
+
Speak
|
|
41
|
+
</button>
|
|
42
|
+
</>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Props
|
|
48
|
+
|
|
49
|
+
Provide **`id`** (cloud twin) **or** **`assets`** (fixed URLs). One is required.
|
|
50
|
+
|
|
51
|
+
| Prop | Description |
|
|
52
|
+
|------|-------------|
|
|
53
|
+
| `id` | Twin id for `getAiTwin` (e.g. `olivia`) |
|
|
54
|
+
| `assets` | `{ twinBase, binBase, encrypted }` — skip `getAiTwin` (lab / custom CDN) |
|
|
55
|
+
| `authToken` | Bearer for TTS + encrypted assets; omitted → dev `getAuthToken` |
|
|
56
|
+
| `apiBase` | Default `https://ai.streamoji.com` |
|
|
57
|
+
| `facesCdnBase` | R2 base when using `id` (default pub `custom-faces`) |
|
|
58
|
+
| `tts` | Provider when using `assets` (default `google`) |
|
|
59
|
+
| `voiceId` | Override TTS voice |
|
|
60
|
+
| `speakingRate` | Default `0.85` |
|
|
61
|
+
| `showErrorOverlay` | Canvas error overlay (default `true`) |
|
|
62
|
+
| `onReady` | Assets loaded and canvas ready |
|
|
63
|
+
| `onStatusChange` | TTS status: idle, loading, speaking, done, error |
|
|
64
|
+
| `onDisplayStatus` | Compositor label (viseme / idle / transition) |
|
|
65
|
+
| `onError` | Load or runtime errors |
|
|
66
|
+
|
|
67
|
+
Use **stable** `useCallback` handlers for `onReady` / `onError` / `onDisplayStatus` in parent components.
|
|
68
|
+
|
|
69
|
+
### Ref handle
|
|
70
|
+
|
|
71
|
+
| Method | Description |
|
|
72
|
+
|--------|-------------|
|
|
73
|
+
| `speakText(text, options?)` | TTS + lipsync; optional per-call `tts` / `voiceId` |
|
|
74
|
+
| `stop()` | Stop playback and return toward idle |
|
|
75
|
+
| `setTtsProvider(provider)` | Switch Google / Inworld / Cartesia |
|
|
76
|
+
| `renderViseme(to, options?)` | Manual viseme transition (lab) |
|
|
77
|
+
| `isReady()` | Whether face assets are loaded |
|
|
78
|
+
|
|
79
|
+
## Architecture (aitwin monorepo)
|
|
80
|
+
|
|
81
|
+
| Path | Role |
|
|
82
|
+
|------|------|
|
|
83
|
+
| `packages/aitwin` | **Source of truth** — canvas renderer, worker, TTS lipsync, `AiTwin` |
|
|
84
|
+
| `frontend` | Lab app: `/viseme-diff-preview` uses `<AiTwin assets={…} />`, `/aitwin-demo` uses `id` |
|
|
85
|
+
| `frontend/src/components/AiTwin` | Legacy **Talking Lady** still-image widget only (re-exports TTS from package) |
|
|
86
|
+
| `frontend/src/lib/twinPreviewConfig.ts` | Vite env → `blondeladyPreviewAssets()` for the diff preview page |
|
|
87
|
+
|
|
88
|
+
Do not duplicate renderer code under `frontend/src/lib`; extend the package instead.
|
|
89
|
+
|
|
90
|
+
## Publishing
|
|
91
|
+
|
|
92
|
+
From `packages/aitwin`:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
npm run build
|
|
96
|
+
npm version patch
|
|
97
|
+
npm publish --access public
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The published tarball includes only **`dist/`** (bundled JS + `.d.ts` + worker chunk). **No `src/` and no source maps.**
|
|
101
|
+
|
|
102
|
+
Scoped packages need `--access public` on the free npm plan.
|
package/dist/aitwin.cjs
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const De=require("react/jsx-runtime"),_=require("react"),Re="https://ai.streamoji.com",Ve="https://pub-607ad1fc22e2400eb57d17240aab857c.r2.dev/custom-faces",gt="https://us-central1-streamoji-265f4.cloudfunctions.net/getAiTwin";class Ae extends Error{constructor(e="AI twin not found"){super(e),this.name="AiTwinNotFoundError"}}async function tt(t,e=gt){const n=new URL(e);n.searchParams.set("id",t);const r=await fetch(n.toString());if(r.status===404)throw new Ae;if(!r.ok)throw new Error(`getAiTwin failed (${r.status})`);const s=await r.json();if(!s.success)throw new Ae(s.error??"AI twin not found");return s.data}const pt="https://us-central1-streamoji-265f4.cloudfunctions.net/getAuthToken",yt="client_6TNvp3SCs4Og0a1ijm9TommXLql1",Et="kk8Fq8EmexzP10jMIEwY3R44M5RKUEm1",St="6TNvp3SCs4Og0a1ijm9TommXLql1",kt="Swaraj Mali";async function Ce(){const t=await fetch(pt,{method:"POST",headers:{"Content-Type":"application/json","Client-Id":yt,"Client-Secret":Et},body:JSON.stringify({userId:St,userName:kt})});if(!t.ok)throw new Error(`getAuthToken failed (${t.status})`);const e=await t.json(),n=e.authToken??e.token??"";if(!n.trim())throw new Error("getAuthToken returned no token");return n}async function vt(t,e=Re){const n=await fetch(`${e.replace(/\/$/,"")}/api/session-value`,{headers:{Authorization:`Bearer ${t}`}});if(!n.ok)throw new Error(`/api/session-value failed (${n.status})`);const r=await n.json();if(!r.value)throw new Error("Session value response missing value");return r}function At(t){const e=t.trim();if(e.length%2!==0)throw new Error("Invalid hex key");const n=new Uint8Array(e.length/2);for(let r=0;r<e.length;r+=2)n[r/2]=Number.parseInt(e.slice(r,r+2),16);return n}function Ee(t){return Uint8Array.from(t)}function Tt(t,e){const n=new Uint8Array(t.length+e.length);return n.set(t,0),n.set(e,t.length),n}async function It(t,e){if(!e)throw new Error("Asset key is required for encrypted assets");if(t.byteLength<28)throw new Error("Encrypted payload too small");const n=new Uint8Array(t),r=Ee(n.subarray(0,12)),s=Ee(n.subarray(12,28)),a=Ee(n.subarray(28)),i=Ee(Tt(a,s)),c=await crypto.subtle.importKey("raw",At(e),{name:"AES-GCM"},!1,["decrypt"]);return crypto.subtle.decrypt({name:"AES-GCM",iv:r,tagLength:128},c,i)}let Pe=null;const Me=new Set;function Rt(t,e=Ve){const n=`${e.replace(/\/$/,"")}/${t}`;return{twinBase:n,binBase:n,encrypted:!0}}function bt(t){Pe=t;for(const e of Me)e()}function ne(){if(!Pe)throw new Error("Twin assets not configured; call setActiveTwinAssets first");return Pe}function xt(t){return Me.add(t),()=>Me.delete(t)}function Dt(){return`${ne().twinBase}/sil.png`}function Ct(){return`${ne().twinBase}/idle.mp4`}function Pt(){const{encrypted:t,binBase:e,twinBase:n}=ne();return t?e:n}function te(){return ne().encrypted}function nt(){const{binBase:t,twinBase:e,encrypted:n}=ne();return n?`${t}/atlas.bin`:`${e}/atlas.json`}function Mt(){const{binBase:t,twinBase:e,encrypted:n}=ne();return n?`${t}/expression_atlas.bin`:`${e}/expression_atlas.json`}function Ft(t){const e=t.split(/\r?\n/);let n="",r="";for(const a of e)a.startsWith("event:")?n=a.slice(6).trim():a.startsWith("data:")&&(r=a.slice(5).trim());if(!n)return null;let s={};if(r)try{s=JSON.parse(r)}catch{s={raw:r}}return{event:n,data:s}}const rt=["aa","CH","DD","E","FF","I","O","PP","RR","SS","TH","U","kk","nn","sil"],Ot=new Map(rt.map(t=>[t.toLowerCase(),t]));function st(t){const e=(t??"").trim();return e?Ot.get(e.toLowerCase())??"sil":"sil"}const Ut={aei:["aa","E","I"],o:["O","U"],ee:["I"],bmp:["PP"],fv:["FF"],l:["nn"],r:["RR"],th:["TH"],qw:["U","O"],cdgknstxyz:["DD","SS","kk","CH"]};function Lt(t){if(!t)return["sil"];const e=t.toLowerCase();return Ut[e]??["sil"]}function Vt(t,e){const n=t.word.length;if(n<=0)return 0;const r=t.wduration;if(r<=0)return 0;const s=Math.max(0,Math.min(1,(e-t.wtime)/r));return Math.min(n-1,Math.floor(s*n))}function be(t,e){const n=Math.max(0,Math.min(t.length-1,e));return t[n]??""}function Bt(t,e){const n=t.toLowerCase(),r=be(n,e),s=n.slice(Math.max(0,e-1),e+2);return r==="i"||r==="y"?"I":s.includes("ee")||r==="e"||n.includes("ea")?"E":"aa"}function $t(t,e){const n=t.toLowerCase(),r=n.slice(Math.max(0,e-1),Math.min(n.length,e+3));if(/oo|ou|uw/.test(r))return"U";const s=be(n,e);return s==="u"||s==="w"?"U":"O"}function Nt(t,e){const n=t.toLowerCase(),r=n.slice(Math.max(0,e-2),e+1);if(/qu/.test(r))return"U";const s=n.slice(Math.max(0,e-1),Math.min(n.length,e+3));return/wo|wh/.test(s)||be(n,e)==="o"?"O":"U"}const Se={d:"DD",t:"DD",n:"DD",s:"SS",z:"SS",k:"kk",g:"kk",c:"CH",x:"CH",j:"CH"};function Ht(t,e){const n=t.toLowerCase(),r=be(n,e);if(Se[r])return Se[r];for(let s=0;s<n.length;s++){const a=n[s];if(Se[a])return Se[a]}return"DD"}function Wt(t,e,n){const r=Lt(t);if(r.length===1)return r[0];const s=e.trim();if(!s)return r[0];const a=(t??"").toLowerCase(),i=Math.max(0,n);switch(a){case"aei":return Bt(s,i);case"o":return $t(s,i);case"qw":return Nt(s,i);case"cdgknstxyz":return Ht(s,i);default:return r[0]}}function Kt(t){let e=t.trim();const n=e.indexOf(",");e.startsWith("data:")&&n>=0&&(e=e.slice(n+1)),e=e.replace(/\s+/g,"").replace(/-/g,"+").replace(/_/g,"/");const r=e.length%4;return r!==0&&(e+="=".repeat(4-r)),e}const qt=24e3;function jt(t){return t.length>=4&&t[0]===82&&t[1]===73&&t[2]===70&&t[3]===70}function qe(t,e,n=qt){const r=e.byteOffset%2===0?e:e.slice(),s=new Int16Array(r.buffer,r.byteOffset,r.byteLength/2),a=new Float32Array(s.length);for(let c=0;c<s.length;c++)a[c]=s[c]>=32768?-(65536-s[c])/32768:s[c]/32767;const i=t.createBuffer(1,a.length,n);return i.copyToChannel(a,0),i}function zt(t){const e=Kt(t),n=window.atob(e),r=new Uint8Array(n.length);for(let s=0;s<n.length;s++)r[s]=n.charCodeAt(s);return r}async function Gt(t,e){const n=zt(e);if(jt(n)){const r=n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength);return t.decodeAudioData(r)}if(n.length>=2&&n.length%2===0)return qe(t,n);try{const r=n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength);return await t.decodeAudioData(r)}catch{return qe(t,n)}}function it(t,e){let n=null;for(const r of t)e<r.vtime||e>=r.vtime+r.vduration||(!n||r.vtime>=n.vtime)&&(n=r);return n?n.viseme:"sil"}function ot(t,e){for(const n of t)if(e>=n.wtime&&e<n.wtime+n.wduration)return n;return null}function je(t){let e=0;for(const n of t)e=Math.max(e,n.vtime+n.vduration);return e}const at=.5,ct=1.5,Be="Olivia",ut="f786b574-daa5-4673-aa0c-cbe3e8534c02",$e=.85;function lt(t,e=Re){const n=`${e.replace(/\/$/,"")}/avatar_ttsWithPoses`,r=[],s=[],a=[];let i="neutral";const c=[],u=[];let p=null,w=!1,o=!1,f=!1,d=!1,S=0,D=0,F=0,Z=0,N=null,h=1,y="",P="google",O="google",M=!1;const K=new Set,re=()=>{for(const m of u)m.playbackRate.value=h},C=()=>K.forEach(m=>m()),H=()=>{N!=null&&(clearTimeout(N),N=null)},se=()=>{for(const m of u)try{m.stop()}catch{}u.length=0},ue=()=>{M=!0,H(),se(),c.length=0,w=!1,o=!1,f=!1,d=!1,r.length=0,s.length=0,a.length=0,i="neutral",F=0,Z=0,D=0,t("idle"),C()},ie=()=>{!d||w||u.length>0||c.length>0||(o=!1,H(),t("done"),C())},le=()=>{const m=je(r);if(m<=0){t("done"),C();return}o=!0,S=performance.now(),t("speaking"),C(),H();const b=h>0?m/h:m;N=setTimeout(()=>{M||(o=!1,N=null,t("done"),C())},b)},z=(m,b,T)=>{m.length!==0&&(T&&(F=b),m.forEach(I=>{const R=String(I.word??"").trim();if(!R)return;const q=Math.round((I.start??0)*1e3),B=Math.round((I.duration??0)*1e3);s.push({word:R,wtime:F+q,wduration:B,queueIndex:s.length})}))},ge=(m,b,T,I)=>{m.length!==0&&(T&&(F=b),m.forEach(x=>{const R=String(x.symbol??"").trim();if(!R)return;const q=Math.round((x.start??0)*1e3),B=Math.max(1,Math.round((x.duration??0)*1e3)),A=F+q;let G;if(I==="google"||I==="cartesia")G=st(R);else{const v=ot(s,A),$=v?Vt(v,A):0;G=Wt(R,v?.word??"",$)}r.push({viseme:G,weight:1,vtime:A,vduration:B})}),C())},fe=(m,b,T,I,x)=>{z(b,T,I),ge(m,T,I,x)},oe=async(m,b,T,I)=>{if(M||!m?.trim()){(b.length>0||T.length>0)&&!f&&fe(b,T,F,I,O);return}if(w){c.push({audio:m,visemes:b,words:T,isNewSegment:I});return}w=!0;try{const x=window.AudioContext||window.webkitAudioContext,R=p??new x;R.state==="suspended"&&await R.resume(),p=R;const q=await Gt(R,m);f=!0;const B=R.currentTime;let A=Z;const G=!o;A<B&&(A=B+.1),Z=A+q.duration;const v=R.createBufferSource();if(v.buffer=q,v.playbackRate.value=h,v.connect(R.destination),u.push(v),M){u.pop();return}if(G){o=!0,t("speaking"),D=A,F=0,r.length=0,s.length=0;const V=(A-B)*1e3;S=performance.now()+V}v.onended=()=>{const V=u.indexOf(v);V>=0&&u.splice(V,1),ie(),C()},v.start(A);const $=(A-D)*1e3;fe(b,T,$,I,O)}catch(x){console.error("[avatarTtsLipsync] audio chunk failed:",x)}finally{w=!1;const x=c.shift();x?await oe(x.audio,x.visemes,x.words,x.isNewSegment):ie()}},pe=()=>{if(d=!0,!f&&r.length>0){le();return}ie()};return{setDeveloperToken:m=>{y=m},setTtsProvider:m=>{P=m},getTtsProvider:()=>P,speak:async(m,b)=>{ue(),M=!1,t("loading");const T=y.trim();if(!T)throw new Error("Developer token required for avatar_ttsWithPoses");const I=b?.tts??P;O=I;const x=b?.voiceId??Be,R=b?.speakingRate??$e,q=Math.max(at,Math.min(ct,R)),B={user_query:m,tts:I,speakingRate:q};(I==="inworld"||I==="cartesia")&&(B.voice_id=x);try{const A=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${T}`},body:JSON.stringify(B)});if(!A.ok)throw new Error(`avatar_ttsWithPoses failed (${A.status})`);const G=A.body;if(!G)throw new Error("No response body");const v=G.getReader(),$=new TextDecoder;let V="";const J=async ae=>{const U=Ft(ae);if(U){if(U.event==="audio"){const l=U.data.chunk,E=U.data.visemes??[],k=U.data.words??[],g=U.data.is_new_segment??!1;if(l)await oe(l,E,k,g);else if(E.length>0||k.length>0){const L=g&&r.length>0?je(r):F;fe(E,k,L,g,I)}}else if(U.event==="metadata"){const l=U.data.mood;typeof l=="string"&&l.trim()&&(i=l.trim());const E=U.data.sentence_emotions;if(Array.isArray(E)){a.length=0;for(const k of E){if(!k||typeof k!="object")continue;const g=k;a.push({sentence_index:Number(g.sentence_index??0),text:String(g.text??""),sentiment:typeof g.sentiment=="string"?g.sentiment:void 0,emotion:String(g.emotion??i),start_word:Number(g.start_word??0),end_word:Number(g.end_word??0)})}}C()}else if(U.event==="error")throw new Error(String(U.data.message??"avatar_ttsWithPoses stream error"))}};for(;;){const{done:ae,value:U}=await v.read();U&&(V+=$.decode(U,{stream:!0}));const l=V.split(`
|
|
2
|
+
|
|
3
|
+
`);V=l.pop()??"";for(const E of l)await J(E);if(ae){V.trim()&&await J(V.trim()),M||pe();break}}}catch(A){throw console.error("[avatarTtsLipsync]",A),ue(),t("error"),A}},stop:ue,getVisemeQueue:()=>r,getWordQueue:()=>s,getSentenceEmotions:()=>a,getStreamMood:()=>i,getPlaybackElapsedMs:()=>o?f&&p&&D>0?Math.max(0,(p.currentTime-D)*1e3):Math.max(0,(performance.now()-S)*h):0,isSpeaking:()=>o,setPlaybackSpeed:m=>{h=Math.max(.1,Math.min(1,m)),re()},getPlaybackSpeed:()=>h,subscribe:m=>(K.add(m),()=>K.delete(m))}}function Qt(t,e,n){return{width:t.crop?.source_width??e,height:t.crop?.source_height??n}}function Jt(t,e){const n=t.crop;return{x:e?.source_x??n?.x??0,y:e?.source_y??n?.y??0,width:e?.source_w??n?.width??t.frame_width??0,height:e?.source_h??n?.height??t.frame_height??0}}function Xt(t){return t?(t.w??0)>0&&(t.h??0)>0:!1}async function ft(t,e){const n=await fetch(t);if(!n.ok)throw new Error(`Failed to load encrypted asset: ${t}`);const r=await n.arrayBuffer();return It(r,e)}async function dt(t,e){if(!e)throw new Error("Missing asset key for encrypted JSON");const n=await ft(t,e),r=new TextDecoder().decode(n);return JSON.parse(r)}async function Yt(t){const e=new Blob([t],{type:"image/webp"}),n=URL.createObjectURL(e);return new Promise((r,s)=>{const a=new Image;a.decoding="async",a.onload=()=>{URL.revokeObjectURL(n),r(a)},a.onerror=()=>{URL.revokeObjectURL(n),s(new Error("Failed to decode decrypted image"))},a.src=n})}function ht(t,e){return`${t}/${e}_minus_sil.png`}const Ne=15,Zt=["aa","E","I","O","U","PP","FF","DD","SS","TH","CH","RR","kk","nn"],Fe=["t01_0.17","t02_0.33","t03_0.50","t04_0.67","t05_0.83"],en=Fe.length,ze=new Set(["aa__kk","aa__nn","aa__sil","CH__aa","CH__DD","CH__E","CH__FF","CH__I","CH__kk","CH__nn","CH__O","CH__PP","CH__RR","CH__sil","CH__SS","CH__TH","CH__U","DD__aa","DD__E","DD__FF","DD__I","DD__kk","DD__nn","DD__O","DD__PP","DD__RR","DD__sil","DD__SS","DD__TH","DD__U","E__aa","E__FF","E__I","E__kk","E__nn","E__O","E__PP","E__RR","E__sil","E__SS","E__TH","E__U","FF__aa","FF__I","FF__kk","FF__nn","FF__O","FF__PP","FF__RR","FF__sil","FF__SS","FF__TH","FF__U","I__aa","I__kk","I__nn","I__O","I__PP","I__RR","I__sil","I__SS","I__TH","I__U","kk__nn","kk__sil","nn__sil","O__aa","O__kk","O__nn","O__PP","O__RR","O__sil","O__SS","O__TH","O__U","PP__aa","PP__kk","PP__nn","PP__RR","PP__sil","PP__SS","PP__TH","PP__U","RR__aa","RR__kk","RR__nn","RR__sil","RR__SS","RR__TH","RR__U","SS__aa","SS__kk","SS__nn","SS__sil","SS__TH","SS__U","TH__aa","TH__kk","TH__nn","TH__sil","TH__U","U__aa","U__kk","U__nn","U__sil"]);function Ge(t,e){return`${t}__${e}`}function Qe(t,e){return`pairs/${t}/${e}_minus_sil.png`}function Oe(t){return`${t}_minus_sil.png`}function tn(t,e){if(t===e)return[];const n=[],r=Ge(t,e);if(ze.has(r)){for(const a of Fe)n.push(Qe(r,a));return n}const s=Ge(e,t);if(ze.has(s)){for(let a=en-1;a>=0;a--)n.push(Qe(s,Fe[a]));return n}return[]}function nn(t){return t<16?[]:t<35?[2]:t<51?[0,4]:t<67?[0,2,4]:t<83?[0,1,2,3]:[0,1,2,3,4]}function rn(t,e){return nn(e).map(r=>t[r]).filter(r=>r!=null)}function sn(t,e,n,r){if(r<=0)return 0;const a=Math.max(1,n-e)/r,i=t-e,c=Math.floor(i/a);return Math.min(r-1,Math.max(0,c))}function on(t,e){if(!te())return`${t}/${e}`;const n=e.split("/").pop()??e,r=n.includes(".")?n.slice(0,n.lastIndexOf(".")):n;return`${t}/${r}.bin`}async function an(t){const e=nt(),n=te()?await dt(e,t):await fetch(e).then(r=>{if(!r.ok)throw new Error(`Failed to load ${e}`);return r.json()});return{width:n.crop?.source_width??n.frame_width??842,height:n.crop?.source_height??n.frame_height??1264}}const cn=[.32,.24,.16,.08,0],xe=cn.map(t=>`eyes_${t.toFixed(2)}`),un=["eyes_0.32","eyes_0.16","eyes_0.00"],ln=["eyes_0.32","eyes_0.16","eyes_0.08","eyes_0.00"],fn={fear:"fear",afraid:"fear",anxiety:"fear",anger:"anger",angry:"anger",mad:"anger",sad:"sad",sadness:"sad",sorrow:"sad",neutral:"neutral",calm:"neutral",love:"love",loving:"love",affection:"love",happy:"happy",happiness:"happy",joy:"happy",joyful:"happy",excited:"happy",disgust:"disgust",disgusted:"disgust"};function ee(t,e="neutral"){const n=String(t??"").trim().toLowerCase();return n?fn[n]??e:e}function W(t){return t==="neutral"?null:ht(t,"eyes_standard")}function Ue(t,e){return!e||e==="eyes_standard"?W(t):t==="neutral"&&e==="eyes_standard"?null:ht(t,e)}function dn(t){if(!t)return"open";const e=t.match(/\/(eyes_[^/]+)_minus_sil\.png$/);if(!e?.[1])return"open";const n=e[1];return xe.includes(n)?n:"open"}function hn(t){return t==="open"?-1:xe.indexOf(t)}function _n(t,e){const n=[];for(const r of e)n.push(Ue(t,r));for(let r=e.length-2;r>=0;r--)n.push(Ue(t,e[r]));return n.push(W(t)),n}function mn(t,e){if(e==="open")return[W(t)];const n=hn(e);if(n<0)return[W(t)];const r=[];for(let s=n;s>=0;s--)r.push(Ue(t,xe[s]));return r.push(W(t)),r}const wn=["t01_0.20","t02_0.40","t03_0.60","t04_0.80"];function gn(t,e){return`${t}__${e}`}function pn(t,e,n){return`pairs/${gn(t,e)}/${n}_minus_sil.png`}function yn(t,e){return wn.map(n=>pn(t,e,n))}function _e(t,e){return t+Math.random()*(e-t)}function ke(){return _e(14,32)}function En(t={}){const{initialDelayMs:e=800+Math.random()*1200,intervalMinMs:n=2200,intervalMaxMs:r=5800,doubleBlinkChance:s=.18,doubleBlinkGapMinMs:a=120,doubleBlinkGapMaxMs:i=220,fullBlinkChance:c=.75,postEmotionChangeDelayMs:u=380}=t;let p="neutral",w="neutral",o={kind:"idle",nextBlinkAt:performance.now()+e},f=null,d="__eyes_open__|neutral";function S(h,y){f=h,d=`${h??"__eyes_open__"}|${y}`}function D(h,y){o={kind:"idle",nextBlinkAt:h+_e(n,r)},w=y,S(W(y),y)}function F(h,y,P){const O=P?un:Math.random()<c?xe:ln;o={kind:"playing",paths:_n(y,O),index:0,holdUntil:h+ke(),after:"idle",blinkEmotion:y},w=y,S(o.paths[0]??null,y)}function Z(h,y,P){const O=dn(f),M=[];if(O!=="open"){const K=mn(y,O);M.push(...K.slice(0,-1))}M.push(...yn(y,P)),M.push(W(P)),o={kind:"emotionBlend",paths:M,index:0,holdUntil:h+ke(),thenEmotion:P},S(M[0]??null,y)}function N(h){if(o.kind==="emotionBlend"){for(;h>=o.holdUntil&&o.index<o.paths.length-1;)o.index+=1,o.holdUntil=h+ke();const y=o.paths[o.index]??null;S(y,o.thenEmotion),h>=o.holdUntil&&o.index>=o.paths.length-1&&(p=o.thenEmotion,w=o.thenEmotion,S(W(o.thenEmotion),o.thenEmotion),o={kind:"idle",nextBlinkAt:h+u});return}if(o.kind==="idle"){S(W(w),w),h>=o.nextBlinkAt&&F(h,w,!1);return}if(o.kind==="doublePause"){S(W(o.blinkEmotion),o.blinkEmotion),h>=o.resumeAt&&F(h,o.blinkEmotion,!0);return}if(o.kind==="playing"){for(;h>=o.holdUntil&&o.index<o.paths.length-1;)o.index+=1,o.holdUntil=h+ke();if(S(o.paths[o.index]??null,o.blinkEmotion),h>=o.holdUntil&&o.index>=o.paths.length-1){if(o.after==="resumeIdle"){D(h+u,p);return}if(o.after==="doublePause"){o={kind:"doublePause",resumeAt:h+_e(a,i),blinkEmotion:o.blinkEmotion},S(W(o.blinkEmotion),o.blinkEmotion);return}if(o.after==="idle"&&Math.random()<s){o={kind:"doublePause",resumeAt:h+_e(a,i),blinkEmotion:o.blinkEmotion},S(W(o.blinkEmotion),o.blinkEmotion);return}D(h,o.blinkEmotion)}}}return{advance:N,setTargetEmotion(h){const y=ee(h,p);if(y===p&&o.kind!=="emotionBlend")return;const P=performance.now(),O=p;if(p=y,o.kind==="playing"){Z(P,w,y);return}if(o.kind==="emotionBlend"){o={...o,thenEmotion:y};return}Z(P,O,y)},getTargetEmotion:()=>p,getExpressionPath:()=>f,getDrawKey:()=>d,reset(){p="neutral",w="neutral",D(performance.now()+_e(n,r),"neutral")}}}function Sn(t){const e=document.createElement("video");e.muted=!0,e.playsInline=!0,e.preload="auto",e.setAttribute("playsinline",""),e.loop=!0;let n=!1;return{async load(){n||await new Promise((r,s)=>{const a=()=>{const i=e.duration;if(!Number.isFinite(i)||i<=0){s(new Error(`Idle video has invalid duration: ${t}`));return}n=!0,e.currentTime=0,r()};e.addEventListener("loadeddata",a,{once:!0}),e.addEventListener("error",()=>s(new Error(`Failed to load idle video: ${t}`)),{once:!0}),e.src=t,e.load()})},isReady(){return n},getVideo(){return e},setActive(r){n&&(r?e.paused&&e.play().catch(()=>{}):e.paused||e.pause())},restart(){n&&(e.currentTime=0)}}}function kn(t,e){for(const n of t)if(e>=n.wtime&&e<n.wtime+n.wduration)return n;return null}function vn(t,e,n,r="neutral"){const s=ee(r);if(e.length===0)return s;if(n.length===0)return ee(e[0]?.emotion,s);const a=kn(n,t);let i;if(a)i=a.queueIndex??n.findIndex(c=>c===a),i<0&&(i=0);else{if(t<n[0].wtime)return ee(e[0]?.emotion,s);i=n[n.length-1].queueIndex??n.length-1}for(const c of e)if(i>=c.start_word&&i<=c.end_word)return ee(c.emotion,s);for(const c of e)if(i<c.start_word)return ee(c.emotion,s);return ee(e[e.length-1]?.emotion,s)}function _t(t,e,n,r,s=Ne){const a=new OffscreenCanvas(n,r),i=a.getContext("2d",{willReadFrequently:!0});i.drawImage(t,0,0,n,r);const c=i.getImageData(0,0,n,r),u=i.createImageData(n,r);u.data.set(c.data);const w=new OffscreenCanvas(n,r).getContext("2d",{willReadFrequently:!0});for(const o of e){w.clearRect(0,0,n,r),w.drawImage(o,0,0,n,r);const f=w.getImageData(0,0,n,r);for(let d=0;d<u.data.length;d+=4){const S=f.data[d],D=f.data[d+1],F=f.data[d+2];Math.max(S,D,F)>s&&(u.data[d]=S,u.data[d+1]=D,u.data[d+2]=F,u.data[d+3]=255)}}return i.putImageData(u,0,0),a}let me=null,we=null;const Te=new Map;function An(){me=null,we=null,Te.clear()}xt(An);function Tn(t){return t??we??void 0}function He(t){return new Promise((e,n)=>{const r=new Image;r.decoding="async",r.onload=()=>e(r),r.onerror=()=>n(new Error(`Failed to load image: ${t}`)),r.src=t})}async function Je(t,e,n){const r=te()?await dt(t,n):await fetch(t).then(u=>{if(!u.ok)throw new Error(`Failed to load ${t}`);return u.json()}),s=r.sheets?.[0];if(!s?.path)throw new Error(`${t} has no sheets[0].path`);const a=te()?await Yt(await ft(on(e,s.path),n??"")):await He(`${e}/${s.path}`),i=new Map;for(const u of r.cells??[])u.path&&i.set(u.path,u);const c=new Map;for(const u of r.sheets??[])c.set(u.index,u);return{atlas:a,atlasMeta:r,cellByPath:i,sheetByIndex:c,diffBase:e}}async function Ie(t){const e=Tn(t);if(te()&&!e)throw new Error("Encrypted assets enabled but no key provided");if(me&&(!te()||we===(e??null)))return me;we=e??null,Te.clear();const n=Pt();return me=(async()=>{const r=await He(Dt()),s=await Je(nt(),n,e);let a=null;try{a=await Je(Mt(),n,e)}catch{a=null}return{sil:r,viseme:s,expression:a}})(),me}function We(t){return Qt(t.viseme.atlasMeta,t.sil.naturalWidth,t.sil.naturalHeight)}function In(t,e){const n=document.createElement("canvas");return n.width=e.w,n.height=e.h,n.getContext("2d",{willReadFrequently:!0}).drawImage(t.atlas,e.x,e.y,e.w,e.h,0,0,e.w,e.h),n}async function Xe(t,e,n){const r=`${e.diffBase}::${n}`,s=Te.get(r);if(s)return s;const a=(async()=>{const i=e.cellByPath.get(n);if(!i)throw new Error(`No atlas cell for path: ${n}`);const c=Jt(e.atlasMeta,i),u=We(t);if(Xt(i)){const o=In(e,i),f=document.createElement("canvas");f.width=u.width,f.height=u.height;const d=f.getContext("2d");return d.fillStyle="#000",d.fillRect(0,0,u.width,u.height),d.drawImage(o,c.x,c.y),f}const p=`${e.diffBase}/${n}`,w=await He(p);if(w.naturalWidth===u.width&&w.naturalHeight===u.height)return w;throw new Error(`Diff PNG wrong size for ${n}`)})();return Te.set(r,a),a}async function Ye(t,e,n,r=Ne){const s=await Ie(),{width:a,height:i}=We(s);(t.width!==a||t.height!==i)&&(t.width=a,t.height=i);const c=[],u=t.getContext("2d");if(u.clearRect(0,0,a,i),e.readyState<HTMLMediaElement.HAVE_CURRENT_DATA){const w=s.sil;u.drawImage(w,0,0,a,i);return}if(c.length===0){u.drawImage(e,0,0,a,i);return}const p=_t(e,c,a,i,r);u.drawImage(p,0,0)}async function Le(t,e,n=Ne){const r=await Ie(we??void 0),{width:s,height:a}=We(r);(t.width!==s||t.height!==a)&&(t.width=s,t.height=a);const i=[];e.mouthPath&&i.push(await Xe(r,r.viseme,e.mouthPath)),e.expressionPath&&r.expression&&i.push(await Xe(r,r.expression,e.expressionPath));const c=t.getContext("2d");if(c.clearRect(0,0,s,a),i.length===0){c.drawImage(r.sil,0,0,s,a);return}const u=_t(r.sil,i,s,a,n);c.drawImage(u,0,0)}async function Rn(t){await Le(t,{mouthPath:null,expressionPath:null})}function bn(t){const e=new Map;for(const n of t)e.has(n.vtime)||e.set(n.vtime,n);return[...e.values()].sort((n,r)=>n.vtime-r.vtime)}function xn(t){const e=bn(t),n=[];for(let r=0;r<e.length-1;r++){const s=e[r].viseme,a=e[r+1].viseme,i=e[r].vtime,c=e[r+1].vtime;s===a||c<=i||n.push({from:s,to:a,fromVtime:i,toVtime:c})}return n}function Dn(t,e){let n=null;for(const r of t)e>=r.fromVtime&&e<r.toVtime&&(n=r);return n}function Cn(t,e){return{gapMs:Math.max(0,e-t),holdEnd:t,transStart:t}}const Pn=500,Mn="__sil__";function Fn(){return{lastDrawnKey:""}}function he(t){t.lastDrawnKey=""}function Ze(t){return t??Mn}function On(t,e){if(t.length===0)return{diffPath:null,label:"sil"};const n=xn(t),r=Dn(n,e);if(r){const a=r.toVtime-r.fromVtime,i=Cn(r.fromVtime,r.toVtime);if(e>=i.transStart&&e<r.toVtime){const c=tn(r.from,r.to),u=rn(c,a);if(u.length>0){const p=sn(e,i.transStart,r.toVtime,u.length);return{diffPath:u[p]??u[u.length-1],label:`${r.from}→${r.to}`}}return{diffPath:Oe(r.to),label:r.to}}}const s=it(t,e);return s==="sil"?{diffPath:null,label:"sil"}:{diffPath:Oe(s),label:s}}function mt(t,e){const n=e??Ze(t.expressionPath);return`${Ze(t.mouthPath)}|${n}`}function Un(t,e,n,r,s,a,i,c){const u=On(e,n),p={mouthPath:u.diffPath,expressionPath:r},w=mt(p,s);w!==a.lastDrawnKey&&(a.lastDrawnKey=w,c(u.label),i(t,p))}function Ln(t,e,n,r,s,a,i=null){const c={mouthPath:null,expressionPath:e},u=i!=null?`|idle@${Math.round(i*1e3)}`:"",p=mt(c,n)+u;!(i!=null)&&p===r.lastDrawnKey||(r.lastDrawnKey=p,a(i!=null?"idle":"sil"),s(t,c))}function Vn(t){return new Worker("/assets/visemeDiffPreview.worker-B8Juk7ys.js",{name:t?.name})}function Bn(){return typeof Worker<"u"&&typeof OffscreenCanvas<"u"}function $n(t,e){(t.width!==e.width||t.height!==e.height)&&(t.width=e.width,t.height=e.height);const n=t.getContext("2d");n&&n.drawImage(e,0,0)}function et(t,e){let n=0;return{usesWorker:!1,async loadAssets(r){await Ie(r)},async renderSilOnly(){n+=1,await Rn(t)},async drawLiveFrame(r,s){await Ie(),await Le(r,s)},async renderViseme(r,s){n+=1;const a=n;e.onStatus?.(s),await Le(r,{mouthPath:Oe(s),expressionPath:null})},dispose(){n+=1}}}function Nn(t,e){const n=new Vn;let r=1,s=0,a=!1,i=!1;const c=new Map,u=(o,f)=>{i||n.postMessage(o,[])},p=(o,f,d=!1)=>{if(i)return Promise.resolve();const S=r++;return new Promise((D,F)=>{c.set(S,{resolve:()=>D(),reject:F,generation:typeof o.generation=="number"?o.generation:void 0,expectRenderDone:d}),u({...o,requestId:S})})};n.onmessage=o=>{if(i)return;const f=o.data;if(f.type==="status"){e.onStatus?.(f.label);return}if(f.type==="frame"){const S=f.generation<0;if(!S&&f.generation!==s){f.bitmap.close();return}if(S&&e.shouldAcceptLiveFrame&&!e.shouldAcceptLiveFrame()){f.bitmap.close();return}$n(t,f.bitmap),f.bitmap.close();return}const d=c.get(f.requestId);if(d)switch(f.type){case"ready":case"loadAssetsDone":c.delete(f.requestId),d.resolve();break;case"renderDone":if(d.expectRenderDone&&d.generation!=null&&f.generation!==d.generation)return;c.delete(f.requestId),d.resolve();break;case"renderAborted":c.delete(f.requestId),d.resolve();break;case"error":c.delete(f.requestId),e.onError?.(f.message),d.reject(new Error(f.message));break}},n.onerror=o=>{if(i)return;const f=o.message||"Worker error";e.onError?.(f);for(const[,d]of c)d.reject(new Error(f));c.clear()};const w=(async()=>{await p({type:"init"}),a=!0})();return{usesWorker:!0,async loadAssets(o){if(!i&&(await w,!i)){if(!a)throw new Error("Worker init failed");await p({type:"loadAssets",keyHex:o,urls:ne()})}},async renderSilOnly(){if(i||(await w,i))return;s+=1;const o=s;await p({type:"renderSil",generation:o},void 0,!0)},async drawLiveFrame(o,f){i||(await w,!i&&await p({type:"drawFrame",diffPath:f.mouthPath,expressionDiffPath:f.expressionPath},void 0,!0))},async renderViseme(o,f,d={}){if(i||(await w,i))return;s+=1;const S=s;try{await p({type:"renderViseme",generation:S,to:f,from:d.from,transitionDurationMs:d.transitionDurationMs,gapMs:d.gapMs,threshold:d.threshold},void 0,!0)}catch(D){if(D?.name==="AbortError")return;throw D}},dispose(){if(!i){i=!0,s+=1;for(const[,o]of c)o.resolve();c.clear(),n.terminate()}}}}function Hn(t,e={}){if(!Bn())return et(t,e);try{return Nn(t,e)}catch{return et(t,e)}}const Wn={position:"relative",display:"inline-block",lineHeight:0},Kn={display:"block",maxWidth:"100%",height:"auto"},qn={position:"absolute",inset:0,display:"flex",alignItems:"center",justifyContent:"center",padding:"1rem",background:"rgba(0,0,0,0.55)",color:"#fff",fontSize:"0.875rem",textAlign:"center"};function ve(t){return t==="cartesia"?ut:Be}const jn=_.forwardRef(function({id:e,assets:n,authToken:r,apiBase:s=Re,facesCdnBase:a=Ve,tts:i="google",voiceId:c,speakingRate:u=$e,className:p,style:w,canvasStyle:o,onStatusChange:f,onDisplayStatus:d,onError:S,onReady:D,showErrorOverlay:F=!0},Z){const N=_.useRef(null),h=_.useRef(null),y=_.useRef(Fn()),P=_.useRef(En()),O=_.useRef(0),M=_.useRef(0),K=_.useRef(null),re=_.useRef(!1),C=_.useRef(null),H=_.useRef(!1),[se,ue]=_.useState("idle"),[ie,le]=_.useState(null),[z,ge]=_.useState(!1),[fe,oe]=_.useState(!1),[pe,Ke]=_.useState({width:842,height:1264}),m=_.useRef(r??null),b=_.useRef(null),T=_.useRef(null),I=_.useRef(!1),x=_.useRef(D),R=_.useRef(d),q=_.useRef(S);x.current=D,R.current=d,q.current=S;const B=_.useCallback(l=>{const E=l?.tts??i??T.current?.tts??"google",k=l?.voiceId??c??T.current?.voiceId??ve(E);return{tts:E,voiceId:k}},[i,c]),A=_.useCallback(l=>{le(l),q.current?.(l)},[]),G=_.useCallback(l=>{ue(l),f?.(l)},[f]),v=_.useRef(lt(G,s)),$=_.useCallback(()=>{K.current!=null&&(clearTimeout(K.current),K.current=null)},[]),V=_.useCallback(async()=>{const l=M.current,E=h.current;!E||!z||v.current.isSpeaking()||(he(y.current),!H.current&&(await E.renderSilOnly(),l!==M.current||v.current.isSpeaking()))},[z]),J=_.useCallback(()=>{$();const l=M.current;V(),K.current=setTimeout(()=>{K.current=null,l===M.current&&V()},Pn)},[$,V]),ae=_.useCallback(async()=>{const l=Math.floor(Date.now()/1e3);if(b.current&&l<b.current.expiresAt-60)return b.current.key;const E=m.current??await Ce();m.current=E,v.current.setDeveloperToken(E);const k=await vt(E,s);return b.current={key:k.value,expiresAt:k.expiresAt},k.value},[s]);_.useEffect(()=>{r&&(m.current=r,v.current.setDeveloperToken(r))},[r]),_.useEffect(()=>{v.current.setTtsProvider(i),T.current&&(T.current.tts=i)},[i]),_.useEffect(()=>{const l=c??ve(i);T.current&&(T.current.voiceId=l)},[c,i]);const U=_.useMemo(()=>n?JSON.stringify(n):`id:${e??""}:${a}`,[n,e,a]);return _.useEffect(()=>{if(!e&&!n){A("AiTwin requires either `id` or `assets`");return}let l=!1;I.current=!1,ge(!1),oe(!1),le(null),T.current=null,C.current=null,H.current=!1;const E=N.current;if(!E)return;const k=Hn(E,{onStatus:g=>{l||R.current?.(g)},onError:g=>{l||A(g)},shouldAcceptLiveFrame:()=>v.current.isSpeaking()});return h.current=k,(async()=>{try{let g,L,Q;if(n)g=n,L=i,Q=c??ve(L);else{const Y=await tt(e);if(l)return;L=Y.tts,Q=c??Y.voiceId??ve(L),g=Rt(Y.faceId,a)}T.current={tts:L,voiceId:Q},bt(g),C.current=Sn(Ct());const X=m.current??r??await Ce();if(l)return;m.current=X,v.current.setDeveloperToken(X),v.current.setTtsProvider(L);const ye=te()?await ae():void 0;if(l)return;const j=await an(ye);if(!l&&j.width>0&&j.height>0&&Ke(j),await k.loadAssets(ye),l)return;let ce=!1;try{await C.current.load(),ce=!0,H.current=!0,oe(!0)}catch(Y){H.current=!1,oe(!1),console.warn("[AiTwin] Idle video unavailable:",Y)}if(!ce)await k.renderSilOnly(),R.current?.("sil");else{he(y.current);const Y=C.current.getVideo();C.current.setActive(!0);try{await Ye(E,Y,null),R.current?.("idle")}catch(de){console.warn("[AiTwin] Initial idle frame paint failed:",de)}}if(l)return;I.current=!0,ge(!0),x.current?.()}catch(g){if(l)return;if(g instanceof Ae){A(g.message);return}A(g instanceof Error?g.message:"Failed to load AI twin")}})(),()=>{l=!0,k.dispose(),h.current=null}},[U,e,a,r,s,ae,A]),_.useEffect(()=>{se==="done"&&z&&!v.current.isSpeaking()&&J()},[se,z,J]),_.useEffect(()=>{const l=v.current,E={current:!1},k=()=>{const g=N.current,L=h.current;if(!g||!L||!z){O.current=requestAnimationFrame(k);return}const Q=l.isSpeaking(),X=l.getPlaybackElapsedMs(),ye=Q?vn(X,l.getSentenceEmotions(),l.getWordQueue(),l.getStreamMood()):l.getStreamMood();if(Q){if(H.current&&C.current?.setActive(!1),l.getVisemeQueue().length===0){O.current=requestAnimationFrame(k);return}P.current.setTargetEmotion(ye),P.current.advance(performance.now());const ce=P.current.getExpressionPath(),Y=P.current.getDrawKey();Un(g,l.getVisemeQueue(),X,ce,Y,y.current,(de,wt)=>L.drawLiveFrame(de,wt),de=>R.current?.(de))}else if(H.current&&C.current){const j=C.current;j.setActive(!0),E.current||(E.current=!0,Ye(g,j.getVideo()).then(()=>R.current?.("idle")).finally(()=>{E.current=!1}))}else Ln(g,null,"__sil__",y.current,(j,ce)=>L.drawLiveFrame(j,ce),j=>R.current?.(j));re.current&&!Q&&(P.current.reset(),H.current&&C.current?.restart(),J()),re.current=Q,O.current=requestAnimationFrame(k)};return O.current=requestAnimationFrame(k),()=>{cancelAnimationFrame(O.current),$(),l.stop()}},[z,fe,J,$]),_.useImperativeHandle(Z,()=>({speakText:async(l,E)=>{const k=l.trim();if(!k)return;const g=h.current;if(!g||!I.current&&!z)throw new Error("AI twin is not ready");const{tts:L,voiceId:Q}=B(E);le(null),M.current+=1,$(),he(y.current),P.current.reset(),re.current=!1;try{H.current?(C.current?.setActive(!1),C.current?.restart(),he(y.current)):await g.renderSilOnly(),await v.current.speak(k,{voiceId:Q,speakingRate:E?.speakingRate??u,tts:L})}catch(X){throw A(X instanceof Error?X.message:"TTS failed"),X}},stop:()=>{M.current+=1,$(),v.current.stop(),he(y.current),re.current=!1,J()},setTtsProvider:l=>{v.current.setTtsProvider(l),T.current&&(T.current.tts=l)},renderViseme:async(l,E)=>{const k=h.current,g=N.current;if(!k||!g||!I.current)throw new Error("AI twin is not ready");await k.renderViseme(g,l,{from:E?.from,transitionDurationMs:E?.transitionDurationMs,gapMs:E?.gapMs,onStatus:L=>R.current?.(L)})},isReady:()=>I.current,getStatus:()=>se}),[z,u,$,J,A,B,se]),De.jsxs("div",{className:p,style:{...Wn,...w},children:[De.jsx("canvas",{ref:N,width:pe.width,height:pe.height,style:{...Kn,...o},"aria-label":e?`AI twin ${e}`:"AI twin face"}),F&&ie?De.jsx("div",{style:qn,children:ie}):null]})});exports.AiTwin=jn;exports.AiTwinNotFoundError=Ae;exports.DEFAULT_API_BASE=Re;exports.DEFAULT_FACES_CDN_BASE=Ve;exports.OCULUS_VISEME_IDS=rt;exports.SPEAKING_RATE_MAX=ct;exports.SPEAKING_RATE_MIN=at;exports.VISEME_IDS=Zt;exports.VISEME_TEST_CARTESIA_VOICE_ID=ut;exports.VISEME_TEST_SPEAKING_RATE=$e;exports.VISEME_TEST_VOICE_ID=Be;exports.createAvatarTtsLipsyncController=lt;exports.fetchAiTwin=tt;exports.fetchDevAuthToken=Ce;exports.normalizeOculusViseme=st;exports.resolveVisemeAtTime=it;exports.resolveWordAtTime=ot;
|