Live avatar: once the call connects and the agent has its avatar setting on, the square stage shows the live video of the AI avatar (stock face Layla by default) lip-synced to the agent's voice, with an “AI avatar” chip. Until then — and whenever the live video is unavailable — the stage shows a still of Layla (avatar-layla.jpg, source in AVATAR-MEDIA.md); no clips are configured, so Video.js is never loaded. /avatar-clips shows the same card with a presenter clip as the failover instead.
This page is the host site: the call lives in the card below. No build
step — the page loads widget.bundle.js directly with a
<script> tag. Every press of Call asks
example/server.py for a fresh room, so the short-lived LiveKit token
is never stale.
The widget is the optional component shipped by the SDK. Restyle it by overriding the tokens on .ht-widget — see
“Optional widget” in README.md.
“Call details” inside the widget holds the dynamic variables the agent receives;
the list itself is declared once in example/scenarios/ and chosen
by the server, next to the agent it belongs to. Call talks,
Chat runs the same conversation with no microphone and no audio. The second
layout — the call as the whole page — is at /call, and
/free is this same widget with nothing configured: the agent
and every variable are typed in. /avatar shows avatar mode. /file replaces the
microphone with an mp3 or wav file.
.env on that server — see
.env.example. Neither this page nor the SDK ever sees the key, which is
the shape a production integration has too: your backend calls
POST /v1/calls/web and returns just the session JSON to the browser. The
variable values typed into the widget are accepted only for the keys the server
declares — the browser names values, never the contract.