react-ai-avatar 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,7 @@
1
+ <p align="center">
2
+ <img src="./assets/banner.png" alt="react-ai-avatar — A face for your AI" width="100%" />
3
+ </p>
4
+
1
5
  # react-ai-avatar
2
6
 
3
7
  > A presentational React avatar for realtime LLM voice UIs — **you bring the connection, it brings the face.**
@@ -24,6 +28,7 @@ One thing, done well, embeddable in a few lines, no backend, MIT. The library ha
24
28
  - 🦺 **Graceful degradation** — `analyser={null}` while `state="speaking"`? The mouth animates with a synthetic speech-like pattern instead of freezing. Perfect for demos and non-WebRTC apps.
25
29
  - ⌨️ **Text-streaming LLMs too** — no audio? Drive the mouth from *token cadence* with `createSpeechActivity()`. A text-only assistant (OpenAI-style `/chat/completions` or `/responses` with `stream: true`) gets a face that visibly tracks the stream — busy while tokens arrive, settling on pauses.
26
30
  - 🧠 **A visible `thinking` state** — pulsing thought bubble + upward gaze. Your users *see* the LLM thinking, not just a color change.
31
+ - 🛠️ **A `working` state for tool use** — the fifth state, for agentic UIs. While the LLM runs a tool, the face goes amber and the state pill reads `Working: {tool}` (pass the tool name via the `tool` prop). Your users see *which* tool is running, not just a spinner.
27
32
  - 🎨 **Own-design avatar catalog** — `geometric`, `memoji`, `pixelart`, `doodle`: four MIT, CC0-safe SVG presets. No third-party assets, no attribution headaches.
