react-ai-avatar 0.1.0
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/LICENSE +21 -0
- package/README.md +310 -0
- package/dist/lib/GlbArkitAvatar-CcPWCsQV.cjs +1 -0
- package/dist/lib/GlbArkitAvatar-Dm9STiyR.js +232 -0
- package/dist/lib/VrmAvatar-CehRzj0J.js +224 -0
- package/dist/lib/VrmAvatar-D_jr2TOG.cjs +1 -0
- package/dist/lib/components/AudioVisualizer.d.ts +17 -0
- package/dist/lib/components/ContractAvatar.d.ts +25 -0
- package/dist/lib/components/DefaultAvatar.d.ts +37 -0
- package/dist/lib/components/DiceBearAvatar.d.ts +48 -0
- package/dist/lib/components/DiceBearThumb.d.ts +15 -0
- package/dist/lib/components/DoodleAvatar.d.ts +21 -0
- package/dist/lib/components/GeometricAvatar.d.ts +22 -0
- package/dist/lib/components/GlbArkitAvatar.d.ts +7 -0
- package/dist/lib/components/MemojiAvatar.d.ts +19 -0
- package/dist/lib/components/PixelArtAvatar.d.ts +23 -0
- package/dist/lib/components/RealtimeAvatar.d.ts +74 -0
- package/dist/lib/components/SquirrelAvatar.d.ts +29 -0
- package/dist/lib/components/VrmAvatar.d.ts +6 -0
- package/dist/lib/index.cjs +6 -0
- package/dist/lib/index.js +1231 -0
- package/dist/lib/lib/color.d.ts +6 -0
- package/dist/lib/lib/dicebear.d.ts +110 -0
- package/dist/lib/lib/index.d.ts +34 -0
- package/dist/lib/lib/mouthEngine.d.ts +37 -0
- package/dist/lib/lib/speechActivity.d.ts +51 -0
- package/dist/lib/lib/types.d.ts +22 -0
- package/dist/lib/lib/useAudioMouth.d.ts +20 -0
- package/dist/lib/lib/useAvatarRuntime.d.ts +39 -0
- package/dist/lib/lib/useReducedMotion.d.ts +5 -0
- package/dist/lib/lib/useStreamingTextActivity.d.ts +46 -0
- package/dist/lib/lib/vrm.d.ts +9 -0
- package/dist/lib/react-ai-avatar.css +1 -0
- package/dist/lib/useReducedMotion-BDcEizfP.js +104 -0
- package/dist/lib/useReducedMotion-BRDEmRNI.cjs +1 -0
- package/dist/lib/vrm.cjs +1 -0
- package/dist/lib/vrm.js +4 -0
- package/package.json +127 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert a hex color (`#rgb` or `#rrggbb`) to an `rgba()` string at the given
|
|
3
|
+
* opacity. Non-hex input is returned as-is (or `transparent` when empty), so it
|
|
4
|
+
* is safe to pass already-resolved CSS colors through it.
|
|
5
|
+
*/
|
|
6
|
+
export declare function hexToRgba(color: string, opacity: number): string;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DiceBear catalog — curated, CC0-only styles.
|
|
3
|
+
*
|
|
4
|
+
* DiceBear (https://www.dicebear.com) ships ~30 avatar styles under mixed
|
|
5
|
+
* licenses. To keep this library's "no attribution headaches" promise, we
|
|
6
|
+
* expose ONLY the styles licensed CC0 1.0 (public domain). Styles such as
|
|
7
|
+
* `bottts` ("free for personal and commercial use") or the many CC BY 4.0
|
|
8
|
+
* ones are intentionally left out — a host app can still use them via the
|
|
9
|
+
* raw `<DiceBearAvatar collection={...} />` escape hatch at its own
|
|
10
|
+
* licensing discretion, the same way `byos` SVGs keep their own license.
|
|
11
|
+
*
|
|
12
|
+
* Generation is done client-side with the `@dicebear/core` +
|
|
13
|
+
* `@dicebear/collection` packages (optional peer deps, lazy-loaded). No
|
|
14
|
+
* network call, deterministic per `seed`, works offline.
|
|
15
|
+
*/
|
|
16
|
+
/** The curated set of CC0 1.0 DiceBear style ids (kebab-case = DiceBear id). */
|
|
17
|
+
export type DiceBearCollection = 'pixel-art' | 'pixel-art-neutral' | 'lorelei' | 'lorelei-neutral' | 'notionists' | 'notionists-neutral' | 'open-peeps' | 'thumbs';
|
|
18
|
+
export interface DiceBearStyleMeta {
|
|
19
|
+
/** Kebab-case id — both the DiceBear style id and the UI/value key. */
|
|
20
|
+
id: DiceBearCollection;
|
|
21
|
+
/** camelCase named export inside `@dicebear/collection`. */
|
|
22
|
+
exportName: string;
|
|
23
|
+
/** Human label for catalog UIs. */
|
|
24
|
+
label: string;
|
|
25
|
+
/** Always 'CC0 1.0' here — that's the whole point of the curation. */
|
|
26
|
+
license: 'CC0 1.0';
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* kebab-case style id -> camelCase `@dicebear/collection` export name.
|
|
30
|
+
* `pixel-art-neutral` -> `pixelArtNeutral`, `open-peeps` -> `openPeeps`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function collectionExportName(id: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* Curated CC0 catalog, in display order. All entries have a face, so all
|
|
35
|
+
* articulate via viseme swapping (see DICEBEAR_RIGS). The abstract CC0 styles
|
|
36
|
+
* (shapes, rings, glass, identicon) are intentionally excluded — they have no
|
|
37
|
+
* mouth to drive.
|
|
38
|
+
*/
|
|
39
|
+
export declare const DICEBEAR_STYLES: readonly DiceBearStyleMeta[];
|
|
40
|
+
/**
|
|
41
|
+
* Prefix every internal id in a DiceBear SVG (and every reference to it) so
|
|
42
|
+
* multiple SVGs from the same style can coexist in one document without their
|
|
43
|
+
* `clipPath` / `mask` / gradient ids colliding — which would otherwise blank
|
|
44
|
+
* out all but the first. Needed because we stack several frames of the same
|
|
45
|
+
* avatar (closed/open mouth, blink) on top of each other.
|
|
46
|
+
*/
|
|
47
|
+
export declare function scopeSvgIds(svg: string, prefix: string): string;
|
|
48
|
+
/**
|
|
49
|
+
* Per-style "rig": how to articulate a given DiceBear style for talking.
|
|
50
|
+
*
|
|
51
|
+
* DiceBear SVGs are pre-baked with no `#rra-*` hooks, but the official option
|
|
52
|
+
* API lets us *pick* which mouth/lips/face/eyes variant to render. So instead
|
|
53
|
+
* of the bounce-only fallback we pre-generate a few frames of the SAME avatar
|
|
54
|
+
* (same seed → identical everything else) with different mouth/eye variants and
|
|
55
|
+
* swap between them per audio frame — real articulation via the supported API.
|
|
56
|
+
*
|
|
57
|
+
* Only styles that actually expose suitable variants are rigged; the rest
|
|
58
|
+
* (abstract styles with no face) fall back to the audio-reactive bounce. The
|
|
59
|
+
* variant ids below were chosen by eye from each style's variant set.
|
|
60
|
+
*/
|
|
61
|
+
export interface DiceBearRig {
|
|
62
|
+
/** Option key that drives the mouth shape. */
|
|
63
|
+
part: 'mouth' | 'lips' | 'face';
|
|
64
|
+
/** Three variant names ordered [closed, mid, open]. */
|
|
65
|
+
visemes: [string, string, string];
|
|
66
|
+
/** Independent eye blink (for `mouth`/`lips` styles where eyes are separate). */
|
|
67
|
+
blink?: {
|
|
68
|
+
open: string;
|
|
69
|
+
closed: string;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* For `face` styles, mouth + eyes are a single coupled value, so a blink is a
|
|
73
|
+
* whole-face variant that briefly replaces the current viseme.
|
|
74
|
+
*/
|
|
75
|
+
faceBlink?: string;
|
|
76
|
+
}
|
|
77
|
+
export declare const DICEBEAR_RIGS: Partial<Record<DiceBearCollection, DiceBearRig>>;
|
|
78
|
+
/**
|
|
79
|
+
* Curated "featured faces": hand-picked {style, seed} pairs that look good out
|
|
80
|
+
* of the box, so a host (or the demo picker) can offer a gallery instead of
|
|
81
|
+
* asking users to guess a seed string. Seeds are deterministic — these exact
|
|
82
|
+
* faces render identically everywhere, offline, no network call.
|
|
83
|
+
*/
|
|
84
|
+
export interface DiceBearFeaturedFace {
|
|
85
|
+
/** Curated CC0 style id. */
|
|
86
|
+
collection: DiceBearCollection;
|
|
87
|
+
/** Deterministic seed picked for a nice-looking result. */
|
|
88
|
+
seed: string;
|
|
89
|
+
}
|
|
90
|
+
export declare const DICEBEAR_FEATURED_FACES: readonly DiceBearFeaturedFace[];
|
|
91
|
+
/** Default face when none is provided — the first featured face. */
|
|
92
|
+
export declare const DEFAULT_DICEBEAR_COLLECTION: DiceBearCollection;
|
|
93
|
+
export declare const DEFAULT_DICEBEAR_SEED: string;
|
|
94
|
+
/** O(1) lookup by id. */
|
|
95
|
+
export declare const DICEBEAR_STYLE_BY_ID: Record<DiceBearCollection, DiceBearStyleMeta>;
|
|
96
|
+
type CreateAvatar = (style: unknown, options: Record<string, unknown>) => {
|
|
97
|
+
toString(): string;
|
|
98
|
+
};
|
|
99
|
+
export type DiceBearModules = {
|
|
100
|
+
createAvatar: CreateAvatar;
|
|
101
|
+
collection: Record<string, unknown>;
|
|
102
|
+
};
|
|
103
|
+
export declare function loadDiceBear(): Promise<DiceBearModules>;
|
|
104
|
+
/**
|
|
105
|
+
* Render a single static DiceBear SVG string for a given style + seed. Used for
|
|
106
|
+
* non-animated thumbnails (e.g. a face-picker gallery). Throws if the style id
|
|
107
|
+
* is unknown.
|
|
108
|
+
*/
|
|
109
|
+
export declare function renderDiceBearSvg(collection: DiceBearCollection | string, seed: string, options?: Record<string, unknown>): Promise<string>;
|
|
110
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import './lib.css';
|
|
2
|
+
export { RealtimeAvatar } from '../components/RealtimeAvatar';
|
|
3
|
+
export type { RealtimeAvatarProps } from '../components/RealtimeAvatar';
|
|
4
|
+
export { ContractAvatar } from '../components/ContractAvatar';
|
|
5
|
+
export type { ContractAvatarProps } from '../components/ContractAvatar';
|
|
6
|
+
export { GeometricAvatar } from '../components/GeometricAvatar';
|
|
7
|
+
export type { GeometricAvatarProps } from '../components/GeometricAvatar';
|
|
8
|
+
export { MemojiAvatar } from '../components/MemojiAvatar';
|
|
9
|
+
export type { MemojiAvatarProps } from '../components/MemojiAvatar';
|
|
10
|
+
export { PixelArtAvatar } from '../components/PixelArtAvatar';
|
|
11
|
+
export type { PixelArtAvatarProps } from '../components/PixelArtAvatar';
|
|
12
|
+
export { DoodleAvatar } from '../components/DoodleAvatar';
|
|
13
|
+
export type { DoodleAvatarProps } from '../components/DoodleAvatar';
|
|
14
|
+
export { SquirrelAvatar } from '../components/SquirrelAvatar';
|
|
15
|
+
export type { SquirrelAvatarProps } from '../components/SquirrelAvatar';
|
|
16
|
+
export { DiceBearAvatar } from '../components/DiceBearAvatar';
|
|
17
|
+
export type { DiceBearAvatarProps } from '../components/DiceBearAvatar';
|
|
18
|
+
export { DiceBearThumb } from '../components/DiceBearThumb';
|
|
19
|
+
export type { DiceBearThumbProps } from '../components/DiceBearThumb';
|
|
20
|
+
export { DICEBEAR_STYLES, DICEBEAR_STYLE_BY_ID, DICEBEAR_RIGS, DICEBEAR_FEATURED_FACES, DEFAULT_DICEBEAR_COLLECTION, DEFAULT_DICEBEAR_SEED, collectionExportName, scopeSvgIds, loadDiceBear, renderDiceBearSvg, } from './dicebear';
|
|
21
|
+
export type { DiceBearCollection, DiceBearStyleMeta, DiceBearRig, DiceBearFeaturedFace, DiceBearModules, } from './dicebear';
|
|
22
|
+
export { AudioVisualizer } from '../components/AudioVisualizer';
|
|
23
|
+
export type { AudioVisualizerProps } from '../components/AudioVisualizer';
|
|
24
|
+
export { useAvatarRuntime } from './useAvatarRuntime';
|
|
25
|
+
export type { AvatarRuntimeOptions } from './useAvatarRuntime';
|
|
26
|
+
export { useAudioMouth } from './useAudioMouth';
|
|
27
|
+
export { createMouthEngine } from './mouthEngine';
|
|
28
|
+
export type { MouthEngine, MouthFrame, MouthShape, MouthSource } from './mouthEngine';
|
|
29
|
+
export { createSpeechActivity, isSpeechActivity, SPEECH_ACTIVITY_BRAND } from './speechActivity';
|
|
30
|
+
export type { SpeechActivitySource, SpeechActivityOptions } from './speechActivity';
|
|
31
|
+
export { useStreamingTextActivity } from './useStreamingTextActivity';
|
|
32
|
+
export { useReducedMotion } from './useReducedMotion';
|
|
33
|
+
export type { AvatarState, StateColors, StateLabels } from './types';
|
|
34
|
+
export type { AvatarCustomization, AvatarProps } from '../components/DefaultAvatar';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared mouth-animation engine.
|
|
3
|
+
*
|
|
4
|
+
* One implementation for every avatar variant (SVG presets, VRM, byos):
|
|
5
|
+
* given an AnalyserNode it produces a per-frame amplitude level plus a
|
|
6
|
+
* coarse mouth shape (A/E/O) derived from frequency-band ratios. Given
|
|
7
|
+
* `null` it degrades gracefully to a synthetic, speech-like procedural
|
|
8
|
+
* pattern — the mouth never freezes shut while "speaking".
|
|
9
|
+
*
|
|
10
|
+
* This is intentionally amplitude-based ("audio-reactive mouth"), not
|
|
11
|
+
* phoneme-accurate lip-sync: an AnalyserNode gives energy, not phonemes.
|
|
12
|
+
*/
|
|
13
|
+
import { SpeechActivitySource } from './speechActivity';
|
|
14
|
+
export type MouthShape = 'a' | 'e' | 'o' | 'closed';
|
|
15
|
+
/**
|
|
16
|
+
* Anything that can drive the mouth: a WebAudio AnalyserNode (audio), a
|
|
17
|
+
* token-rate SpeechActivitySource (text streams), or `null` (procedural
|
|
18
|
+
* fallback). Components thread this through unchanged — the engine picks
|
|
19
|
+
* the right driver.
|
|
20
|
+
*/
|
|
21
|
+
export type MouthSource = AnalyserNode | SpeechActivitySource | null;
|
|
22
|
+
export interface MouthFrame {
|
|
23
|
+
/** Normalized mouth-opening level, 0 (closed) to 1 (max). */
|
|
24
|
+
level: number;
|
|
25
|
+
/** Coarse mouth shape suggestion for the current frame. */
|
|
26
|
+
shape: MouthShape;
|
|
27
|
+
}
|
|
28
|
+
export interface MouthEngine {
|
|
29
|
+
/** Read the next frame. Call once per animation frame. */
|
|
30
|
+
read(): MouthFrame;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Create a mouth engine for the given source: a WebAudio AnalyserNode
|
|
34
|
+
* (audio-reactive), a token-rate SpeechActivitySource (text streams), or
|
|
35
|
+
* `null` for the procedural (synthetic) fallback engine.
|
|
36
|
+
*/
|
|
37
|
+
export declare function createMouthEngine(source: MouthSource): MouthEngine;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token-rate mouth driver.
|
|
3
|
+
*
|
|
4
|
+
* A text-streaming LLM (OpenAI-style `/chat/completions` or `/responses`
|
|
5
|
+
* with `stream: true`) returns no audio — only tokens arriving over time.
|
|
6
|
+
* This source turns that *cadence* into the same 0..1 energy signal an
|
|
7
|
+
* AnalyserNode would give the mouth engine: the host feeds streamed text
|
|
8
|
+
* chunks via `push()`, energy rises with how fast tokens arrive and decays
|
|
9
|
+
* during pauses. The result is a mouth that visibly tracks the stream
|
|
10
|
+
* (busy while the model is producing, settling when it stalls or ends).
|
|
11
|
+
*
|
|
12
|
+
* The library stays presentational and provider-agnostic: it never fetches
|
|
13
|
+
* anything. You bring the stream; this turns it into a face.
|
|
14
|
+
*/
|
|
15
|
+
/** Brand so the mouth engine can tell a SpeechActivitySource from an AnalyserNode. */
|
|
16
|
+
export declare const SPEECH_ACTIVITY_BRAND: "__rraSpeechActivity";
|
|
17
|
+
export interface SpeechActivitySource {
|
|
18
|
+
readonly [SPEECH_ACTIVITY_BRAND]: true;
|
|
19
|
+
/** Feed a streamed chunk of model text (one or more tokens). */
|
|
20
|
+
push(textChunk: string): void;
|
|
21
|
+
/** Mark the stream finished; energy decays to closed on its own. */
|
|
22
|
+
end(): void;
|
|
23
|
+
/** Drop all energy immediately (e.g. an interrupted turn). */
|
|
24
|
+
reset(): void;
|
|
25
|
+
/** Current decayed energy, 0 (closed) to 1 (wide). Read once per frame. */
|
|
26
|
+
sample(): number;
|
|
27
|
+
}
|
|
28
|
+
export interface SpeechActivityOptions {
|
|
29
|
+
/**
|
|
30
|
+
* Energy added per character pushed. Higher = the mouth opens wider for
|
|
31
|
+
* the same amount of text. Default tuned for typical token sizes.
|
|
32
|
+
*/
|
|
33
|
+
chargePerChar?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Decay time constant in ms: energy falls to ~37% of its value after this
|
|
36
|
+
* long without new tokens. Smaller = the mouth closes faster on pauses.
|
|
37
|
+
*/
|
|
38
|
+
decayMs?: number;
|
|
39
|
+
/** Max energy a single `push()` can contribute, so a big chunk can't peg the mouth. */
|
|
40
|
+
maxChargePerPush?: number;
|
|
41
|
+
/** Clock source, injectable for tests. Defaults to performance.now / Date.now. */
|
|
42
|
+
now?: () => number;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Create a token-rate activity source. Pass the returned object as the
|
|
46
|
+
* `speechActivity` prop of `<RealtimeAvatar />` and call `push()` with each
|
|
47
|
+
* streamed chunk of model text.
|
|
48
|
+
*/
|
|
49
|
+
export declare function createSpeechActivity(options?: SpeechActivityOptions): SpeechActivitySource;
|
|
50
|
+
/** Type guard: is this mouth source a token-rate SpeechActivitySource? */
|
|
51
|
+
export declare function isSpeechActivity(source: AnalyserNode | SpeechActivitySource | null | undefined): source is SpeechActivitySource;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core public types for react-ai-avatar.
|
|
3
|
+
*
|
|
4
|
+
* The library is purely presentational: the host app resolves the
|
|
5
|
+
* conversation state and (optionally) provides a WebAudio AnalyserNode;
|
|
6
|
+
* the library renders the face.
|
|
7
|
+
*/
|
|
8
|
+
export type AvatarState = 'idle' | 'listening' | 'thinking' | 'speaking' | 'working';
|
|
9
|
+
export interface StateColors {
|
|
10
|
+
idle?: string;
|
|
11
|
+
listening?: string;
|
|
12
|
+
thinking?: string;
|
|
13
|
+
speaking?: string;
|
|
14
|
+
working?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface StateLabels {
|
|
17
|
+
idle?: string;
|
|
18
|
+
listening?: string;
|
|
19
|
+
thinking?: string;
|
|
20
|
+
speaking?: string;
|
|
21
|
+
working?: string;
|
|
22
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { MouthFrame, MouthSource } from './mouthEngine';
|
|
2
|
+
export interface UseAudioMouthOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Mouth source: a WebAudio AnalyserNode (audio), a token-rate
|
|
5
|
+
* SpeechActivitySource (text streams), or `null` (procedural fallback).
|
|
6
|
+
*/
|
|
7
|
+
analyser: MouthSource;
|
|
8
|
+
/** Run the loop only while true (typically `state === 'speaking'`). */
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
/** Called once per animation frame with the current mouth frame. */
|
|
11
|
+
onFrame: (frame: MouthFrame) => void;
|
|
12
|
+
/** Called when the loop stops (close the mouth here). */
|
|
13
|
+
onStop?: () => void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Drives a mouth animation from audio (or the procedural fallback) via
|
|
17
|
+
* requestAnimationFrame. Components map the emitted MouthFrame onto
|
|
18
|
+
* whatever they animate: an SVG path, an ellipse `ry`, VRM visemes, etc.
|
|
19
|
+
*/
|
|
20
|
+
export declare function useAudioMouth({ analyser, enabled, onFrame, onStop }: UseAudioMouthOptions): void;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { RefObject } from 'react';
|
|
2
|
+
import { AvatarState, StateColors } from './types';
|
|
3
|
+
import { MouthSource } from './mouthEngine';
|
|
4
|
+
/**
|
|
5
|
+
* The layer contract: any SVG that exposes these stable hooks can be
|
|
6
|
+
* animated by this runtime — both the built-in presets and byos
|
|
7
|
+
* (bring-your-own-SVG) avatars.
|
|
8
|
+
*
|
|
9
|
+
* | hook | part | the runtime drives |
|
|
10
|
+
* |-------------------|-----------------|------------------------------------------|
|
|
11
|
+
* | `#rra-ring` | state ring | `stroke` = stateColors[state] |
|
|
12
|
+
* | `#rra-mouth` | mouth | ellipse: `ry`/`rx`; rect: `height` |
|
|
13
|
+
* | `.rra-pupil` (x2) | pupils | circle: `cx`/`cy`; rect: `x`/`y` |
|
|
14
|
+
* | `.rra-lid` (x2) | eyelids | `height` = blink (0 = open) |
|
|
15
|
+
* | `#rra-think` | thought bubble | `opacity` + pulsing dots (thinking) |
|
|
16
|
+
*
|
|
17
|
+
* Optional data attributes on the SVG refine behavior:
|
|
18
|
+
* - `data-base-x` / `data-base-y` on `.rra-pupil`: rest position.
|
|
19
|
+
* - `data-max-height` on `.rra-lid`: fully-closed lid height (default 16).
|
|
20
|
+
* - `data-quantize` on `#rra-mouth` / `.rra-pupil`: snap movement to a grid
|
|
21
|
+
* (e.g. `1` for pixel-art presets so motion happens in whole pixels).
|
|
22
|
+
*
|
|
23
|
+
* Honors `prefers-reduced-motion`: blink, gaze tracking and bubble pulsing
|
|
24
|
+
* are disabled; the informative audio-reactive mouth stays on.
|
|
25
|
+
*/
|
|
26
|
+
export interface AvatarRuntimeOptions {
|
|
27
|
+
state: AvatarState;
|
|
28
|
+
/** Mouth source: AnalyserNode (audio), SpeechActivitySource (text), or null. */
|
|
29
|
+
analyser: MouthSource;
|
|
30
|
+
stateColors?: StateColors;
|
|
31
|
+
/** Scales mouth opening; ~30 ≈ ellipse ry growing ~12 viewBox units. */
|
|
32
|
+
maxMouthOpening?: number;
|
|
33
|
+
/** 0 disables gaze tracking, 1 is default, up to 2. */
|
|
34
|
+
mouseTrackingIntensity?: number;
|
|
35
|
+
blinkIntervalMin?: number;
|
|
36
|
+
blinkIntervalMax?: number;
|
|
37
|
+
blinkDuration?: number;
|
|
38
|
+
}
|
|
39
|
+
export declare function useAvatarRuntime(containerRef: RefObject<HTMLElement | null>, options: AvatarRuntimeOptions): void;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { SpeechActivitySource } from './speechActivity';
|
|
2
|
+
/**
|
|
3
|
+
* Pure diff between the previously-seen accumulated text and the latest one.
|
|
4
|
+
* Streaming hooks (e.g. the Vercel AI SDK's `useChat`) expose the *growing*
|
|
5
|
+
* assistant message, not raw chunks — so we infer what's new:
|
|
6
|
+
*
|
|
7
|
+
* - `push` — `next` continues `prev` (the common case mid-stream): the new
|
|
8
|
+
* suffix is the token cadence to feed the mouth.
|
|
9
|
+
* - `reset` — `next` replaced or shrank `prev` (a new turn, or the field was
|
|
10
|
+
* cleared): drop energy and re-seed with `next` (empty seed = just clear).
|
|
11
|
+
* - `none` — unchanged.
|
|
12
|
+
*
|
|
13
|
+
* Exported for unit tests; the hook below is a thin stateful wrapper.
|
|
14
|
+
*/
|
|
15
|
+
export type StreamingTextAction = {
|
|
16
|
+
type: 'push';
|
|
17
|
+
text: string;
|
|
18
|
+
} | {
|
|
19
|
+
type: 'reset';
|
|
20
|
+
seed: string;
|
|
21
|
+
} | {
|
|
22
|
+
type: 'none';
|
|
23
|
+
};
|
|
24
|
+
export declare function diffStreamingText(prev: string, next: string): StreamingTextAction;
|
|
25
|
+
/**
|
|
26
|
+
* Declarative bridge for the dominant chat pattern: hooks like `useChat` hand
|
|
27
|
+
* you the *accumulated* assistant text (it grows every render), not stream
|
|
28
|
+
* chunks. This hook diffs that growth and feeds the new suffix into an internal
|
|
29
|
+
* `SpeechActivitySource`, so the mouth tracks the stream without the host ever
|
|
30
|
+
* touching `createSpeechActivity` or a reader loop.
|
|
31
|
+
*
|
|
32
|
+
* Pass `<RealtimeAvatar streamingText={lastAssistantText} />` and you're done:
|
|
33
|
+
*
|
|
34
|
+
* const { messages, status } = useChat();
|
|
35
|
+
* const last = messages.at(-1);
|
|
36
|
+
* <RealtimeAvatar
|
|
37
|
+
* state={status === 'submitted' ? 'thinking'
|
|
38
|
+
* : status === 'streaming' ? 'speaking' : 'idle'}
|
|
39
|
+
* streamingText={last?.role === 'assistant' ? last.text : ''}
|
|
40
|
+
* />
|
|
41
|
+
*
|
|
42
|
+
* Returns `null` when `text` is `undefined` (the prop wasn't used), so the
|
|
43
|
+
* caller can fall through to the `analyser` mouth driver. The low-level
|
|
44
|
+
* `createSpeechActivity` push() API stays available for imperative streams.
|
|
45
|
+
*/
|
|
46
|
+
export declare function useStreamingTextActivity(text: string | undefined): SpeechActivitySource | null;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional VRM entry point: `react-ai-avatar/vrm`.
|
|
3
|
+
*
|
|
4
|
+
* Importing this module pulls in three.js, @react-three/fiber, @react-three/drei
|
|
5
|
+
* and @pixiv/three-vrm (optional peer dependencies). The main entry never does:
|
|
6
|
+
* `<RealtimeAvatar variant="vrm" />` loads this chunk lazily on demand.
|
|
7
|
+
*/
|
|
8
|
+
export { VrmAvatar } from '../components/VrmAvatar';
|
|
9
|
+
export type { VrmAvatarProps } from '../components/VrmAvatar';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial}}}@layer theme{:root,:host{--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-400:oklch(70.4% .191 22.216);--color-amber-400:oklch(82.8% .189 84.429);--color-emerald-500:oklch(69.6% .17 162.48);--color-purple-400:oklch(71.4% .203 305.504);--color-purple-500:oklch(62.7% .265 303.9);--color-zinc-100:oklch(96.7% .001 286.375);--color-zinc-200:oklch(92% .004 286.32);--color-zinc-300:oklch(87.1% .006 286.286);--color-zinc-400:oklch(70.5% .015 286.067);--color-zinc-500:oklch(55.2% .016 285.938);--color-zinc-800:oklch(27.4% .006 286.033);--color-zinc-900:oklch(21% .006 285.885);--color-zinc-950:oklch(14.1% .005 285.823);--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--font-weight-medium:500;--font-weight-bold:700;--tracking-wider:.05em;--tracking-widest:.1em;--leading-relaxed:1.625;--radius-md:.375rem;--radius-xl:.75rem;--radius-2xl:1rem;--radius-3xl:1.5rem;--drop-shadow-2xl:0 25px 25px #00000026;--animate-spin:spin 1s linear infinite;--animate-ping:ping 1s cubic-bezier(0, 0, .2, 1) infinite;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--blur-md:12px;--blur-xl:24px;--blur-2xl:40px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1)}}@layer base,components;@layer utilities{.pointer-events-none{pointer-events:none}.collapse{visibility:collapse}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.inset-0{inset:calc(var(--spacing) * 0)}.top-\[115\%\]{top:115%}.-bottom-3{bottom:calc(var(--spacing) * -3)}.-bottom-6{bottom:calc(var(--spacing) * -6)}.-bottom-8{bottom:calc(var(--spacing) * -8)}.bottom-\[108\%\]{bottom:108%}.left-1\/2{left:50%}.left-\[47\%\]{left:47%}.left-\[48\%\]{left:48%}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}.container{width:100%}@media(min-width:40rem){.container{max-width:40rem}}@media(min-width:48rem){.container{max-width:48rem}}@media(min-width:64rem){.container{max-width:64rem}}@media(min-width:80rem){.container{max-width:80rem}}@media(min-width:96rem){.container{max-width:96rem}}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-2{height:calc(var(--spacing) * 2)}.h-2\.5{height:calc(var(--spacing) * 2.5)}.h-4{height:calc(var(--spacing) * 4)}.h-8{height:calc(var(--spacing) * 8)}.h-10{height:calc(var(--spacing) * 10)}.h-full{height:100%}.w-1\.5{width:calc(var(--spacing) * 1.5)}.w-2{width:calc(var(--spacing) * 2)}.w-2\.5{width:calc(var(--spacing) * 2.5)}.w-4{width:calc(var(--spacing) * 4)}.w-8{width:calc(var(--spacing) * 8)}.w-10{width:calc(var(--spacing) * 10)}.w-\[90vw\]{width:90vw}.w-full{width:100%}.max-w-\[200px\]{max-width:200px}.max-w-\[220px\]{max-width:220px}.max-w-\[340px\]{max-width:340px}.max-w-\[500px\]{max-width:500px}.-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.animate-ping{animation:var(--animate-ping)}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-default{cursor:default}.resize{resize:both}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.overflow-hidden{overflow:hidden}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-3xl{border-radius:var(--radius-3xl)}.rounded-\[1\.75rem\]{border-radius:1.75rem}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-4{border-style:var(--tw-border-style);border-width:4px}.border-emerald-500\/20{border-color:#00bb7f33}@supports (color:color-mix(in lab,red,red)){.border-emerald-500\/20{border-color:color-mix(in oklab,var(--color-emerald-500) 20%,transparent)}}.border-purple-500\/10{border-color:#ac4bff1a}@supports (color:color-mix(in lab,red,red)){.border-purple-500\/10{border-color:color-mix(in oklab,var(--color-purple-500) 10%,transparent)}}.border-purple-500\/15{border-color:#ac4bff26}@supports (color:color-mix(in lab,red,red)){.border-purple-500\/15{border-color:color-mix(in oklab,var(--color-purple-500) 15%,transparent)}}.border-purple-500\/20{border-color:#ac4bff33}@supports (color:color-mix(in lab,red,red)){.border-purple-500\/20{border-color:color-mix(in oklab,var(--color-purple-500) 20%,transparent)}}.border-purple-500\/25{border-color:#ac4bff40}@supports (color:color-mix(in lab,red,red)){.border-purple-500\/25{border-color:color-mix(in oklab,var(--color-purple-500) 25%,transparent)}}.border-white\/10{border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.border-white\/10{border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.border-zinc-800{border-color:var(--color-zinc-800)}.border-zinc-800\/40{border-color:#27272a66}@supports (color:color-mix(in lab,red,red)){.border-zinc-800\/40{border-color:color-mix(in oklab,var(--color-zinc-800) 40%,transparent)}}.border-t-emerald-500{border-top-color:var(--color-emerald-500)}.bg-purple-400{background-color:var(--color-purple-400)}.bg-white{background-color:var(--color-white)}.bg-zinc-800\/40{background-color:#27272a66}@supports (color:color-mix(in lab,red,red)){.bg-zinc-800\/40{background-color:color-mix(in oklab,var(--color-zinc-800) 40%,transparent)}}.bg-zinc-900{background-color:var(--color-zinc-900)}.bg-zinc-900\/90{background-color:#18181be6}@supports (color:color-mix(in lab,red,red)){.bg-zinc-900\/90{background-color:color-mix(in oklab,var(--color-zinc-900) 90%,transparent)}}.bg-zinc-950\/40{background-color:#09090b66}@supports (color:color-mix(in lab,red,red)){.bg-zinc-950\/40{background-color:color-mix(in oklab,var(--color-zinc-950) 40%,transparent)}}.bg-zinc-950\/80{background-color:#09090bcc}@supports (color:color-mix(in lab,red,red)){.bg-zinc-950\/80{background-color:color-mix(in oklab,var(--color-zinc-950) 80%,transparent)}}.bg-zinc-950\/90{background-color:#09090be6}@supports (color:color-mix(in lab,red,red)){.bg-zinc-950\/90{background-color:color-mix(in oklab,var(--color-zinc-950) 90%,transparent)}}.p-2{padding:calc(var(--spacing) * 2)}.p-4{padding:calc(var(--spacing) * 4)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-4{padding-block:calc(var(--spacing) * 4)}.pb-8{padding-bottom:calc(var(--spacing) * 8)}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.text-amber-400{color:var(--color-amber-400)}.text-purple-400{color:var(--color-purple-400)}.text-red-400{color:var(--color-red-400)}.text-white{color:var(--color-white)}.text-zinc-100{color:var(--color-zinc-100)}.text-zinc-200{color:var(--color-zinc-200)}.text-zinc-300{color:var(--color-zinc-300)}.text-zinc-400{color:var(--color-zinc-400)}.text-zinc-500{color:var(--color-zinc-500)}.uppercase{text-transform:uppercase}.italic{font-style:italic}.opacity-50{opacity:.5}.opacity-75{opacity:.75}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[0_10px_30px_rgba\(139\,92\,246\,0\.15\)\]{--tw-shadow:0 10px 30px var(--tw-shadow-color,#8b5cf626);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.blur-2xl{--tw-blur:blur(var(--blur-2xl));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.drop-shadow-2xl{--tw-drop-shadow-size:drop-shadow(0 25px 25px var(--tw-drop-shadow-color,#00000026));--tw-drop-shadow:drop-shadow(var(--drop-shadow-2xl));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-blur-md{--tw-backdrop-blur:blur(var(--blur-md));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-xl{--tw-backdrop-blur:blur(var(--blur-xl));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.select-none{-webkit-user-select:none;user-select:none}@media(min-width:48rem){.md\:max-w-\[420px\]{max-width:420px}.md\:max-w-\[640px\]{max-width:640px}.md\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}}.\[\&\>svg\]\:h-full>svg{height:100%}.\[\&\>svg\]\:w-full>svg{width:100%}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}@keyframes pulse{50%{opacity:.5}}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { useState as A, useEffect as D } from "react";
|
|
2
|
+
const v = "__rraSpeechActivity", l = {
|
|
3
|
+
chargePerChar: 0.12,
|
|
4
|
+
decayMs: 140,
|
|
5
|
+
maxChargePerPush: 0.9
|
|
6
|
+
};
|
|
7
|
+
function R(e = {}) {
|
|
8
|
+
const t = e.chargePerChar ?? l.chargePerChar, n = e.decayMs ?? l.decayMs, r = e.maxChargePerPush ?? l.maxChargePerPush, a = e.now ?? (typeof performance < "u" ? () => performance.now() : () => Date.now());
|
|
9
|
+
let c = 0, o = a();
|
|
10
|
+
const h = (s) => {
|
|
11
|
+
const i = s - o;
|
|
12
|
+
o = s, i > 0 && (c *= Math.exp(-i / n));
|
|
13
|
+
};
|
|
14
|
+
return {
|
|
15
|
+
[v]: !0,
|
|
16
|
+
push(s) {
|
|
17
|
+
if (!s) return;
|
|
18
|
+
const i = a();
|
|
19
|
+
h(i), c = Math.min(1, c + Math.min(r, s.length * t));
|
|
20
|
+
},
|
|
21
|
+
end() {
|
|
22
|
+
},
|
|
23
|
+
reset() {
|
|
24
|
+
c = 0, o = a();
|
|
25
|
+
},
|
|
26
|
+
sample() {
|
|
27
|
+
return h(a()), c < 1e-3 ? 0 : Math.min(1, c);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function q(e) {
|
|
32
|
+
return !!e && e[v] === !0;
|
|
33
|
+
}
|
|
34
|
+
const M = 0.05;
|
|
35
|
+
function x(e) {
|
|
36
|
+
let t = new Uint8Array(e.frequencyBinCount), n = new Uint8Array(e.frequencyBinCount);
|
|
37
|
+
return {
|
|
38
|
+
read() {
|
|
39
|
+
t.length !== e.frequencyBinCount && (t = new Uint8Array(e.frequencyBinCount), n = new Uint8Array(e.frequencyBinCount)), e.getByteTimeDomainData(t);
|
|
40
|
+
let r = 0;
|
|
41
|
+
for (let u = 0; u < t.length; u++) {
|
|
42
|
+
const d = Math.abs(t[u] - 128);
|
|
43
|
+
d > r && (r = d);
|
|
44
|
+
}
|
|
45
|
+
const a = Math.min(1, r / 128);
|
|
46
|
+
if (a <= M)
|
|
47
|
+
return { level: 0, shape: "closed" };
|
|
48
|
+
e.getByteFrequencyData(n);
|
|
49
|
+
const o = (e.context.sampleRate || 24e3) / 2 / e.frequencyBinCount, h = (u, d) => {
|
|
50
|
+
let C = 0;
|
|
51
|
+
const E = Math.round(u / o), w = Math.min(Math.round(d / o), n.length - 1);
|
|
52
|
+
for (let f = E; f <= w; f++) C += n[f];
|
|
53
|
+
return C;
|
|
54
|
+
}, s = h(200, 800), i = h(800, 1800), g = h(1800, 3200), p = s + i + g + 1e-3, y = g / p, P = i / p;
|
|
55
|
+
let m = "a";
|
|
56
|
+
return y > 0.35 ? m = "e" : P > 0.4 && y < 0.2 && (m = "o"), { level: a, shape: m };
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function B() {
|
|
61
|
+
let e = Math.random() * 100;
|
|
62
|
+
return {
|
|
63
|
+
read() {
|
|
64
|
+
e += 0.18;
|
|
65
|
+
const t = 0.35 + 0.3 * Math.sin(e) + 0.25 * Math.sin(e * 1.7 + 1.3) + 0.1 * Math.sin(e * 3.1), n = Math.min(1, Math.max(0, t));
|
|
66
|
+
if (n <= M + 0.03)
|
|
67
|
+
return { level: 0, shape: "closed" };
|
|
68
|
+
const r = Math.sin(e * 0.43), a = r > 0.55 ? "o" : r < -0.6 ? "e" : "a";
|
|
69
|
+
return { level: n, shape: a };
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function S(e) {
|
|
74
|
+
let t = Math.random() * 100;
|
|
75
|
+
return {
|
|
76
|
+
read() {
|
|
77
|
+
const n = e.sample();
|
|
78
|
+
if (n <= M)
|
|
79
|
+
return { level: 0, shape: "closed" };
|
|
80
|
+
t += 0.3;
|
|
81
|
+
const r = 0.5 + 0.34 * Math.sin(t) + 0.16 * Math.sin(t * 1.7 + 1.3), a = Math.min(1, n * Math.max(0, r)), c = Math.sin(t * 0.41), o = c > 0.55 ? "o" : c < -0.6 ? "e" : "a";
|
|
82
|
+
return { level: a, shape: o };
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function b(e) {
|
|
87
|
+
return q(e) ? S(e) : e ? x(e) : B();
|
|
88
|
+
}
|
|
89
|
+
function L() {
|
|
90
|
+
const [e, t] = A(!1);
|
|
91
|
+
return D(() => {
|
|
92
|
+
const n = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
93
|
+
t(n.matches);
|
|
94
|
+
const r = (a) => t(a.matches);
|
|
95
|
+
return n.addEventListener("change", r), () => n.removeEventListener("change", r);
|
|
96
|
+
}, []), e;
|
|
97
|
+
}
|
|
98
|
+
export {
|
|
99
|
+
v as S,
|
|
100
|
+
R as a,
|
|
101
|
+
b as c,
|
|
102
|
+
q as i,
|
|
103
|
+
L as u
|
|
104
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";const E=require("react"),M="__rraSpeechActivity",l={chargePerChar:.12,decayMs:140,maxChargePerPush:.9};function S(e={}){const t=e.chargePerChar??l.chargePerChar,n=e.decayMs??l.decayMs,r=e.maxChargePerPush??l.maxChargePerPush,a=e.now??(typeof performance<"u"?()=>performance.now():()=>Date.now());let c=0,o=a();const h=i=>{const s=i-o;o=i,s>0&&(c*=Math.exp(-s/n))};return{[M]:!0,push(i){if(!i)return;const s=a();h(s),c=Math.min(1,c+Math.min(r,i.length*t))},end(){},reset(){c=0,o=a()},sample(){return h(a()),c<.001?0:Math.min(1,c)}}}function A(e){return!!e&&e[M]===!0}const g=.05;function q(e){let t=new Uint8Array(e.frequencyBinCount),n=new Uint8Array(e.frequencyBinCount);return{read(){t.length!==e.frequencyBinCount&&(t=new Uint8Array(e.frequencyBinCount),n=new Uint8Array(e.frequencyBinCount)),e.getByteTimeDomainData(t);let r=0;for(let u=0;u<t.length;u++){const d=Math.abs(t[u]-128);d>r&&(r=d)}const a=Math.min(1,r/128);if(a<=g)return{level:0,shape:"closed"};e.getByteFrequencyData(n);const o=(e.context.sampleRate||24e3)/2/e.frequencyBinCount,h=(u,d)=>{let v=0;const w=Math.round(u/o),D=Math.min(Math.round(d/o),n.length-1);for(let f=w;f<=D;f++)v+=n[f];return v},i=h(200,800),s=h(800,1800),p=h(1800,3200),y=i+s+p+.001,C=p/y,P=s/y;let m="a";return C>.35?m="e":P>.4&&C<.2&&(m="o"),{level:a,shape:m}}}}function B(){let e=Math.random()*100;return{read(){e+=.18;const t=.35+.3*Math.sin(e)+.25*Math.sin(e*1.7+1.3)+.1*Math.sin(e*3.1),n=Math.min(1,Math.max(0,t));if(n<=g+.03)return{level:0,shape:"closed"};const r=Math.sin(e*.43),a=r>.55?"o":r<-.6?"e":"a";return{level:n,shape:a}}}}function R(e){let t=Math.random()*100;return{read(){const n=e.sample();if(n<=g)return{level:0,shape:"closed"};t+=.3;const r=.5+.34*Math.sin(t)+.16*Math.sin(t*1.7+1.3),a=Math.min(1,n*Math.max(0,r)),c=Math.sin(t*.41),o=c>.55?"o":c<-.6?"e":"a";return{level:a,shape:o}}}}function T(e){return A(e)?R(e):e?q(e):B()}function x(){const[e,t]=E.useState(!1);return E.useEffect(()=>{const n=window.matchMedia("(prefers-reduced-motion: reduce)");t(n.matches);const r=a=>t(a.matches);return n.addEventListener("change",r),()=>n.removeEventListener("change",r)},[]),e}exports.SPEECH_ACTIVITY_BRAND=M;exports.createMouthEngine=T;exports.createSpeechActivity=S;exports.isSpeechActivity=A;exports.useReducedMotion=x;
|
package/dist/lib/vrm.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("./VrmAvatar-D_jr2TOG.cjs");exports.VrmAvatar=r.VrmAvatar;
|
package/dist/lib/vrm.js
ADDED