Robust pattern covering mic denial, timeout, and unexpected drops.
tsx
import { useAvatar, useAvatarEvent } from "@avatarfactory/react";// Copy for the codes your users can act on. Everything else gets one generic// line — error.message is written for you, not for them.const COPY: Partial<Record<AvatarErrorCode, string>> = { MIC_PERMISSION_DENIED: "Enable your microphone in browser settings.", SESSION_IDLE_TIMEOUT: "Session timed out due to inactivity.", CONNECTION_FAILED: "We couldn't reach the service. Check your connection.",};function RobustController() { const { error, start } = useAvatar(); useAvatarEvent("error", (err) => { analytics.track("avatar_error", { code: err.code, source: err.source, fatal: err.fatal, message: err.message, }); }); // No branch replacing the avatar: a fatal error ends the session but leaves // the avatar painted with isIdle back to true, so the start button reappears // and IS the retry. Just say what happened. if (error?.fatal) { return <Banner>{COPY[error.code] ?? "Something went wrong on our side."}</Banner>; } return null;}
Live context — product page assistant
Feed the avatar real-time information about what the user is viewing.
tsx
import { useEffect } from "react";import { useAvatar } from "@avatarfactory/react";function ProductAssistant({ product }) { const { addLiveContext } = useAvatar(); // Update the AI's context whenever the product changes. useEffect(() => { addLiveContext?.({ context: `User is viewing: ${product.name} — $${product.price}. ` + `Description: ${product.description}`, // Set update: true to replace the previous context entirely // instead of appending to it. }); }, [product, addLiveContext]); return null; // Avatar UI is handled by <Avatar /> elsewhere}
addLiveContext defaults to append — each call adds to the existing context. Pass update: true to replace it. Keep context strings concise and factual; max 2000 characters.
Audio mode — custom TTS pipeline
Use your own audio source (ElevenLabs, Play.ai, etc.) with the avatar's lip-sync.
A hold-to-talk button for noisy rooms, where an automatic endpointer would keep
triggering on background speech.
tsx
const config = { getSessionToken: async () => fetchSessionToken(), mode: "call" as const, turnTaking: { mode: "push-to-talk" as const, spacebar: true }, avatar: { avatarId: "default" }, controls: { enabled: false }, // we're building our own};function TalkButton() { const { isPushToTalk, isTalking, startTalking, stopTalking } = useAvatar(); if (!isPushToTalk) return null; return ( <button // Cancel and leave matter as much as up: a pointer that escapes the // button would otherwise leave the turn open forever. onPointerDown={startTalking} onPointerUp={stopTalking} onPointerCancel={stopTalking} onPointerLeave={stopTalking} aria-pressed={isTalking} > {isTalking ? "Listening…" : "Hold to talk"} </button> );}
Camera perception with picture-in-picture
Let the avatar see, and give the camera the stage while it does.
tsx
const config = { getSessionToken: async () => fetchSessionToken(), mode: "call" as const, perception: { camera: true }, // a grant, not a switch — it opens just-in-time avatar: { avatarId: "default", systemPrompt: "You can see the user's camera when you ask to look.", }, controls: { enabled: true },};export function VisionApp() { return ( <AvatarProvider config={config}> <div style={{ width: 480, height: 480 }}> <Avatar usePictureInPicture pictureInPictureCorner="top-right" /> </div> </AvatarProvider> );}
Handling a plan limit
A notice is the server ending the session gracefully — the avatar says the
message out loud, then the session closes. It is not an error, and unlike
error.message the text is written for your users.