28
33
  - 🎲 **DiceBear avatars (`dicebear`)** — generate deterministic [DiceBear](https://www.dicebear.com) avatars client-side, from a curated **CC0-only** style set (still no attribution). Animated with an audio-reactive bounce.
29
34
  - 🔌 **Bring your own SVG (`byos`)** — any SVG implementing the small layer contract gets the full animation runtime for free. Your avatar, your license.
@@ -52,7 +57,7 @@ import 'react-ai-avatar/style.css';
52
57
 
53
58
  export default function App() {
54
59
  // You resolve this in your app (Gemini, OpenAI Realtime, WebRTC, anything)
55
- const aiState = 'speaking'; // 'idle' | 'listening' | 'thinking' | 'speaking'
60
+ const aiState = 'speaking'; // 'idle' | 'listening' | 'thinking' | 'speaking' | 'working'
56
61
 
57
62
  return <RealtimeAvatar state={aiState} />;
58
63
  }
@@ -71,7 +76,7 @@ Every default is overridable. Opt into as much as you need:
71
76
  size={300} // default 280
72
77
  variant="geometric" // 'geometric' | 'memoji' | 'pixelart' | 'doodle' | 'dicebear' | 'vrm' | 'glb' | 'byos'
73
78
  customization={{ skinColor: '#f5c7a9', hairColor: '#2c2c2c', glasses: true, headphones: true }}
74
- stateColors={{ idle: '#4b5563', listening: '#3b82f6', thinking: '#8b5cf6', speaking: '#10b981' }}
79
+ stateColors={{ idle: '#4b5563', listening: '#3b82f6', thinking: '#8b5cf6', speaking: '#10b981', working: '#f59e0b' }}
75
80
  />
76
81
  ```
77
82
 
@@ -155,7 +160,8 @@ Optional data attributes: `data-base-x`/`data-base-y` (pupil rest position), `da
155
160
 
156
161
  ### `<RealtimeAvatar />`
157
162
 
158
- - `state` (`'idle' | 'listening' | 'thinking' | 'speaking'`) — required. You resolve it; it is never inferred.
163
+ - `state` (`'idle' | 'listening' | 'thinking' | 'speaking' | 'working'`) — required. You resolve it; it is never inferred. `working` is the tool-use state for agentic UIs (amber).
164
+ - `tool` (`string`) — optional. The name of the tool currently running. While `state="working"`, the state pill reads `Working: {tool}` instead of the generic label.
159
165
  - `analyser` (`AnalyserNode | null`) — optional. Drives the mouth from audio. Omitted or `null`, speaking falls back to the synthetic pattern.
160
166
  - `streamingText` (`string`) — optional. Declarative mouth driver: pass the accumulated assistant text (e.g. from `useChat`) and the avatar diffs its growth to drive the mouth. Takes precedence over `analyser`. See [Text-streaming LLMs](#text-streaming-llms-no-audio).
161
167
  - `speechActivity` (`SpeechActivitySource`) — optional. Imperative token-rate mouth driver, from `createSpeechActivity()`. Takes precedence over both `streamingText` and `analyser` when set.
@@ -166,8 +172,8 @@ Optional data attributes: `data-base-x`/`data-base-y` (pupil rest position), `da
166
172
  - `glbUrl` (`string`) — CORS-enabled `.glb` URL with ARKit blendshapes, for `variant="glb"`.
167
173
  - `dicebearCollection` (`string`) — DiceBear style id (curated CC0 set), for `variant="dicebear"`.
168
174
  - `dicebearSeed` (`string`) — deterministic DiceBear seed, for `variant="dicebear"`.
169
- - `subtitle` / `thought` (`string`) — optional movie-style captions and a thought bubble.
170
- - `showGlow` / `showStatePill` / `showThought` / `showSubtitle` (`boolean`) — HUD satellites, each `true` by default. Set any to `false` to hide it individually: the reactive glow, the state pill, the thought bubble, and the subtitle respectively.
175
+ - `subtitle` / `thought` (`string`) — optional movie-style caption and a thought bubble. Pass raw text or markdown: both are flattened to spoken prose and rolled to a trailing window internally, so a long streamed reply never overflows or shows raw `**`/tables. For a long assistant reply, keep the full markdown in your chat transcript and pass the same text here for the short live caption.
176
+ - `showGlow` / `showStatePill` / `showThought` / `showSubtitle` (`boolean`) — HUD satellites, each `true` by default. Set any to `false` to hide it individually: the reactive glow, the state pill, the thought bubble, and the subtitle respectively. The built-in subtitle/thought float `absolute` around the face (needs open canvas); inside a constrained card, set `showSubtitle={false}` / `showThought={false}` and render `<AvatarCaption>` / `<AvatarThought>` in your own layout slot instead.
171
177
  - `maxMouthOpening`, `mouseTrackingIntensity`, `blinkIntervalMin/Max`, `blinkDuration` — animation tuning.
172
178
  - `stateColors`, `stateLabels` — theming; labels are announced via `aria-live`.
173
179
  - `customization` — preset colors and accessories (skin, hair, clothing, glasses, headphones…).
@@ -183,7 +189,10 @@ Everything the runtime uses is exported, so you can compose your own:
183
189
  - `useStreamingTextActivity(text)` — declarative wrapper: diffs accumulated streaming text into a `SpeechActivitySource` for you (what the `streamingText` prop uses).
184
190
  - `useReducedMotion()` — SSR-safe `prefers-reduced-motion` hook.
185
191
  - `GeometricAvatar`, `MemojiAvatar`, `PixelArtAvatar`, `DoodleAvatar` — the raw presets.
192
+ - `SquirrelAvatar` — a full branded character (red-squirrel dev face) built on the `#rra-*` contract; the worked `byos` example, shipped so the demos render it from one source. See [`examples/08-character-avatar-squirrel.tsx`](examples/08-character-avatar-squirrel.tsx).
186
193
  - `AudioVisualizer` — Siri-style waveform telemetry strip.
194
+ - `AvatarCaption` / `AvatarThought` — host-placed caption + thought widgets. In-flow (not `absolute`), so they fit your own layout slot without overflow; both flatten markdown to spoken prose and roll a trailing window.
195
+ - `toPlainText(md)` / `tailWindow(text, { maxChars })` — the pure text helpers behind those widgets, for building your own caption.
187
196
 
188
197
  ## Getting an `AnalyserNode`
189
198
 
@@ -280,7 +289,7 @@ function TextAvatar() {
280
289
 
281
290
  `createSpeechActivity(options?)` accepts `chargePerChar`, `decayMs` and `maxChargePerPush` to tune how wide / how fast the mouth reacts. The returned source has `push(chunk)`, `end()`, `reset()` (drop energy on an interrupted turn) and `sample()`. When `speechActivity` is provided it takes precedence over both `streamingText` and `analyser`. (`streamingText` is just this, with the diffing done for you — under the hood it's the exported `useStreamingTextActivity` hook.)
282
291
 
283
- > The demo dashboard ships this end-to-end: toggle **TEXT (STREAM)** to talk to an OpenAI-compatible endpoint (set `OPENAI_BASE_URL` / `OPENAI_API_KEY` / `OPENAI_MODEL`, or leave them unset / `MOCK_REALTIME=true` for a no-key mock). See `src/demo/useStreamingLLM.ts` and the `/api/chat` route in `server.ts`.
292
+ > [`examples/03-streaming-text-imperative.tsx`](examples/03-streaming-text-imperative.tsx) shows this end-to-end against an OpenAI-compatible endpoint. The browser only ever talks to your own `/api/chat`; a tiny reference relay that proxies to the provider (so the key never reaches the client) lives in [`examples/server/proxy.ts`](examples/server/proxy.ts).
284
293
 
285
294
  ## Positioning
286
295
 
@@ -294,17 +303,21 @@ The closest reference is [TalkingHead](https://github.com/met4citizen/TalkingHea
294
303
  | Makes visible | the voice | the *thinking* |
295
304
  | Setup | avatar platform + Blender + rig | `npm i` + one component |
296
305
 
297
- ## Demo / development
306
+ ## Development
298
307
 
299
- The repo ships a demo dashboard (Gemini Live + a no-API-key mock):
308
+ This repo is the library only no app or backend. The runnable, hosted demos
309
+ live on the project's docs site (built separately, client-side mock, no API key).
300
310
 
301
311
  ```bash
302
312
  npm install
303
- npm run dev # starts the demo at :3000 (MOCK_REALTIME=true needs no API key)
304
313
  npm test # vitest: engine, layer contract, SSR, parsers
314
+ npm run lint # tsc --noEmit
305
315
  npm run build:lib # builds the publishable package into dist/lib
306
316
  ```
307
317
 
318
+ Copy-pasteable integration examples — including a reference relay server for
319
+ real voice/text providers — live in [`examples/`](examples/).
320
+
308
321
  ## License
309
322
 
310
323
  MIT — for the library, the runtime and all built-in presets. Use it commercially, fork it, reskin it. SVGs you bring via `byos` keep whatever license they had.
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("react/jsx-runtime"),n=require("react"),Y=require("@react-three/fiber"),he=require("three/addons/loaders/GLTFLoader.js"),me=require("three/addons/utils/SkeletonUtils.js"),de=require("@react-three/drei"),ge=require("three"),ae=require("./useReducedMotion-BRDEmRNI.cjs");function pe(f){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(f){for(const a in f)if(a!=="default"){const R=Object.getOwnPropertyDescriptor(f,a);Object.defineProperty(t,a,R.get?R:{enumerable:!0,get:()=>f[a]})}}return t.default=f,Object.freeze(t)}const W=pe(ge);class xe extends n.Component{constructor(t){super(t),this.state={hasError:!1}}static getDerivedStateFromError(){return{hasError:!0}}componentDidCatch(t){this.props.onError((t==null?void 0:t.message)||String(t))}render(){return this.state.hasError?null:this.props.children}}function be(f){const t=new Map,a=c=>c.toLowerCase().replace(/[^a-z]/g,"");f.traverse(c=>{const h=c,m=h.morphTargetDictionary;if(!(!m||!h.morphTargetInfluences))for(const i of Object.keys(m)){const g=m[i],r=a(i);t.has(r)||t.set(r,[]),t.get(r).push({mesh:h,index:g})}});const R=c=>{const h=a(c);if(t.has(h))return t.get(h);const m=[];for(const[i,g]of t)i.endsWith(h)&&m.push(...g);return m},j=new Map;return{has(c){return this.entries(c).length>0},entries(c){return j.has(c)||j.set(c,R(c)),j.get(c)},set(c,h){const m=this.entries(c),i=Math.max(0,Math.min(1,h));for(const{mesh:g,index:r}of m)g.morphTargetInfluences[r]=i},names:Array.from(t.keys())}}function ke({url:f,state:t,analyser:a,maxMouthOpening:R=30,mouseTrackingIntensity:j=1,blinkIntervalMin:c=2e3,blinkIntervalMax:h=6e3,blinkDuration:m=100,reducedMotion:i=!1,onLoaded:g}){const r=Y.useLoader(he.GLTFLoader,f),{camera:l,controls:q}=Y.useThree(),p=n.useMemo(()=>me.clone(r.scene),[r.scene]),e=n.useMemo(()=>be(p),[p]);n.useEffect(()=>{var B;g(!0),console.log("[GLB] Model loaded. ARKit blendshapes found:",e.names),p.traverse(G=>{G.frustumCulled=!1}),p.updateMatrixWorld(!0);const L=new W.Box3().setFromObject(p),b=new W.Vector3,o=new W.Vector3;L.getSize(b),L.getCenter(o);const k=L.max.y-b.y*.08,Z=Math.max(.45,b.y*.42);l.position.set(o.x,k,L.max.z+Z),l.lookAt(o.x,k,o.z),l.updateProjectionMatrix();const w=q;w!=null&&w.target&&(w.target.set(o.x,k,o.z),(B=w.update)==null||B.call(w))},[p,e,l,q,g]);const S=n.useRef(0),O=n.useRef(0),v=n.useRef(0),z=n.useRef(0),T=n.useRef(0),A=n.useRef(0),d=n.useRef(0),u=n.useRef(0),D=n.useRef(t),$=n.useRef(0),I=n.useRef(0),V=n.useRef(0),X=n.useRef(0),J=n.useRef(Math.random()*3+2),x=n.useRef(0),N=n.useRef(!1),K=n.useRef(null),ee=n.useRef(null);return Y.useFrame((L,b)=>{var oe,ue;const o=W.MathUtils.lerp,k=L.clock.elapsedTime;if(t!==D.current&&(t==="listening"&&!i&&(u.current=1),D.current==="speaking"&&t!=="speaking"&&!i&&(I.current=2),D.current=t),u.current=Math.max(0,u.current-b*2.5),I.current=Math.max(0,I.current-b),!i)if(!N.current)J.current-=b,J.current<=0&&(N.current=!0);else{const M=1e3/(m||100);x.current+=b*M*2,x.current>=1&&(x.current=1,N.current=!1,J.current=Math.random()*((h-c)/1e3)+c/1e3)}if(!N.current&&x.current>0){const M=1e3/(m||100);x.current-=b*M*2,x.current<0&&(x.current=0)}e.set("eyeBlinkLeft",x.current),e.set("eyeBlinkRight",x.current);const Z=((oe=L.pointer)==null?void 0:oe.x)??0,w=((ue=L.pointer)==null?void 0:ue.y)??0,B=i?0:j;let G=Z*B,Q=w*B;t==="thinking"?(G=-.5*(i?0:1),Q=.6*(i?0:1)):i||(G+=Math.sin(k*.23)*.12,Q+=Math.sin(k*.31+1.3)*.08),V.current=o(V.current,G,.1),X.current=o(X.current,Q,.1);const F=V.current,U=X.current;e.set("eyeLookOutRight",Math.max(0,F)),e.set("eyeLookInLeft",Math.max(0,F)),e.set("eyeLookOutLeft",Math.max(0,-F)),e.set("eyeLookInRight",Math.max(0,-F)),e.set("eyeLookUpLeft",Math.max(0,U)),e.set("eyeLookUpRight",Math.max(0,U)),e.set("eyeLookDownLeft",Math.max(0,-U)),e.set("eyeLookDownRight",Math.max(0,-U));let E=0,te=0,ne=0;t==="listening"?(E=.35,te=.25):t==="thinking"?ne=.45:t==="speaking"?E=.12:E=.05,I.current>0&&t!=="speaking"&&(E=Math.max(E,.55));const le=t==="thinking"&&!i?1:0;T.current=o(T.current,E,.1),A.current=o(A.current,te,.1),d.current=o(d.current,ne,.1),$.current=o($.current,le,.08);const y=T.current,_=$.current;e.set("mouthSmileLeft",y),e.set("mouthSmileRight",y),e.set("browInnerUp",A.current),e.set("browDownLeft",d.current),e.set("browDownRight",d.current),e.set("cheekSquintLeft",y*.6),e.set("cheekSquintRight",y*.6),e.set("mouthDimpleLeft",y*.5),e.set("mouthDimpleRight",y*.5),e.set("eyeSquintLeft",Math.max(y*.35,_*.45)),e.set("eyeSquintRight",Math.max(y*.35,_*.45)),e.set("mouthPressLeft",_*.4),e.set("mouthPressRight",_*.4),e.set("eyeWideLeft",u.current*.7),e.set("eyeWideRight",u.current*.7),e.set("browOuterUpLeft",u.current*.6),e.set("browOuterUpRight",u.current*.6);let H=0,re=0,se=0,ce=0;if(t==="speaking"){(!K.current||ee.current!==a)&&(K.current=ae.createMouthEngine(a),ee.current=a);const M=K.current.read(),fe=R/30,P=Math.min(1,M.level*fe*1.7);M.shape==="o"?(re=P*.85,se=P*.6,H=P*.35):M.shape==="e"?(ce=P*.7,H=P*.3):M.shape==="a"&&(H=P*.9)}S.current=o(S.current,H,.25),O.current=o(O.current,re,.25),v.current=o(v.current,se,.25),z.current=o(z.current,ce,.25),e.set("jawOpen",S.current),e.set("mouthFunnel",O.current),e.set("mouthPucker",v.current),e.set("mouthStretchLeft",z.current),e.set("mouthStretchRight",z.current);const C=i||t==="speaking"||t==="thinking"?0:1,ie=Math.sin(k*.4)*.06*C;e.set("mouthLeft",Math.max(0,ie)),e.set("mouthRight",Math.max(0,-ie)),e.set("mouthRollLower",(Math.sin(k*.33)*.5+.5)*.07*C),e.set("mouthShrugUpper",(Math.sin(k*.7)*.5+.5)*.06*C)}),s.jsx("primitive",{object:p})}function we({state:f,analyser:t,size:a=300,className:R="",style:j,maxMouthOpening:c,blinkIntervalMin:h,blinkIntervalMax:m,blinkDuration:i,mouseTrackingIntensity:g,stateColors:r,glbUrl:l}){const[q,p]=n.useState(!1),[e,S]=n.useState(null),[O,v]=n.useState(!1),z=ae.useReducedMotion(),T=n.useCallback(d=>p(d),[]);n.useEffect(()=>{if(p(!1),S(null),v(!1),!l)return;let d=!1;return fetch(l).then(u=>{const D=u.headers.get("content-type")||"";if(!u.ok)throw new Error(`HTTP ${u.status} for ${l}`);if(D.includes("text/html"))throw new Error(`No .glb found at ${l} (server returned HTML).`);d||v(!0)}).catch(u=>{d||S((u==null?void 0:u.message)||String(u))}),()=>{d=!0}},[l]);const A={idle:(r==null?void 0:r.idle)??"#4b5563",listening:(r==null?void 0:r.listening)??"#3b82f6",thinking:(r==null?void 0:r.thinking)??"#8b5cf6",speaking:(r==null?void 0:r.speaking)??"#10b981",working:(r==null?void 0:r.working)??"#f59e0b"};return s.jsxs("div",{className:`relative flex items-center justify-center rounded-3xl overflow-hidden border border-zinc-800/40 bg-zinc-950/40 ${R}`,style:{width:a,height:a,...j},children:[s.jsxs(Y.Canvas,{camera:{position:[0,1.5,1],fov:35},shadows:!0,gl:{antialias:!0,alpha:!0,preserveDrawingBuffer:!0},style:{width:"100%",height:"100%",background:"transparent"},children:[s.jsx("ambientLight",{intensity:1.5}),s.jsx("spotLight",{position:[0,3,2],angle:.6,penumbra:1,intensity:3,color:A[f],castShadow:!0}),s.jsx("directionalLight",{position:[-2,2,-2],intensity:1.8,color:"#ffffff"}),s.jsx("directionalLight",{position:[2,2,2],intensity:2.2,color:"#ffffff"}),s.jsx(xe,{onError:d=>S(d),children:l&&O&&s.jsx(n.Suspense,{fallback:null,children:s.jsx(ke,{url:l,state:f,analyser:t,maxMouthOpening:c,mouseTrackingIntensity:g,blinkIntervalMin:h,blinkIntervalMax:m,blinkDuration:i,reducedMotion:z,onLoaded:T})})}),s.jsx(de.OrbitControls,{makeDefault:!0,enableZoom:!1,enablePan:!1,minPolarAngle:Math.PI/2.6,maxPolarAngle:Math.PI/1.7,minAzimuthAngle:-Math.PI/4,maxAzimuthAngle:Math.PI/4})]}),!l&&s.jsxs("div",{className:"absolute inset-0 flex flex-col items-center justify-center bg-zinc-950/90 backdrop-blur-md z-20 p-4 text-center",children:[s.jsx("span",{className:"text-xs font-mono font-bold text-amber-400 uppercase tracking-wider mb-2",children:"Missing glbUrl"}),s.jsx("p",{className:"text-[10px] text-zinc-500 max-w-[200px] leading-relaxed",children:"Pass a CORS-enabled .glb URL (with ARKit blendshapes) via the glbUrl prop."})]}),l&&!q&&!e&&s.jsxs("div",{className:"absolute inset-0 flex flex-col items-center justify-center bg-zinc-950/80 backdrop-blur-md z-20",children:[s.jsx("div",{className:"w-10 h-10 border-4 border-t-emerald-500 border-emerald-500/20 rounded-full animate-spin mb-3"}),s.jsx("span",{className:"text-[10px] font-mono font-bold tracking-widest text-zinc-400 animate-pulse",children:"LOADING GLB MODEL..."})]}),e&&s.jsxs("div",{className:"absolute inset-0 flex flex-col items-center justify-center bg-zinc-950/90 backdrop-blur-md z-20 p-4 text-center",children:[s.jsx("span",{className:"text-xs font-mono font-bold text-red-400 uppercase tracking-wider mb-2",children:"Failed to load GLB"}),s.jsx("p",{className:"text-[10px] text-zinc-500 max-w-[200px] leading-relaxed break-all",children:e})]})]})}exports.GlbArkitAvatar=we;
@@ -0,0 +1,233 @@
1
+ import { jsxs as O, jsx as a } from "react/jsx-runtime";
2
+ import de, { useState as ee, useCallback as pe, useEffect as he, Suspense as ge, useMemo as le, useRef as s } from "react";
3
+ import { Canvas as xe, useLoader as ke, useThree as be, useFrame as we } from "@react-three/fiber";
4
+ import { GLTFLoader as Le } from "three/addons/loaders/GLTFLoader.js";
5
+ import { clone as ye } from "three/addons/utils/SkeletonUtils.js";
6
+ import { OrbitControls as Me } from "@react-three/drei";
7
+ import * as H from "three";
8
+ import { u as Re, c as ze } from "./useReducedMotion-BDcEizfP.js";
9
+ class Se extends de.Component {
10
+ constructor(t) {
11
+ super(t), this.state = { hasError: !1 };
12
+ }
13
+ static getDerivedStateFromError() {
14
+ return { hasError: !0 };
15
+ }
16
+ componentDidCatch(t) {
17
+ this.props.onError((t == null ? void 0 : t.message) || String(t));
18
+ }
19
+ render() {
20
+ return this.state.hasError ? null : this.props.children;
21
+ }
22
+ }
23
+ function ve(M) {
24
+ const t = /* @__PURE__ */ new Map(), p = (r) => r.toLowerCase().replace(/[^a-z]/g, "");
25
+ M.traverse((r) => {
26
+ const l = r, h = l.morphTargetDictionary;
27
+ if (!(!h || !l.morphTargetInfluences))
28
+ for (const c of Object.keys(h)) {
29
+ const f = h[c], n = p(c);
30
+ t.has(n) || t.set(n, []), t.get(n).push({ mesh: l, index: f });
31
+ }
32
+ });
33
+ const A = (r) => {
34
+ const l = p(r);
35
+ if (t.has(l)) return t.get(l);
36
+ const h = [];
37
+ for (const [c, f] of t)
38
+ c.endsWith(l) && h.push(...f);
39
+ return h;
40
+ }, R = /* @__PURE__ */ new Map();
41
+ return {
42
+ has(r) {
43
+ return this.entries(r).length > 0;
44
+ },
45
+ entries(r) {
46
+ return R.has(r) || R.set(r, A(r)), R.get(r);
47
+ },
48
+ set(r, l) {
49
+ const h = this.entries(r), c = Math.max(0, Math.min(1, l));
50
+ for (const { mesh: f, index: n } of h)
51
+ f.morphTargetInfluences[n] = c;
52
+ },
53
+ /** All normalized morph-target names found, for debugging. */
54
+ names: Array.from(t.keys())
55
+ };
56
+ }
57
+ function Ee({
58
+ url: M,
59
+ state: t,
60
+ analyser: p,
61
+ maxMouthOpening: A = 30,
62
+ mouseTrackingIntensity: R = 1,
63
+ blinkIntervalMin: r = 2e3,
64
+ blinkIntervalMax: l = 6e3,
65
+ blinkDuration: h = 100,
66
+ reducedMotion: c = !1,
67
+ onLoaded: f
68
+ }) {
69
+ const n = ke(Le, M), { camera: u, controls: N } = be(), d = le(() => ye(n.scene), [n.scene]), e = le(() => ve(d), [d]);
70
+ he(() => {
71
+ var G;
72
+ f(!0), console.log("[GLB] Model loaded. ARKit blendshapes found:", e.names), d.traverse((I) => {
73
+ I.frustumCulled = !1;
74
+ }), d.updateMatrixWorld(!0);
75
+ const w = new H.Box3().setFromObject(d), x = new H.Vector3(), i = new H.Vector3();
76
+ w.getSize(x), w.getCenter(i);
77
+ const k = w.max.y - x.y * 0.08, Q = Math.max(0.45, x.y * 0.42);
78
+ u.position.set(i.x, k, w.max.z + Q), u.lookAt(i.x, k, i.z), u.updateProjectionMatrix();
79
+ const b = N;
80
+ b != null && b.target && (b.target.set(i.x, k, i.z), (G = b.update) == null || G.call(b));
81
+ }, [d, e, u, N, f]);
82
+ const z = s(0), B = s(0), S = s(0), v = s(0), D = s(0), T = s(0), m = s(0), o = s(0), j = s(t), V = s(0), F = s(0), X = s(0), J = s(0), K = s(Math.random() * 3 + 2), g = s(0), q = s(!1), Z = s(null), te = s(null);
83
+ return we((w, x) => {
84
+ var ae, ue;
85
+ const i = H.MathUtils.lerp, k = w.clock.elapsedTime;
86
+ if (t !== j.current && (t === "listening" && !c && (o.current = 1), j.current === "speaking" && t !== "speaking" && !c && (F.current = 2), j.current = t), o.current = Math.max(0, o.current - x * 2.5), F.current = Math.max(0, F.current - x), !c)
87
+ if (!q.current)
88
+ K.current -= x, K.current <= 0 && (q.current = !0);
89
+ else {
90
+ const y = 1e3 / (h || 100);
91
+ g.current += x * y * 2, g.current >= 1 && (g.current = 1, q.current = !1, K.current = Math.random() * ((l - r) / 1e3) + r / 1e3);
92
+ }
93
+ if (!q.current && g.current > 0) {
94
+ const y = 1e3 / (h || 100);
95
+ g.current -= x * y * 2, g.current < 0 && (g.current = 0);
96
+ }
97
+ e.set("eyeBlinkLeft", g.current), e.set("eyeBlinkRight", g.current);
98
+ const Q = ((ae = w.pointer) == null ? void 0 : ae.x) ?? 0, b = ((ue = w.pointer) == null ? void 0 : ue.y) ?? 0, G = c ? 0 : R;
99
+ let I = Q * G, _ = b * G;
100
+ t === "thinking" ? (I = -0.5 * (c ? 0 : 1), _ = 0.6 * (c ? 0 : 1)) : c || (I += Math.sin(k * 0.23) * 0.12, _ += Math.sin(k * 0.31 + 1.3) * 0.08), X.current = i(X.current, I, 0.1), J.current = i(J.current, _, 0.1);
101
+ const U = X.current, W = J.current;
102
+ e.set("eyeLookOutRight", Math.max(0, U)), e.set("eyeLookInLeft", Math.max(0, U)), e.set("eyeLookOutLeft", Math.max(0, -U)), e.set("eyeLookInRight", Math.max(0, -U)), e.set("eyeLookUpLeft", Math.max(0, W)), e.set("eyeLookUpRight", Math.max(0, W)), e.set("eyeLookDownLeft", Math.max(0, -W)), e.set("eyeLookDownRight", Math.max(0, -W));
103
+ let E = 0, ne = 0, re = 0;
104
+ t === "listening" ? (E = 0.35, ne = 0.25) : t === "thinking" ? re = 0.45 : t === "speaking" ? E = 0.12 : E = 0.05, F.current > 0 && t !== "speaking" && (E = Math.max(E, 0.55));
105
+ const me = t === "thinking" && !c ? 1 : 0;
106
+ D.current = i(D.current, E, 0.1), T.current = i(T.current, ne, 0.1), m.current = i(m.current, re, 0.1), V.current = i(V.current, me, 0.08);
107
+ const L = D.current, Y = V.current;
108
+ e.set("mouthSmileLeft", L), e.set("mouthSmileRight", L), e.set("browInnerUp", T.current), e.set("browDownLeft", m.current), e.set("browDownRight", m.current), e.set("cheekSquintLeft", L * 0.6), e.set("cheekSquintRight", L * 0.6), e.set("mouthDimpleLeft", L * 0.5), e.set("mouthDimpleRight", L * 0.5), e.set("eyeSquintLeft", Math.max(L * 0.35, Y * 0.45)), e.set("eyeSquintRight", Math.max(L * 0.35, Y * 0.45)), e.set("mouthPressLeft", Y * 0.4), e.set("mouthPressRight", Y * 0.4), e.set("eyeWideLeft", o.current * 0.7), e.set("eyeWideRight", o.current * 0.7), e.set("browOuterUpLeft", o.current * 0.6), e.set("browOuterUpRight", o.current * 0.6);
109
+ let $ = 0, ce = 0, se = 0, ie = 0;
110
+ if (t === "speaking") {
111
+ (!Z.current || te.current !== p) && (Z.current = ze(p), te.current = p);
112
+ const y = Z.current.read(), fe = A / 30, P = Math.min(1, y.level * fe * 1.7);
113
+ y.shape === "o" ? (ce = P * 0.85, se = P * 0.6, $ = P * 0.35) : y.shape === "e" ? (ie = P * 0.7, $ = P * 0.3) : y.shape === "a" && ($ = P * 0.9);
114
+ }
115
+ z.current = i(z.current, $, 0.25), B.current = i(B.current, ce, 0.25), S.current = i(S.current, se, 0.25), v.current = i(v.current, ie, 0.25), e.set("jawOpen", z.current), e.set("mouthFunnel", B.current), e.set("mouthPucker", S.current), e.set("mouthStretchLeft", v.current), e.set("mouthStretchRight", v.current);
116
+ const C = c || t === "speaking" || t === "thinking" ? 0 : 1, oe = Math.sin(k * 0.4) * 0.06 * C;
117
+ e.set("mouthLeft", Math.max(0, oe)), e.set("mouthRight", Math.max(0, -oe)), e.set("mouthRollLower", (Math.sin(k * 0.33) * 0.5 + 0.5) * 0.07 * C), e.set("mouthShrugUpper", (Math.sin(k * 0.7) * 0.5 + 0.5) * 0.06 * C);
118
+ }), /* @__PURE__ */ a("primitive", { object: d });
119
+ }
120
+ function Ie({
121
+ state: M,
122
+ analyser: t,
123
+ size: p = 300,
124
+ className: A = "",
125
+ style: R,
126
+ maxMouthOpening: r,
127
+ blinkIntervalMin: l,
128
+ blinkIntervalMax: h,
129
+ blinkDuration: c,
130
+ mouseTrackingIntensity: f,
131
+ stateColors: n,
132
+ glbUrl: u
133
+ }) {
134
+ const [N, d] = ee(!1), [e, z] = ee(null), [B, S] = ee(!1), v = Re(), D = pe((m) => d(m), []);
135
+ he(() => {
136
+ if (d(!1), z(null), S(!1), !u) return;
137
+ let m = !1;
138
+ return fetch(u).then((o) => {
139
+ const j = o.headers.get("content-type") || "";
140
+ if (!o.ok) throw new Error(`HTTP ${o.status} for ${u}`);
141
+ if (j.includes("text/html"))
142
+ throw new Error(`No .glb found at ${u} (server returned HTML).`);
143
+ m || S(!0);
144
+ }).catch((o) => {
145
+ m || z((o == null ? void 0 : o.message) || String(o));
146
+ }), () => {
147
+ m = !0;
148
+ };
149
+ }, [u]);
150
+ const T = {
151
+ idle: (n == null ? void 0 : n.idle) ?? "#4b5563",
152
+ listening: (n == null ? void 0 : n.listening) ?? "#3b82f6",
153
+ thinking: (n == null ? void 0 : n.thinking) ?? "#8b5cf6",
154
+ speaking: (n == null ? void 0 : n.speaking) ?? "#10b981",
155
+ working: (n == null ? void 0 : n.working) ?? "#f59e0b"
156
+ };
157
+ return /* @__PURE__ */ O(
158
+ "div",
159
+ {
160
+ className: `relative flex items-center justify-center rounded-3xl overflow-hidden border border-zinc-800/40 bg-zinc-950/40 ${A}`,
161
+ style: { width: p, height: p, ...R },
162
+ children: [
163
+ /* @__PURE__ */ O(
164
+ xe,
165
+ {
166
+ camera: { position: [0, 1.5, 1], fov: 35 },
167
+ shadows: !0,
168
+ gl: { antialias: !0, alpha: !0, preserveDrawingBuffer: !0 },
169
+ style: { width: "100%", height: "100%", background: "transparent" },
170
+ children: [
171
+ /* @__PURE__ */ a("ambientLight", { intensity: 1.5 }),
172
+ /* @__PURE__ */ a(
173
+ "spotLight",
174
+ {
175
+ position: [0, 3, 2],
176
+ angle: 0.6,
177
+ penumbra: 1,
178
+ intensity: 3,
179
+ color: T[M],
180
+ castShadow: !0
181
+ }
182
+ ),
183
+ /* @__PURE__ */ a("directionalLight", { position: [-2, 2, -2], intensity: 1.8, color: "#ffffff" }),
184
+ /* @__PURE__ */ a("directionalLight", { position: [2, 2, 2], intensity: 2.2, color: "#ffffff" }),
185
+ /* @__PURE__ */ a(Se, { onError: (m) => z(m), children: u && B && /* @__PURE__ */ a(ge, { fallback: null, children: /* @__PURE__ */ a(
186
+ Ee,
187
+ {
188
+ url: u,
189
+ state: M,
190
+ analyser: t,
191
+ maxMouthOpening: r,
192
+ mouseTrackingIntensity: f,
193
+ blinkIntervalMin: l,
194
+ blinkIntervalMax: h,
195
+ blinkDuration: c,
196
+ reducedMotion: v,
197
+ onLoaded: D
198
+ }
199
+ ) }) }),
200
+ /* @__PURE__ */ a(
201
+ Me,
202
+ {
203
+ makeDefault: !0,
204
+ enableZoom: !1,
205
+ enablePan: !1,
206
+ minPolarAngle: Math.PI / 2.6,
207
+ maxPolarAngle: Math.PI / 1.7,
208
+ minAzimuthAngle: -Math.PI / 4,
209
+ maxAzimuthAngle: Math.PI / 4
210
+ }
211
+ )
212
+ ]
213
+ }
214
+ ),
215
+ !u && /* @__PURE__ */ O("div", { className: "absolute inset-0 flex flex-col items-center justify-center bg-zinc-950/90 backdrop-blur-md z-20 p-4 text-center", children: [
216
+ /* @__PURE__ */ a("span", { className: "text-xs font-mono font-bold text-amber-400 uppercase tracking-wider mb-2", children: "Missing glbUrl" }),
217
+ /* @__PURE__ */ a("p", { className: "text-[10px] text-zinc-500 max-w-[200px] leading-relaxed", children: "Pass a CORS-enabled .glb URL (with ARKit blendshapes) via the glbUrl prop." })
218
+ ] }),
219
+ u && !N && !e && /* @__PURE__ */ O("div", { className: "absolute inset-0 flex flex-col items-center justify-center bg-zinc-950/80 backdrop-blur-md z-20", children: [
220
+ /* @__PURE__ */ a("div", { className: "w-10 h-10 border-4 border-t-emerald-500 border-emerald-500/20 rounded-full animate-spin mb-3" }),
221
+ /* @__PURE__ */ a("span", { className: "text-[10px] font-mono font-bold tracking-widest text-zinc-400 animate-pulse", children: "LOADING GLB MODEL..." })
222
+ ] }),
223
+ e && /* @__PURE__ */ O("div", { className: "absolute inset-0 flex flex-col items-center justify-center bg-zinc-950/90 backdrop-blur-md z-20 p-4 text-center", children: [
224
+ /* @__PURE__ */ a("span", { className: "text-xs font-mono font-bold text-red-400 uppercase tracking-wider mb-2", children: "Failed to load GLB" }),
225
+ /* @__PURE__ */ a("p", { className: "text-[10px] text-zinc-500 max-w-[200px] leading-relaxed break-all", children: e })
226
+ ] })
227
+ ]
228
+ }
229
+ );
230
+ }
231
+ export {
232
+ Ie as GlbArkitAvatar
233
+ };
@@ -0,0 +1,245 @@
1
+ import { jsxs as O, jsx as i } from "react/jsx-runtime";
2
+ import oe, { useState as H, useEffect as ie, useCallback as ne, Suspense as se, useRef as d } from "react";
3
+ import { Canvas as ce, useFrame as le } from "@react-three/fiber";
4
+ import { GLTFLoader as ue } from "three/addons/loaders/GLTFLoader.js";
5
+ import { VRMLoaderPlugin as de, VRMUtils as ae } from "@pixiv/three-vrm";
6
+ import { OrbitControls as pe } from "@react-three/drei";
7
+ import * as a from "three";
8
+ import { u as me, c as fe } from "./useReducedMotion-BDcEizfP.js";
9
+ class he extends oe.Component {
10
+ constructor(r) {
11
+ super(r), this.state = { hasError: !1 };
12
+ }
13
+ static getDerivedStateFromError() {
14
+ return { hasError: !0 };
15
+ }
16
+ componentDidCatch(r) {
17
+ this.props.onError((r == null ? void 0 : r.message) || String(r));
18
+ }
19
+ render() {
20
+ return this.state.hasError ? null : this.props.children;
21
+ }
22
+ }
23
+ function f(l, r, h) {
24
+ if (!l.expressionManager) return;
25
+ const E = l.expressionManager.expressionMap || {}, v = Object.keys(E);
26
+ let o = [];
27
+ switch (r) {
28
+ case "blink":
29
+ o = ["blink", "Blink", "BLINK"];
30
+ break;
31
+ case "aa":
32
+ o = ["aa", "Aa", "a", "A", "AA", "mouth_a", "mouth_A"];
33
+ break;
34
+ case "ee":
35
+ o = ["ee", "Ee", "e", "E", "EE", "mouth_e", "mouth_E"];
36
+ break;
37
+ case "oo":
38
+ o = ["oh", "Oh", "oo", "Oo", "o", "O", "OO", "mouth_o", "mouth_O"];
39
+ break;
40
+ case "happy":
41
+ o = ["happy", "Happy", "joy", "Joy", "JOY", "fun", "Fun", "FUN", "HAPPY"];
42
+ break;
43
+ case "sad":
44
+ o = ["sad", "Sad", "sorrow", "Sorrow", "SORROW", "sadness", "SAD"];
45
+ break;
46
+ case "relaxed":
47
+ o = ["relaxed", "Relaxed", "fun", "Fun", "FUN", "neutral", "Neutral", "RELAXED"];
48
+ break;
49
+ }
50
+ const A = o.find((x) => E[x] !== void 0 || v.includes(x));
51
+ if (A)
52
+ l.expressionManager.setValue(A, h);
53
+ else
54
+ try {
55
+ l.expressionManager.setValue(o[0], h);
56
+ } catch {
57
+ }
58
+ }
59
+ function ge({
60
+ url: l,
61
+ state: r,
62
+ analyser: h,
63
+ maxMouthOpening: E = 30,
64
+ mouseTrackingIntensity: v = 1,
65
+ blinkIntervalMin: o = 2e3,
66
+ blinkIntervalMax: A = 6e3,
67
+ blinkDuration: x = 100,
68
+ reducedMotion: P = !1,
69
+ onLoaded: S,
70
+ onError: t
71
+ }) {
72
+ const [e, B] = H(null);
73
+ ie(() => {
74
+ let b = !1, M = null;
75
+ const c = new ue();
76
+ return c.register((n) => new de(n)), c.load(
77
+ l,
78
+ (n) => {
79
+ var D, L;
80
+ if (b) {
81
+ const y = n.userData.vrm;
82
+ y && ae.deepDispose(y.scene);
83
+ return;
84
+ }
85
+ const k = n.userData.vrm;
86
+ if (!k) {
87
+ t(`No VRM found at ${l}.`);
88
+ return;
89
+ }
90
+ console.log(
91
+ "[VRM] Model loaded successfully. Available expressions:",
92
+ Object.keys(((D = k.expressionManager) == null ? void 0 : D.expressionMap) || {})
93
+ ), k.scene.traverse((y) => {
94
+ y.frustumCulled = !1;
95
+ });
96
+ const F = ((L = k.meta) == null ? void 0 : L.metaVersion) === "0";
97
+ k.scene.rotation.y = F ? Math.PI : 0, M = k, B(k), S(!0);
98
+ },
99
+ void 0,
100
+ (n) => {
101
+ b || t((n == null ? void 0 : n.message) || String(n));
102
+ }
103
+ ), () => {
104
+ b = !0, M && ae.deepDispose(M.scene);
105
+ };
106
+ }, [l, S, t]);
107
+ const g = d(0), p = d(0), m = d(0), U = d(0), w = d(0), z = d(0), V = d(Math.random() * 3 + 2), s = d(0), j = d(!1), I = d(null), K = d(null);
108
+ return le((b, M) => {
109
+ var T, X, G, J, $, W, Z, q, Q;
110
+ if (!e) return;
111
+ e.update(M);
112
+ const c = (T = e.humanoid) == null ? void 0 : T.getNormalizedBoneNode("neck"), n = (X = e.humanoid) == null ? void 0 : X.getNormalizedBoneNode("head");
113
+ if (((G = e.meta) == null ? void 0 : G.metaVersion) === "0") {
114
+ const u = (J = e.humanoid) == null ? void 0 : J.getNormalizedBoneNode("leftUpperArm"), N = ($ = e.humanoid) == null ? void 0 : $.getNormalizedBoneNode("rightUpperArm");
115
+ u && (u.rotation.z = Math.PI / 2.6), N && (N.rotation.z = -Math.PI / 2.6);
116
+ }
117
+ const F = ((W = b.pointer) == null ? void 0 : W.x) ?? ((Z = b.mouse) == null ? void 0 : Z.x) ?? 0, D = ((q = b.pointer) == null ? void 0 : q.y) ?? ((Q = b.mouse) == null ? void 0 : Q.y) ?? 0, L = P ? 0 : v, y = F * 0.35 * L, Y = -D * 0.2 * L;
118
+ if (c && (c.rotation.y = a.MathUtils.lerp(c.rotation.y, y, 0.1), c.rotation.x = a.MathUtils.lerp(c.rotation.x, Y, 0.1)), n && (n.rotation.y = a.MathUtils.lerp(n.rotation.y, y * 0.2, 0.1), n.rotation.x = a.MathUtils.lerp(n.rotation.x, Y * 0.2, 0.1)), !P)
119
+ if (!j.current)
120
+ V.current -= M, V.current <= 0 && (j.current = !0);
121
+ else {
122
+ const u = 1e3 / (x || 100);
123
+ s.current += M * u * 2, s.current >= 1 && (s.current = 1, j.current = !1, V.current = Math.random() * ((A - o) / 1e3) + o / 1e3);
124
+ }
125
+ if (!j.current && s.current > 0) {
126
+ const u = 1e3 / (x || 100);
127
+ s.current -= M * u * 2, s.current < 0 && (s.current = 0);
128
+ }
129
+ if (e.expressionManager) {
130
+ f(e, "blink", s.current);
131
+ let u = 0, N = 0, C = 0;
132
+ if (r === "listening" ? (u = 0.35, N = 0.2) : r === "thinking" ? (C = 0.25, N = 0.15, c && (c.rotation.y = a.MathUtils.lerp(c.rotation.y, -0.18, 0.05), c.rotation.x = a.MathUtils.lerp(c.rotation.x, 0.12, 0.05))) : r === "speaking" ? u = 0.15 : u = 0.05, U.current = a.MathUtils.lerp(U.current, u, 0.1), w.current = a.MathUtils.lerp(w.current, N, 0.1), z.current = a.MathUtils.lerp(z.current, C, 0.1), f(e, "happy", U.current), f(e, "relaxed", w.current), f(e, "sad", z.current), r === "speaking") {
133
+ (!I.current || K.current !== h) && (I.current = fe(h), K.current = h);
134
+ const R = I.current.read(), _ = E / 30;
135
+ let ee = 0, te = 0, re = 0;
136
+ R.shape === "e" ? te = R.level * 0.8 * _ : R.shape === "o" ? re = R.level * 0.8 * _ : R.shape === "a" && (ee = R.level * 0.95 * _), g.current = a.MathUtils.lerp(g.current, ee, 0.25), p.current = a.MathUtils.lerp(p.current, te, 0.25), m.current = a.MathUtils.lerp(m.current, re, 0.25), f(e, "aa", g.current), f(e, "ee", p.current), f(e, "oo", m.current);
137
+ } else
138
+ g.current = a.MathUtils.lerp(g.current, 0, 0.2), p.current = a.MathUtils.lerp(p.current, 0, 0.2), m.current = a.MathUtils.lerp(m.current, 0, 0.2), f(e, "aa", g.current), f(e, "ee", p.current), f(e, "oo", m.current);
139
+ e.expressionManager.update();
140
+ }
141
+ }), e ? /* @__PURE__ */ i("primitive", { object: e.scene }) : null;
142
+ }
143
+ function Ne({
144
+ state: l,
145
+ analyser: r,
146
+ size: h = 300,
147
+ className: E = "",
148
+ style: v,
149
+ maxMouthOpening: o,
150
+ blinkIntervalMin: A,
151
+ blinkIntervalMax: x,
152
+ blinkDuration: P,
153
+ mouseTrackingIntensity: S,
154
+ stateColors: t,
155
+ vrmUrl: e
156
+ }) {
157
+ const [B, g] = H(!1), [p, m] = H(null), U = me();
158
+ ie(() => {
159
+ g(!1), m(null);
160
+ }, [e]);
161
+ const w = ne((s) => g(s), []), z = ne((s) => m(s), []), V = {
162
+ idle: (t == null ? void 0 : t.idle) ?? "#4b5563",
163
+ listening: (t == null ? void 0 : t.listening) ?? "#3b82f6",
164
+ thinking: (t == null ? void 0 : t.thinking) ?? "#8b5cf6",
165
+ speaking: (t == null ? void 0 : t.speaking) ?? "#10b981",
166
+ working: (t == null ? void 0 : t.working) ?? "#f59e0b"
167
+ };
168
+ return /* @__PURE__ */ O(
169
+ "div",
170
+ {
171
+ className: `relative flex items-center justify-center rounded-3xl overflow-hidden border border-zinc-800/40 bg-zinc-950/40 ${E}`,
172
+ style: { width: h, height: h, ...v },
173
+ children: [
174
+ /* @__PURE__ */ O(
175
+ ce,
176
+ {
177
+ camera: { position: [0, 1.43, 0.88], fov: 44 },
178
+ shadows: !0,
179
+ gl: { antialias: !0, alpha: !0, preserveDrawingBuffer: !0 },
180
+ style: { width: "100%", height: "100%", background: "transparent" },
181
+ children: [
182
+ /* @__PURE__ */ i("ambientLight", { intensity: 1.5 }),
183
+ /* @__PURE__ */ i(
184
+ "spotLight",
185
+ {
186
+ position: [0, 3, 2],
187
+ angle: 0.6,
188
+ penumbra: 1,
189
+ intensity: 3,
190
+ color: V[l],
191
+ castShadow: !0
192
+ }
193
+ ),
194
+ /* @__PURE__ */ i("directionalLight", { position: [-2, 2, -2], intensity: 1.8, color: "#ffffff" }),
195
+ /* @__PURE__ */ i("directionalLight", { position: [2, 2, 2], intensity: 2.2, color: "#ffffff" }),
196
+ /* @__PURE__ */ i(he, { onError: (s) => m(s), children: e && /* @__PURE__ */ i(se, { fallback: null, children: /* @__PURE__ */ i(
197
+ ge,
198
+ {
199
+ url: e,
200
+ state: l,
201
+ analyser: r,
202
+ maxMouthOpening: o,
203
+ mouseTrackingIntensity: S,
204
+ blinkIntervalMin: A,
205
+ blinkIntervalMax: x,
206
+ blinkDuration: P,
207
+ reducedMotion: U,
208
+ onLoaded: w,
209
+ onError: z
210
+ }
211
+ ) }) }),
212
+ /* @__PURE__ */ i(
213
+ pe,
214
+ {
215
+ enableZoom: !1,
216
+ enablePan: !1,
217
+ minPolarAngle: Math.PI / 2.6,
218
+ maxPolarAngle: Math.PI / 1.7,
219
+ minAzimuthAngle: -Math.PI / 4,
220
+ maxAzimuthAngle: Math.PI / 4,
221
+ target: [0, 1.38, 0]
222
+ }
223
+ )
224
+ ]
225
+ }
226
+ ),
227
+ !e && /* @__PURE__ */ O("div", { className: "absolute inset-0 flex flex-col items-center justify-center bg-zinc-950/90 backdrop-blur-md z-20 p-4 text-center", children: [
228
+ /* @__PURE__ */ i("span", { className: "text-xs font-mono font-bold text-amber-400 uppercase tracking-wider mb-2", children: "Missing vrmUrl" }),
229
+ /* @__PURE__ */ i("p", { className: "text-[10px] text-zinc-500 max-w-[200px] leading-relaxed", children: "Pass a CORS-enabled .vrm URL via the vrmUrl prop." })
230
+ ] }),
231
+ e && !B && !p && /* @__PURE__ */ O("div", { className: "absolute inset-0 flex flex-col items-center justify-center bg-zinc-950/80 backdrop-blur-md z-20", children: [
232
+ /* @__PURE__ */ i("div", { className: "w-10 h-10 border-4 border-t-emerald-500 border-emerald-500/20 rounded-full animate-spin mb-3" }),
233
+ /* @__PURE__ */ i("span", { className: "text-[10px] font-mono font-bold tracking-widest text-zinc-400 animate-pulse", children: "LOADING NEURAL VRM MODEL..." })
234
+ ] }),
235
+ p && /* @__PURE__ */ O("div", { className: "absolute inset-0 flex flex-col items-center justify-center bg-zinc-950/90 backdrop-blur-md z-20 p-4 text-center", children: [
236
+ /* @__PURE__ */ i("span", { className: "text-xs font-mono font-bold text-red-400 uppercase tracking-wider mb-2", children: "Failed to load VRM" }),
237
+ /* @__PURE__ */ i("p", { className: "text-[10px] text-zinc-500 max-w-[200px] leading-relaxed break-all", children: p })
238
+ ] })
239
+ ]
240
+ }
241
+ );
242
+ }
243
+ export {
244
+ Ne as VrmAvatar
245
+ };
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("react/jsx-runtime"),r=require("react"),re=require("@react-three/fiber"),ae=require("three/addons/loaders/GLTFLoader.js"),I=require("@pixiv/three-vrm"),se=require("@react-three/drei"),ie=require("three"),ne=require("./useReducedMotion-BRDEmRNI.cjs");function oe(s){const n=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(s){for(const c in s)if(c!=="default"){const x=Object.getOwnPropertyDescriptor(s,c);Object.defineProperty(n,c,x.get?x:{enumerable:!0,get:()=>s[c]})}}return n.default=s,Object.freeze(n)}const o=oe(ie);class ce extends r.Component{constructor(n){super(n),this.state={hasError:!1}}static getDerivedStateFromError(){return{hasError:!0}}componentDidCatch(n){this.props.onError((n==null?void 0:n.message)||String(n))}render(){return this.state.hasError?null:this.props.children}}function m(s,n,c){if(!s.expressionManager)return;const x=s.expressionManager.expressionMap||{},N=Object.keys(x);let l=[];switch(n){case"blink":l=["blink","Blink","BLINK"];break;case"aa":l=["aa","Aa","a","A","AA","mouth_a","mouth_A"];break;case"ee":l=["ee","Ee","e","E","EE","mouth_e","mouth_E"];break;case"oo":l=["oh","Oh","oo","Oo","o","O","OO","mouth_o","mouth_O"];break;case"happy":l=["happy","Happy","joy","Joy","JOY","fun","Fun","FUN","HAPPY"];break;case"sad":l=["sad","Sad","sorrow","Sorrow","SORROW","sadness","SAD"];break;case"relaxed":l=["relaxed","Relaxed","fun","Fun","FUN","neutral","Neutral","RELAXED"];break}const j=l.find(b=>x[b]!==void 0||N.includes(b));if(j)s.expressionManager.setValue(j,c);else try{s.expressionManager.setValue(l[0],c)}catch{}}function le({url:s,state:n,analyser:c,maxMouthOpening:x=30,mouseTrackingIntensity:N=1,blinkIntervalMin:l=2e3,blinkIntervalMax:j=6e3,blinkDuration:b=100,reducedMotion:z=!1,onLoaded:L,onError:a}){const[e,D]=r.useState(null);r.useEffect(()=>{let M=!1,y=null;const d=new ae.GLTFLoader;return d.register(i=>new I.VRMLoaderPlugin(i)),d.load(s,i=>{var S,w;if(M){const k=i.userData.vrm;k&&I.VRMUtils.deepDispose(k.scene);return}const R=i.userData.vrm;if(!R){a(`No VRM found at ${s}.`);return}console.log("[VRM] Model loaded successfully. Available expressions:",Object.keys(((S=R.expressionManager)==null?void 0:S.expressionMap)||{})),R.scene.traverse(k=>{k.frustumCulled=!1});const B=((w=R.meta)==null?void 0:w.metaVersion)==="0";R.scene.rotation.y=B?Math.PI:0,y=R,D(R),L(!0)},void 0,i=>{M||a((i==null?void 0:i.message)||String(i))}),()=>{M=!0,y&&I.VRMUtils.deepDispose(y.scene)}},[s,L,a]);const g=r.useRef(0),p=r.useRef(0),h=r.useRef(0),v=r.useRef(0),O=r.useRef(0),U=r.useRef(0),V=r.useRef(Math.random()*3+2),u=r.useRef(0),P=r.useRef(!1),_=r.useRef(null),q=r.useRef(null);return re.useFrame((M,y)=>{var H,K,Y,G,X,J,$,W,Z;if(!e)return;e.update(y);const d=(H=e.humanoid)==null?void 0:H.getNormalizedBoneNode("neck"),i=(K=e.humanoid)==null?void 0:K.getNormalizedBoneNode("head");if(((Y=e.meta)==null?void 0:Y.metaVersion)==="0"){const f=(G=e.humanoid)==null?void 0:G.getNormalizedBoneNode("leftUpperArm"),E=(X=e.humanoid)==null?void 0:X.getNormalizedBoneNode("rightUpperArm");f&&(f.rotation.z=Math.PI/2.6),E&&(E.rotation.z=-Math.PI/2.6)}const B=((J=M.pointer)==null?void 0:J.x)??(($=M.mouse)==null?void 0:$.x)??0,S=((W=M.pointer)==null?void 0:W.y)??((Z=M.mouse)==null?void 0:Z.y)??0,w=z?0:N,k=B*.35*w,T=-S*.2*w;if(d&&(d.rotation.y=o.MathUtils.lerp(d.rotation.y,k,.1),d.rotation.x=o.MathUtils.lerp(d.rotation.x,T,.1)),i&&(i.rotation.y=o.MathUtils.lerp(i.rotation.y,k*.2,.1),i.rotation.x=o.MathUtils.lerp(i.rotation.x,T*.2,.1)),!z)if(!P.current)V.current-=y,V.current<=0&&(P.current=!0);else{const f=1e3/(b||100);u.current+=y*f*2,u.current>=1&&(u.current=1,P.current=!1,V.current=Math.random()*((j-l)/1e3)+l/1e3)}if(!P.current&&u.current>0){const f=1e3/(b||100);u.current-=y*f*2,u.current<0&&(u.current=0)}if(e.expressionManager){m(e,"blink",u.current);let f=0,E=0,Q=0;if(n==="listening"?(f=.35,E=.2):n==="thinking"?(Q=.25,E=.15,d&&(d.rotation.y=o.MathUtils.lerp(d.rotation.y,-.18,.05),d.rotation.x=o.MathUtils.lerp(d.rotation.x,.12,.05))):n==="speaking"?f=.15:f=.05,v.current=o.MathUtils.lerp(v.current,f,.1),O.current=o.MathUtils.lerp(O.current,E,.1),U.current=o.MathUtils.lerp(U.current,Q,.1),m(e,"happy",v.current),m(e,"relaxed",O.current),m(e,"sad",U.current),n==="speaking"){(!_.current||q.current!==c)&&(_.current=ne.createMouthEngine(c),q.current=c);const A=_.current.read(),F=x/30;let C=0,ee=0,te=0;A.shape==="e"?ee=A.level*.8*F:A.shape==="o"?te=A.level*.8*F:A.shape==="a"&&(C=A.level*.95*F),g.current=o.MathUtils.lerp(g.current,C,.25),p.current=o.MathUtils.lerp(p.current,ee,.25),h.current=o.MathUtils.lerp(h.current,te,.25),m(e,"aa",g.current),m(e,"ee",p.current),m(e,"oo",h.current)}else g.current=o.MathUtils.lerp(g.current,0,.2),p.current=o.MathUtils.lerp(p.current,0,.2),h.current=o.MathUtils.lerp(h.current,0,.2),m(e,"aa",g.current),m(e,"ee",p.current),m(e,"oo",h.current);e.expressionManager.update()}}),e?t.jsx("primitive",{object:e.scene}):null}function ue({state:s,analyser:n,size:c=300,className:x="",style:N,maxMouthOpening:l,blinkIntervalMin:j,blinkIntervalMax:b,blinkDuration:z,mouseTrackingIntensity:L,stateColors:a,vrmUrl:e}){const[D,g]=r.useState(!1),[p,h]=r.useState(null),v=ne.useReducedMotion();r.useEffect(()=>{g(!1),h(null)},[e]);const O=r.useCallback(u=>g(u),[]),U=r.useCallback(u=>h(u),[]),V={idle:(a==null?void 0:a.idle)??"#4b5563",listening:(a==null?void 0:a.listening)??"#3b82f6",thinking:(a==null?void 0:a.thinking)??"#8b5cf6",speaking:(a==null?void 0:a.speaking)??"#10b981",working:(a==null?void 0:a.working)??"#f59e0b"};return t.jsxs("div",{className:`relative flex items-center justify-center rounded-3xl overflow-hidden border border-zinc-800/40 bg-zinc-950/40 ${x}`,style:{width:c,height:c,...N},children:[t.jsxs(re.Canvas,{camera:{position:[0,1.43,.88],fov:44},shadows:!0,gl:{antialias:!0,alpha:!0,preserveDrawingBuffer:!0},style:{width:"100%",height:"100%",background:"transparent"},children:[t.jsx("ambientLight",{intensity:1.5}),t.jsx("spotLight",{position:[0,3,2],angle:.6,penumbra:1,intensity:3,color:V[s],castShadow:!0}),t.jsx("directionalLight",{position:[-2,2,-2],intensity:1.8,color:"#ffffff"}),t.jsx("directionalLight",{position:[2,2,2],intensity:2.2,color:"#ffffff"}),t.jsx(ce,{onError:u=>h(u),children:e&&t.jsx(r.Suspense,{fallback:null,children:t.jsx(le,{url:e,state:s,analyser:n,maxMouthOpening:l,mouseTrackingIntensity:L,blinkIntervalMin:j,blinkIntervalMax:b,blinkDuration:z,reducedMotion:v,onLoaded:O,onError:U})})}),t.jsx(se.OrbitControls,{enableZoom:!1,enablePan:!1,minPolarAngle:Math.PI/2.6,maxPolarAngle:Math.PI/1.7,minAzimuthAngle:-Math.PI/4,maxAzimuthAngle:Math.PI/4,target:[0,1.38,0]})]}),!e&&t.jsxs("div",{className:"absolute inset-0 flex flex-col items-center justify-center bg-zinc-950/90 backdrop-blur-md z-20 p-4 text-center",children:[t.jsx("span",{className:"text-xs font-mono font-bold text-amber-400 uppercase tracking-wider mb-2",children:"Missing vrmUrl"}),t.jsx("p",{className:"text-[10px] text-zinc-500 max-w-[200px] leading-relaxed",children:"Pass a CORS-enabled .vrm URL via the vrmUrl prop."})]}),e&&!D&&!p&&t.jsxs("div",{className:"absolute inset-0 flex flex-col items-center justify-center bg-zinc-950/80 backdrop-blur-md z-20",children:[t.jsx("div",{className:"w-10 h-10 border-4 border-t-emerald-500 border-emerald-500/20 rounded-full animate-spin mb-3"}),t.jsx("span",{className:"text-[10px] font-mono font-bold tracking-widest text-zinc-400 animate-pulse",children:"LOADING NEURAL VRM MODEL..."})]}),p&&t.jsxs("div",{className:"absolute inset-0 flex flex-col items-center justify-center bg-zinc-950/90 backdrop-blur-md z-20 p-4 text-center",children:[t.jsx("span",{className:"text-xs font-mono font-bold text-red-400 uppercase tracking-wider mb-2",children:"Failed to load VRM"}),t.jsx("p",{className:"text-[10px] text-zinc-500 max-w-[200px] leading-relaxed break-all",children:p})]})]})}exports.VrmAvatar=ue;