Assistant UI
Surfaces
A surface is a place where the conversation appears. There are four, and they all show the same conversation:
| Surface | What it is | Comes with |
|---|---|---|
| Corner widget | Your mark in a corner of the screen. Clicking it opens the drawer | TeaflaskAssistant |
| Drawer | The conversation in a panel. On wide screens the user can switch it between a floating card and a docked sidebar | TeaflaskAssistant |
| Command palette | A centered dialog over your page, opened with ⌘K or Ctrl+K | TeaflaskAssistant |
| Full page | The conversation filling a box on a route of your own, with a history menu | AssistantPage, or <teaflask-assistant-page> |
Most apps need only TeaflaskAssistant, mounted once as described in Install. Add the full page if you want a dedicated /help or /assistant route.
How surfaces share a conversation
Surfaces on a page share one live conversation when they have the same three things:
- the publishable key,
- the API address (
baseUrl), and - the signed-in user (
userId), or no user at all.
If any of the three differs, they're separate conversations. A reply streaming in the drawer also streams into an open full page, and the conversation stays live as long as at least one matching surface is mounted. That's why Install asks you to mount the widget in your app shell: when you open the full page on another route, the widget is still mounted and nothing reconnects.
Two consequences:
- Wire identity the same way on every surface. A widget with
userIdand a full page without it are two conversations, which looks to the user like the assistant forgot what they just said. - Surfaces share only within one copy of the assistant. An npm component and a script-tag element on the same page are two copies and two conversations.
Surfaces also make room for each other. While a full page is mounted, the corner widget hides. While the palette is open, the widget hides too.
The command palette
The palette opens with ⌘K on macOS and Ctrl+K elsewhere. If your app already uses that shortcut, change it or turn it off:
| You want | npm | Script tag |
|---|---|---|
| A different shortcut | hotkey="mod+j" |
hotkey="mod+j" |
| No shortcut | hotkey={false} |
hotkey="false" |
mod means ⌘ on macOS and Ctrl elsewhere. Any combination works, such as "ctrl+shift+p". A value the assistant can't read turns the shortcut off and logs a console warning. It doesn't fall back to ⌘K, because that's likely the shortcut you were trying to avoid. A key press that your own handler has already handled with preventDefault() is ignored.
To open the palette from your own button, call openPalette(). It works even when the shortcut is off. In React, it's on a ref to TeaflaskAssistant:
import { useRef } from "react";
import {
TeaflaskAssistant,
type TeaflaskAssistantHandle,
} from "@teaflask/assistant";
export function AssistantWithButton() {
const assistant = useRef<TeaflaskAssistantHandle>(null);
return (
<>
<button onClick={() => assistant.current?.openPalette()}>
Ask for help
</button>
<TeaflaskAssistant
ref={assistant}
publishableKey="pk_live_…"
hotkey={false}/>
</>
);
}
On the script tag, openPalette() is a method on the element. It's available once the element has loaded, so wait for that first:
await customElements.whenDefined("teaflask-assistant");
document.querySelector("teaflask-assistant").openPalette();
The corner widget and drawer
The widget sits in the bottom-right corner. Set corner to "bottom-left" to move it and its drawer. The drawer's floating and sidebar layouts are the user's choice, made in the drawer itself, so there's nothing for you to configure. Small screens always get the floating layout.
The drawer's welcome screen shows opening prompts. By default these are the prompts configured for your agent in Teaflask. Set suggestions to replace them. Any value replaces them entirely, including an empty list, which shows none:
<TeaflaskAssistant
publishableKey="pk_live_…"
suggestions={[
"How do I invite a teammate?",
{ prompt: "Review my billing settings", kind: "review" },
]}/>
A prompt is a string, or an object whose kind picks its icon: "ask" (the default), "review", "write" or "learn". On the script tag, suggestions is a property.
The full page
The full page fills whatever box you put it in. It has no height of its own, so give its container one. A container with no height shows nothing, and that's the most common mistake with this surface.
In React, render AssistantPage inside TeaflaskAssistant or inside a TeaflaskAssistantProvider. If your app shell already mounts TeaflaskAssistant, as Install recommends, the page needs a provider of its own on its route. Give it the same key and identity as the widget, so both resolve to the same conversation:
import {
AssistantPage,
TeaflaskAssistantProvider,
} from "@teaflask/assistant";
export function HelpRoute() {
return (
<TeaflaskAssistantProvider publishableKey="pk_live_…">
<div style={{ height: "calc(100dvh - 64px)" }}>
<AssistantPage title="Acme Help"/>
</div>
</TeaflaskAssistantProvider>
);
}
The 64px stands for your own header. Use whatever height your layout leaves for the page.
On the script tag, the loader you already added also defines the page element:
<div className="help-page">
<teaflask-assistant-page
publishable-key="pk_live_…"
heading="Acme Help"></teaflask-assistant-page>
</div>
Size .help-page in your stylesheet. On the element, the title attribute is called heading. That's because the standard HTML title attribute would show a tooltip over the whole page.
Full-page settings
| Setting | npm prop | Element | What it does |
|---|---|---|---|
| Title | title |
heading attribute |
The name in the page's header. Defaults to "Assistant" |
| Frameless | frameless |
frameless attribute |
Removes the page's border and rounded corners, so it sits flush inside a frame you already draw |
| Opening prompts | suggestions |
suggestions property |
Prompts under the empty message box. The full page shows none unless you set them |
| First message | initialPrompt |
— | Sent for the user when the page opens on an empty conversation, at most once per mount. Useful for deep links |
| Welcome heading | welcomeTitle |
— | The heading above the empty message box. Defaults to "Where should we begin?" |
| Welcome mark | welcomeMark |
— | Your artwork beside that heading |
| Your own header | renderChrome |
— | Replaces the page's header with controls you draw. Return a portal to put them in a bar you already have |
The full page has its own history menu, which opens from the title. History exists only for signed-in users, so anonymous visitors see no menu. See Identity.
The full page has no corner widget, palette or shortcut. Keep TeaflaskAssistant in your shell if you want those on other routes.
Composing surfaces yourself
TeaflaskAssistant is a provider plus the widget, drawer and palette. To place these pieces separately, mount TeaflaskAssistantProvider and render the ones you want inside it: AssistantCompanion for the corner widget and drawer, AssistantPalette for the palette (you control whether it's open), and AssistantPage. To replace the interface entirely, see Headless.
More resources
- Install – mounting the widget once in your app shell
- Identity – why every surface needs the same identity wiring to share a conversation
- Theming – theming the widget and the full page alike
- Headless – building your own interface instead of using these surfaces
- Transcript – what each surface shows as the conversation unfolds