Examples Web
examples/web is one of two reference Next.js apps in the Charivo workspace, and
the one to read when comparing client styles. Read it as a current integration
example, not just a demo. The other is
Examples Companion, a realtime-only app.
What It Covers
The app exercises the current package stack:
- Live2D rendering through
@charivo/render-live2dand@charivo/render - LLM chat through Gemini and OpenAI, each in a server-route and a browser-direct form, plus an OpenClaw proxy (dev builds only) and a stub
- TTS through Gemini and OpenAI, each in a server-route and a browser-direct form, plus a browser-native player
- STT through Gemini and OpenAI, each in a streaming
(
@charivo/stt/gemini-live,@charivo/stt/openai-realtime), a server-route and a browser-direct form, plus a browser-native transcriber - realtime voice sessions through
@charivo/realtime/remoteand/api/realtime, over the OpenAI Agents WebRTC adapter or the Gemini Live WebSocket adapter, chosen in the settings menu (Gemini Live by default) - avatar expression/motion/gaze tool calling through
@charivo/avatar, wired into both LLM chat and realtime voice sessions
Lifecycle Split
The current hook split is deliberate:
useLive2Downs canvas mount and unmountuseCharivoChatowns Charivo setup, render/LLM/TTS/STT manager attachment, event subscription, and teardownuseRealtimeModeowns the realtime manager: it attaches and detaches it on the shared Charivo instance, and starts and stops the session
This keeps renderer lifecycle separate from conversation lifecycle, and both separate from realtime session lifecycle.
API Routes
The current reference app ships:
POST /api/chat-openaiUses@charivo/server/openaiwith modelgpt-4.1-nanoPOST /api/chat-openclawUses@charivo/server/openclaw. Local-only, matching the two menu entries it serves: it answers 404 in a production build, whereOPENCLAW_BASE_URLdefaults to the server's own localhost and forwardingOPENCLAW_TOKENto a published gateway would expose itPOST /api/chat-geminiUses@charivo/server/geminiwith modelgemini-3.5-flash-litePOST /api/tts-openaiUses@charivo/server/openaiwith modelgpt-4o-mini-tts. The route resolves the voice itself and passes it explicitly, so the provider default is never consulted: a supplied voice must be on the allowlist or the request is rejected with 400, and thesagefallback applies only when none is sentPOST /api/tts-geminiUses@charivo/server/geminiwith modelgemini-3.1-flash-tts-preview. Same voice-resolution behavior as/api/tts-openai, against a Gemini-specific allowlist with aKorefallback. The route streams PCM when the request carriesAccept: audio/pcm(which the Gemini Remote TTS option sends) and answers WAV otherwise. Text is capped at 400 characters: on the buffered path that keeps one synthesis, or a failed answer plus its retry, inside the route's 25s deadline (kept under the remote player's fixed 30s timeout); on the streaming path the same cap avoids a length past which a stream can end on a spuriousSAFETYfinish reason instead of completing (measured: 2,358 characters streamed 96.60s of audio beforeSAFETY, 1,182 completed withSTOP).speedis accepted but ignoredPOST /api/stt-openaiUses@charivo/server/openaiwith modelwhisper-1POST /api/stt-geminiUses@charivo/server/geminiwith modelgemini-3.5-transcribe. Same multipart contract and 1 MB cap as/api/stt-openai. The free tier allows 3 requests per minute on this model, so a fourth within a minute fails with the route's generic 500 — the demo does not throttlePOST /api/stt-openai-realtimeExchanges the OpenAI streaming transcriber's SDP offer with OpenAI so the browser never holds a keyPOST /api/stt-gemini-liveMints a single-use ephemeral Gemini Live token for@charivo/stt/gemini-live, pinning the transcription model and manual VAD in the token's own setup, and returns it with the websocket urlPOST /api/realtimeUses@charivo/server/openaior@charivo/server/gemini, assession.providerselects, to create a realtime session bootstrap; either branch rebuilds the session config server side, and the Gemini branch also requirestransport: "websocket"
Demo Safeguards
The routes are unauthenticated by design — they are a demo, not a deployable
backend. What they do carry is cost bounding, worth copying even though the auth
is missing: cost-bearing session fields are pinned server side
(examples/web/src/app/api/demo-limits.ts), TTS text and realtime
instructions/tools are size-capped, voices come from a per-provider allowlist,
and a client-side timer caps a production realtime session at 90 seconds (15
minutes in development) because the browser talks to the provider directly once
bootstrapped and the server can no longer hang up. A separate timer with the
same limit arms on STT recording, where either streaming transcriber holds an
equally wall-clock-billed session for as long as it records.
Runtime Modes
The settings UI intentionally exposes several implementation styles in one place:
- remote API paths for production-oriented flows, which is where every leg starts: chat and TTS on Gemini Remote, transcription on Gemini Live rather than the unary Gemini Remote route, whose request-per-minute allowance is the tighter of the two (a silent recording then ends in a timeout rather than an empty transcript)
- browser-direct OpenAI, Gemini, and OpenClaw paths for development and testing
(the OpenClaw options are hidden in production builds — they need a gateway
on
OPENCLAW_BASE_URL, which defaults to localhost); TTS and STT mirror the LLM split with their own Gemini Remote and Gemini Direct (Dev) options - browser-native TTS and STT paths for zero-server speech experiments
- two streaming STT paths, backed by
/api/stt-openai-realtimeand/api/stt-gemini-live - a realtime provider selector, OpenAI Realtime or Gemini Live, that starts on Gemini Live and locks while a call is connecting or up
- stub LLM mode for deterministic UI work
Files To Read
examples/web/README.mdexamples/web/src/app/page.tsxexamples/web/src/app/hooks/useCharivoChat.tsexamples/web/src/app/hooks/useLive2D.tsexamples/web/src/app/hooks/useRealtimeMode.tsexamples/web/src/app/hooks/realtime-ui.ts
When To Use It
Use examples/web when you want:
- a concrete browser integration example
- working API route examples
- a reference for lifecycle boundaries between renderer setup and chat/session setup
- a place to compare runtime choices before designing your own app shell