One component. A living receptionist.
@khaveeai/react renders a VRM avatar with idle life, lip-sync and gestures, and exposes the conversation as hooks. Bring any realtime provider. This whole website is built on it.
Three steps to a talking avatar.
Wrap your app
KhaveeProvider holds the avatar state: VRM instance, expressions, current animation and chat status.
Render the avatar
Put VRMAvatar inside a react-three-fiber Canvas. Pass a .vrm file and a map of animation clips.
Connect a provider
Pass a RealtimeProvider to the config. useRealtime() gives you connect, sendMessage, the transcript and the live status.
The snippet below is, give or take a camera, what renders Nong Khavee on this page.
import { KhaveeProvider, VRMAvatar, useRealtime } from '@khaveeai/react'
import { Canvas } from '@react-three/fiber'
export function Reception({ provider }) {
return (
<KhaveeProvider config={{ realtime: provider }}>
<Canvas camera={{ position: [0, 1.3, 3], fov: 30 }}>
<VRMAvatar
src="/models/nongkhavee.vrm"
animations={{ base: '/animations/idle.fbx', talk_1: '/animations/talk.fbx' }}
/>
</Canvas>
<Desk />
</KhaveeProvider>
)
}
function Desk() {
const { connect, sendMessage, chatStatus } = useRealtime()
return <button onClick={connect}>{chatStatus}</button>
}
// Swap the brain, keep the JSX.
import { OpenAIRealtimeProvider } from '@khaveeai/providers-openai-realtime'
const provider = new OpenAIRealtimeProvider({
useProxy: true,
proxyEndpoint: 'https://api.platform.khavee.ai/api/v1/projects/PROJECT_ID/chat/token',
voice: 'coral',
language: 'th',
})
Small packages, one interface.
@khaveeai/react
VRMAvatar, GLBAvatar, KhaveeProvider, useRealtime, useVRMExpressions, useAnimations, AvatarPostFX, ShadowFloor.
@khaveeai/core
RealtimeProvider and pipeline interfaces, vendor-neutral tool calling, typed project data from the platform.
@khaveeai/providers-openai-realtime
Full-duplex voice over WebRTC with automatic lip-sync and tool calls, with a token proxy so keys stay on the server.
@khaveeai/providers-mock
Canned LLM and TTS classes for tests and demos. No keys, no network.
Your UI never learns which vendor is talking.
Everything above the provider line depends only on the RealtimeProvider interface. Swap OpenAI's Realtime API for a composed STT, LLM and TTS pipeline, or for a scripted provider like the one running this page, and the JSX stays the same.
Your app
Chat UI, kiosk shell, booking screens
@khaveeai/react
VRMAvatar · KhaveeProvider · useRealtime
RealtimeProvider interface
connect · sendMessage · chatStatus · getAudioAnalyser
Providers
OpenAI Realtime · Generic STT/LLM/TTS · Scripted (this page)
The parts you'd rather not write twice.
Mixamo retargeting
Drop FBX clips from Mixamo into the animations map. Bones are remapped to the VRM humanoid automatically.
Status-driven clips
Name a clip talk, think, listen or greet and it plays for that chat status, with pose-aware crossfades.
Idle life
Breathing, weight-shift sway, blinking, expression drift and camera-relative gaze, all procedural.
Lip-sync
MFCC phoneme analysis from the provider's audio, mapped to the aa, ih, ou, ee, oh mouth shapes.
Tool calling
Register functions once. Providers map their own wire formats onto one vendor-neutral ToolCall shape.
Render quality
Shadows, anisotropy, tone mapping and an optional bloom + SMAA pass, tuned for MToon materials.
Ship a receptionist this sprint.
Grab an API key from the platform, install the packages, and bring your own VRM or use ours.