Presence and voice
The fifteen presences an owner can choose, how the SDK loads one, the ten live voices, and when the guide is allowed to open a microphone.
The character your users see and what it sounds like are both chosen in the Console, per environment, so staging can try one before production does.
The presences
Fifteen, and the difference between them is worth knowing before you choose.
| Name | Slug | Kind |
|---|---|---|
| Dot | the default | drawn |
| Ada | docent-guide | rendered, animated |
| Dot, rendered | dot-rendered | rendered |
| Penguin | penguin-concierge | rendered |
| Owl | owl-docent | rendered |
| Fox | fox-scout | rendered |
| Corgi | corgi-usher | rendered |
| Cat | cat-curator | rendered |
| Otter | otter-host | rendered |
| Bear | bear-librarian | rendered |
| Raccoon | raccoon-archivist | rendered |
| Luna moth | lunamoth-guide | rendered |
| Axolotl | axolotl-greeter | rendered |
| Snail | snail-lamplighter | rendered |
| Mole | mole-tunneller | rendered |
Dot is drawn, in SVG and CSS. It tints to your brand, its pupils track the cursor, and its arm points at any angle.
The other fourteen are pre-rendered frames. Fixed colours, a facing side rather than a bearing, and no pupil tracking. Neither is better; they are different trades, and the Console's cards say which one each is making.
Loading one
The owner's choice arrives with the session mint, as a slug. Your job is one prop:
import { PRESENCE_LOADERS } from '@usedocent/mascot/library/loaders'
;<Docent
// ...
loadPresence={(slug) => PRESENCE_LOADERS[slug]?.() ?? Promise.reject(new Error(slug))}
/>It is injected rather than chosen by name inside the SDK so that an app showing one presence does
not ship the bytes of the other fourteen. PRESENCE_LOADERS is a chunk per presence and none is
fetched until a session names one.
Omit loadPresence and every session gets Dot, whatever the Console says. A presence that fails
to load is a mascot that stays Dot, not a session that never starts, and the failure arrives as
an error event with the code presence_failed.
You can also override the mascot outright with the presence prop, which takes palette and
mascot among other things. That wins over the owner's choice, so use it only when your app has
a reason to.
Voice
Voice is off unless you ask for it:
voice | What happens |
|---|---|
'off' (default) | Nothing opens a microphone. Text mode is a full peer and works on its own. |
'tap' | Voice starts on the user's own first tap of the presence, and never before it. |
Even with 'tap', the first attempt shows a consent prompt in the caption panel rather than
opening anything, and the browser's own microphone permission comes after that. Declining is not
an error: the guide keeps working by typing. The consent is remembered per app and per user, so
it is asked once rather than on every reload.
The microphone closes after each turn, and a visible indicator is shown while it is open. The overlay's mute control reaches the real call rather than only the mascot's face.
loadAudio is what makes any of this possible. Omit it and startVoice() rejects with
voice_unavailable:
loadAudio={(vendor) =>
vendor === 'openai' ? import('@usedocent/sdk-audio-live') : import('@usedocent/sdk-audio')
}The session grant names the vendor, so only the chunk that session was granted is ever fetched. Both are dynamic imports, so neither is in your bundle until somebody taps.
The live voices
A live session speaks in one of ten, chosen per environment in Console, Voice. The default is
marin.
marin (default) | cedar | alloy |
ash | ballad | coral |
echo | sage | shimmer |
verse |
marin and cedar are the two the vendor's own documentation recommends. The Console previews
each one so you can hear it before you save.
The Telnyx catalogue is the other tab on that page and is far larger. It is filtered: voices whose synthesis would leave the vendor's own infrastructure are not offered, because that is what keeps the retention promise on the privacy page true.
The two are stored separately, because the two vendors share no voice names.
