sandai-react
Version:
React components and utilities for the Sandai 3D AI Characters.
255 lines (190 loc) • 12.1 kB
Markdown
# sandai-react
**The React way to embed a conversational 3D AI avatar; on-device, privacy-first, one component.**
See what it's able to do in a [**custom implementation** from **DIE KAVALLERIE** 🐴](https://sustainabilitymap-mit-horst.diekavallerie.de/)
**Live playground:** [sandai.org](https://sandai.org/api_playground)
`sandai-react` is the React SDK for dropping a fully interactive **3D AI avatar** into any React or Next.js app. Mount a single `<AI3DCharacter />` component and you get a real-time **AI avatar**. A browser-based **digital human, mascot, animal, you name it**, that can speak, listen, think, and emote. Under the hood it renders the same heavily optimized runtime as [`sandai-core`](https://gitlab.com/DavidCks1/sandai-core), so you get a complete **conversational AI avatar**: on-device **text-to-speech**, **speech recognition**, an **on-device LLM**, **emotion inference**, and **lip-sync**, with no backend to run and nothing to install.
By default, **everything runs locally in the browser** via WebAssembly and WebGL/WebGPU. No API keys, no per-message costs, and no user audio ever leaves the device.
`sandai-react` re-exports all of `sandai-core` and wraps it in idiomatic React: a declarative component that builds the embed URL from props and hands you a typed `SandaiClient` in `onLoad`. It builds on the same stack: [`sandai-core`](https://gitlab.com/DavidCks1/sandai-core) → [`ai-vrm-chat`](https://gitlab.com/DavidCks1/ai-vrm-chat) → [`ai-character`](https://gitlab.com/DavidCks1/ai-character) → [`@davidcks/r3f-vrm`](https://gitlab.com/DavidCks1/r3f-vrm) (Three.js / React Three Fiber).
## Features
- **One component, zero boilerplate**: render `<AI3DCharacter />`, get a typed `SandaiClient` in `onLoad`. No iframe wiring, no `postMessage` plumbing, no 3D engine, no game engine, no local GPU server, no API gateway.
- **3D VRM avatars**: load any `.vrm` model and get a fully animated **talking avatar** with auto-blink, gaze, idle motion, and camera focus. Bring your own character, use a bundled one, or **pick from over 100,000 models** at [sandai.org](https://sandai.org/dashboard#API-Playground).
- **On-device LLM**: a **local LLM** (Gemma, Qwen, and others via transformers.js / ONNX and MediaPipe LiteRT-LM) generates replies in-browser. Or plug in your own and just stream text in.
- **Text-to-speech, 100+ voices, 30+ languages**: built-in multilingual **TTS** powered by Piper, selectable per character with the typed `voiceName` prop.
- **Real-time lip-sync**: accurate **lip-sync** driven from phonemes *or* directly from the audio waveform, with coarticulation so the mouth never looks robotic.
- **Speech recognition (ASR) + VAD**: the avatar can **listen** and reply on its own via on-device Whisper / Moonshine speech-to-text with Silero voice-activity detection.
- **Emotion inference & expression**: text is mapped to 28 GoEmotions-style **emotions** that automatically drive **facial expressions** and graded **body animations**.
- **Fully typed**: props, voices (`VoiceNames`), environments (`Environments`), and the entire `SandaiClient` RPC surface ship with TypeScript types and autocomplete.
- **WebGL & WebGPU rendering**, with an optional experimental GPU path-tracer (`raytrace`) for cinematic, ray-traced scenes.
- **Built-in dev tooling**: `AI3DCharacterDocs` renders interactive, runnable documentation for every method the character exposes — a live playground inside your own app.
- **Privacy-first, no backend**: conversation, voice, and inference happen on the user's device. Great for GDPR-sensitive products, offline-capable apps, and **keeping inference costs at zero**.
## Use cases
**AI agents** and website assistants, customer support and onboarding, **AI companions** and VTubers, interactive characters for games and education, **AI mascots** and brand mascots and virtual presenters, and any React product that benefits from a face you can talk to.
## Table of Contents
- [sandai-react](#sandai-react)
- [Features](#features)
- [Use cases](#use-cases)
- [Table of Contents](#table-of-contents)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Components](#components)
- [`AI3DCharacter`](#ai3dcharacter)
- [`AI3DCharacterDocs`](#ai3dcharacterdocs)
- [`FunctionTester`](#functiontester)
- [Driving the character](#driving-the-character)
- [Re-exports from sandai-core](#re-exports-from-sandai-core)
- [Contributing](#contributing)
- [License](#license)
## Installation
```sh
npm install sandai-react
```
Peer dependencies (React 19):
```sh
npm install react react-dom
```
## Quick Start
Mount the component, size its container, and grab the client in `onLoad`:
```tsx
import { AI3DCharacter, SandaiClient } from "sandai-react";
import { useState } from "react";
export default function App() {
const [client, setClient] = useState<SandaiClient>();
return (
<div style={{ width: "100%", height: "600px" }}>
<AI3DCharacter
url="https://sandai.org/chat"
vrmUrl="https://cdn.example/ada.vrm"
voiceName="ruri"
environment="tokyo"
// userId & apiKey are optional. Without them you may see a watermark.
// Grab yours in the dashboard: https://sandai.org/dashboard
onLoad={async (c) => {
setClient(c);
// Speak some text (emotion + lip-sync inferred automatically):
const { speechEndPromise } = await c.interactionManager.say("Hi there!");
await speechEndPromise;
// Or have the on-device LLM reply:
await c.interactionManager.respond("What's your name?");
}}
/>
</div>
);
}
```
> The component renders an `iframe` at `width: 100%; height: 100%`, so give its parent an explicit size.
>
> Audio can't play until the user has interacted with the page (browser autoplay policy). If the character won't speak, that's almost always why — the underlying `LoadManager` tracks interaction state for you.
## Components
### `AI3DCharacter`
The one component you mount. It builds the Sandai embed URL from its props, renders the avatar in an `iframe`, constructs a [`SandaiClient`](https://gitlab.com/DavidCks1/sandai-core), and hands it to you via `onLoad` once it's ready.
```tsx
import { AI3DCharacter } from "sandai-react";
```
#### Props
| Prop | Type | Description |
|------|------|-------------|
| `url` | `string` | **Required.** Base URL of the Sandai chat interface (e.g. `https://sandai.org/chat`). |
| `onLoad` | `(client: SandaiClient) => void` | **Required.** Fired once the `SandaiClient` is initialized and ready to drive. |
| `vrmUrl` | `string` | URL of the `.vrm` model to load as the character. |
| `voiceName` | `VoiceNames` | TTS voice key (100+ multilingual Piper voices). |
| `environment` | `Environments` | Environment preset (e.g. `transparent`, `tokyo`, `sky`, `candyshop`, ...). |
| `showControls` | `boolean` | Show the debug overlay: chat input, emotion sliders, and built-in voices. Handy for previewing how the 5 VRM blendshapes combine into Sandai's 27 emotions. |
| `lowPerformanceMode` | `boolean` | Lighter render path (drops bloom/depth-of-field) for older or low-end devices. |
| `cameraType` | `"default" \| "orthographic"` | Camera projection. `default` is perspective. |
| `initialFocus` | `FocusProps` | Initial camera focus (`focusIntensity`, `cameraOffset`, `lookAtOffset`, `trackCharacterLookAt`, `focusMode`). |
| `raytrace` | `boolean` | Enable the experimental GPU path-tracer. |
| `headAnimationSmoothing` | `number` | Dampen head motion, `0..1` (0 = raw, 1 = frozen). |
| `bodyAnimationSmoothing` | `number` | Dampen body motion, `0..1`. |
| `armsAnimationSmoothing` | `number` | Dampen arm motion, `0..1`. |
| `legsAnimationSmoothing` | `number` | Dampen leg motion, `0..1`. |
| `handsAnimationSmoothing` | `number` | Dampen hand motion, `0..1`. |
| `faceAnimationSmoothing` | `number` | Dampen face/expression motion, `0..1`. Try `0.1`–`0.9` if the mouth looks choppy. |
| `decoupleOverlappingExpressionsExperimental` | `boolean` | Best-guess split of conflicting blendshapes (e.g. a model that opens its mouth when *happy*). |
| `apiKey` | `string` | Your API key (from the [dashboard](https://sandai.org/dashboard)). Optional; omit and you may see a watermark. |
| `userId` | `string` | Your user ID (from the dashboard). |
| `debugMode` | `boolean` | Skips auth + load checks. Convenient locally; **turn off in production** (it disables the interaction gate used to enable audio). |
| `style` | `React.CSSProperties` | Inline styles for the underlying `iframe`. |
| `className` | `string` | Class name for the underlying `iframe`. |
#### Example
```tsx
import { AI3DCharacter } from "sandai-react";
<AI3DCharacter
url="https://sandai.org/chat"
vrmUrl="https://example.com/model.vrm"
voiceName="ruri"
environment="tokyo"
showControls
faceAnimationSmoothing={0.4}
onLoad={(client) => console.log("Sandai client ready", client)}
/>
```
---
### `AI3DCharacterDocs`
Renders **interactive, runnable documentation** for every method the loaded `SandaiClient` exposes — a live API playground you can drop straight into your own app. Pass it the `client` from `AI3DCharacter`'s `onLoad`.
#### Props
| Prop | Type | Description |
|------|------|-------------|
| `client` | `SandaiClient` | The instance from `AI3DCharacter`'s `onLoad` callback. |
#### Example
```tsx
import { AI3DCharacter, AI3DCharacterDocs } from "sandai-react";
import { useState } from "react";
const App = () => {
const [client, setClient] = useState(null);
return (
<>
<AI3DCharacter url="https://sandai.org/chat" onLoad={setClient} showControls />
{client && <AI3DCharacterDocs client={client} />}
</>
);
};
```
---
### `FunctionTester`
A reusable form that dynamically generates input fields from a schema and calls a function on submit. It powers `AI3DCharacterDocs`, and you can use it standalone to build quick test harnesses around any function.
#### Props
| Prop | Type | Description |
|------|------|-------------|
| `func` | `Function` | The function to invoke; receives the form values as arguments. |
| `schema` | `SchemaType[]` | Field definitions for the function parameters. |
#### Example
```tsx
import { FunctionTester } from "sandai-react";
const myFunction = (name, age) => console.log(`Name: ${name}, Age: ${age}`);
const schema = [
{ id: "name", type: "text", label: "Name" },
{ id: "age", type: "number", label: "Age" },
];
<FunctionTester func={myFunction} schema={schema} />;
```
## Driving the character
`onLoad` hands you a `SandaiClient`, which is your control surface for the avatar. The most common calls:
```tsx
// Speak text — emotion + lip-sync are inferred automatically.
const { speechEndPromise } = await client.interactionManager.say("Hello!");
await speechEndPromise;
// Ask the on-device LLM for a reply, then speak it.
const reply = await client.interactionManager.respond("Tell me a joke about space.");
// Interrupt the current utterance.
await client.interactionManager.stop();
// Call any method on the character by typed dot-path (full autocomplete).
await client.rpcManager.call("setEmotion", "joy", 0.8);
await client.rpcManager.call("vrmManager.focusManager.focus");
```
The full `SandaiClient` API — `interactionManager`, `loadManager`, `authManager`, and the typed `rpcManager` — is documented in the [`sandai-core` README](https://gitlab.com/DavidCks1/sandai-core).
## Re-exports from sandai-core
`sandai-react` re-exports everything from `sandai-core`, so you can import the client, the URL builder, the voices map, and all the types straight from this package:
```tsx
import {
SandaiClient,
UrlBuilder,
voices,
type VoiceNames,
type Environments,
type AllRpcMethods,
} from "sandai-react";
```
## Contributing
Contributions are welcome! If you have suggestions or find issues, feel free to open an issue or a pull request.
## License
MIT. See the `LICENSE` file for details.