TemplateDialog
Create or edit an agent template: a name, what it should do, who can use it.
Part of
@aora-bot/surfaces, not@aora-bot/react:pnpm add @aora-bot/surfaces @aora-bot/react, and import@aora-bot/surfaces/styles.cssafter the React stylesheet.
Press Save. The character beside the name works while the promise is out, celebrates when it resolves, and a share card appears once shareUrl is given.
Appearance is not a field
The form is deliberately short because the appearance is not something anyone has to fill in. normalizeTemplateDraft gives an unconfigured template a shape and an ink derived from seed — use the stored template's id, which does not change when the template is renamed — so the character beside the name is never a placeholder and a list of ten unconfigured templates is still ten distinguishable rows. Opening the avatar editor from the trigger is an optional flourish, not a step.
Saving
onSave receives the normalised draft. Return a promise and the dialog's usePublishMark does the rest: working while it is out, celebrate with confetti when it resolves, back to rest when it rejects — the form is about to show its own failure line, and a sad face next to it would read as the library editorialising.
normalizeTemplateDraft
Pure, so a server action can validate a submission with the same call the dialog uses on every keystroke. It returns three things:
draft— trimmed, truncated toTEMPLATE_LIMITS(80 characters for the name, 4000 for the instructions), with an appearance filled in. This is what a live preview should render, so the character and the row behave while a field is still over its limit.errors—name-required,name-too-long,instructions-too-long. This is what the Save button should respect.TEMPLATE_ERROR_KEYSmaps each to a locale key.valid—errors.length === 0.
The name is whitespace-collapsed because a name pasted from a document arrives with a newline in it; instructions lose only trailing whitespace, because leading indentation in a pasted list is part of its meaning.
VisibilityControls and ShareCard
Visibility is two labelled choices rather than a switch: a switch needs one label that stays true in both positions, and the honest labels here — "only you" and "everyone in your workspace" — are two different sentences. disabled turns it into a read-only display for a template someone else owns.
The share card shows the link in full rather than hiding it behind the copy button, so what is about to be handed out is visible before it is copied. onCopy fires after a successful copy for a host that wants its own toast.
API
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | — | — |
face | "cute" | "eyes" | "line" | — | — |
generateImage | ((prompt: string, signal: AbortSignal) => Promise<string>) | — | Passed to the avatar editor. Without it the Generate tab is hidden. |
lineWeight | number | — | — |
nose | "slant" | "angular" | "hook" | "long" | "curve" | "bow" | null | — | — |
onCancel | (() => void) | — | — |
onSave | ((draft: TemplateDraft) => void | Promise<void>) | — | Save the template. An async result drives the mark: it works while the promise is out and celebrates when it resolves. |
seed | string | — | Key for the derived appearance. Use the stored template's id. |
shareUrl | string | — | Show a share card under the form once the template has somewhere to live. |
sizePx | number | TEMPLATE_MARK_PX | Size of the character beside the name, and of the editor's trigger. Defaults to `TEMPLATE_MARK_PX`. |
style | CSSProperties | — | — |
value | Partial<TemplateDraft> | — | The template being edited. Anything missing is filled in by normalisation. |
VisibilityControls
| Prop | Type | Default | Description |
|---|---|---|---|
value* | "private" | "team" | — | — |
className | string | — | — |
disabled | boolean | false | Read-only display, for a card that shows a template someone else owns. |
onSelect | ((value: TemplateVisibility) => void) | — | — |
style | CSSProperties | — | — |
ShareCard
| Prop | Type | Default | Description |
|---|---|---|---|
name* | string | — | — |
url* | string | — | The link to hand out. Shown in full and copied verbatim. |
className | string | — | — |
color | "black" | "brown" | "red" | "orange" | "yellow" | "green" | "cyan" | "blue" | "violet" | "magenta" | "pink" | "gray" | — | — |
face | "cute" | "eyes" | "line" | — | — |
lineWeight | number | — | — |
nose | "slant" | "angular" | "hook" | "long" | "curve" | "bow" | null | — | — |
onCopy | ((url: string) => void) | — | Called after a successful copy, for a host that wants its own toast. |
pictureUrl | string | null | — | — |
shape | "mochi" | "circle" | "blob" | "pebble" | "egg" | "bun" | "tofu" | "pill" | "hex" | "onigiri" | "gem" | "cloud" | "drop" | "leaf" | "flower" | "clover" | "sprout" | "mushroom" | "acorn" | "cactus" | "pear" | "apple" | "strawberry" | "cherry" | "star" | "heart" | "flame" | "bell" | "ghost" | "bot" | "crescent" | "boba" | "fish" | "octopus" | "cat" | "bunny" | "bear" | "chick" | "penguin" | "pig" | "fox" | "bob" | "topknot" | "spiky" | "curly" | "twinbuns" | — | — |
sizePx | number | 36 | — |
style | CSSProperties | — | — |
Next
- usePublishMark — work, celebrate, settle
- Character setup — choosing what an agent looks like