@assistant-ui/mcp-docs-server 0.1.26 → 0.1.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.docs/organized/code-examples/waterfall.md +2 -2
- package/.docs/organized/code-examples/with-a2a.md +2 -2
- package/.docs/organized/code-examples/with-ag-ui.md +3 -3
- package/.docs/organized/code-examples/with-ai-sdk-v6.md +4 -4
- package/.docs/organized/code-examples/with-artifacts.md +4 -4
- package/.docs/organized/code-examples/with-assistant-transport.md +2 -2
- package/.docs/organized/code-examples/with-chain-of-thought.md +4 -4
- package/.docs/organized/code-examples/with-cloud-standalone.md +4 -4
- package/.docs/organized/code-examples/with-cloud.md +4 -4
- package/.docs/organized/code-examples/with-custom-thread-list.md +4 -4
- package/.docs/organized/code-examples/with-elevenlabs-conversational.md +511 -0
- package/.docs/organized/code-examples/with-elevenlabs-scribe.md +6 -6
- package/.docs/organized/code-examples/with-expo.md +17 -17
- package/.docs/organized/code-examples/with-external-store.md +2 -2
- package/.docs/organized/code-examples/with-ffmpeg.md +217 -63
- package/.docs/organized/code-examples/with-generative-ui.md +841 -0
- package/.docs/organized/code-examples/with-google-adk.md +3 -3
- package/.docs/organized/code-examples/with-heat-graph.md +2 -2
- package/.docs/organized/code-examples/with-interactables.md +67 -9
- package/.docs/organized/code-examples/with-langgraph.md +3 -3
- package/.docs/organized/code-examples/with-livekit.md +591 -0
- package/.docs/organized/code-examples/with-parent-id-grouping.md +3 -3
- package/.docs/organized/code-examples/with-react-hook-form.md +5 -5
- package/.docs/organized/code-examples/with-react-ink.md +1 -1
- package/.docs/organized/code-examples/with-react-router.md +7 -7
- package/.docs/organized/code-examples/with-store.md +8 -3
- package/.docs/organized/code-examples/with-tanstack.md +4 -4
- package/.docs/organized/code-examples/with-tap-runtime.md +2 -2
- package/.docs/raw/docs/(docs)/copilots/model-context.mdx +9 -1
- package/.docs/raw/docs/(docs)/guides/interactables.mdx +99 -37
- package/.docs/raw/docs/(docs)/guides/mentions.mdx +406 -0
- package/.docs/raw/docs/(docs)/guides/slash-commands.mdx +275 -0
- package/.docs/raw/docs/(docs)/guides/tool-ui.mdx +29 -0
- package/.docs/raw/docs/(docs)/guides/voice.mdx +333 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/message-part.mdx +23 -0
- package/.docs/raw/docs/primitives/composer.mdx +27 -4
- package/.docs/raw/docs/runtimes/a2a/index.mdx +4 -0
- package/.docs/raw/docs/runtimes/ai-sdk/v6.mdx +2 -2
- package/.docs/raw/docs/runtimes/assistant-transport.mdx +6 -2
- package/.docs/raw/docs/ui/context-display.mdx +2 -2
- package/.docs/raw/docs/ui/model-selector.mdx +1 -1
- package/.docs/raw/docs/ui/voice.mdx +172 -0
- package/package.json +5 -6
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Mentions
|
|
3
|
+
description: Let users @-mention tools or custom items in the composer to guide the LLM.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Mentions let users type `@` in the composer to open a popover picker, select an item (e.g. a tool), and insert a directive into the message text. The LLM can then use the directive as a hint.
|
|
7
|
+
|
|
8
|
+
## How It Works
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
User types "@" → Trigger detected → Adapter provides categories/items
|
|
12
|
+
↓
|
|
13
|
+
Directive inserted ← User selects item from popover
|
|
14
|
+
↓
|
|
15
|
+
Message sent with ":tool[Label]{name=id}" in text
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The mention system has three layers:
|
|
19
|
+
|
|
20
|
+
1. **Trigger detection** — watches the composer text for a trigger character (`@` by default) and extracts the query
|
|
21
|
+
2. **Adapter** — provides the categories and items to display in the popover (e.g. registered tools)
|
|
22
|
+
3. **Formatter** — serializes a selected item into directive text (`:type[label]{name=id}`) and parses it back for rendering
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
The fastest path is the pre-built [Mention UI component](/docs/ui/mention), which wires everything together with a single shadcn component:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx shadcn@latest add "https://r.assistant-ui.com/composer-mention"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
See the [Mention UI guide](/docs/ui/mention) for setup steps.
|
|
33
|
+
|
|
34
|
+
The rest of this guide covers the underlying concepts and customization points.
|
|
35
|
+
|
|
36
|
+
## Mention Adapter
|
|
37
|
+
|
|
38
|
+
A `Unstable_MentionAdapter` provides the data for the popover. All methods are **synchronous** — use external state management (React Query, SWR, local state) for async data, then expose loaded results through the adapter.
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import type { Unstable_MentionAdapter } from "@assistant-ui/core";
|
|
42
|
+
|
|
43
|
+
const myAdapter: Unstable_MentionAdapter = {
|
|
44
|
+
categories() {
|
|
45
|
+
return [
|
|
46
|
+
{ id: "tools", label: "Tools" },
|
|
47
|
+
{ id: "users", label: "Users" },
|
|
48
|
+
];
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
categoryItems(categoryId) {
|
|
52
|
+
if (categoryId === "tools") {
|
|
53
|
+
return [
|
|
54
|
+
{ id: "search", type: "tool", label: "Search" },
|
|
55
|
+
{ id: "calculator", type: "tool", label: "Calculator" },
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
if (categoryId === "users") {
|
|
59
|
+
return [
|
|
60
|
+
{ id: "alice", type: "user", label: "Alice" },
|
|
61
|
+
{ id: "bob", type: "user", label: "Bob" },
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
return [];
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
// Optional — global search across all categories
|
|
68
|
+
search(query) {
|
|
69
|
+
const lower = query.toLowerCase();
|
|
70
|
+
const all = [
|
|
71
|
+
...this.categoryItems("tools"),
|
|
72
|
+
...this.categoryItems("users"),
|
|
73
|
+
];
|
|
74
|
+
return all.filter(
|
|
75
|
+
(item) =>
|
|
76
|
+
item.label.toLowerCase().includes(lower) ||
|
|
77
|
+
item.id.toLowerCase().includes(lower),
|
|
78
|
+
);
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Pass the adapter to `MentionRoot`:
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
<ComposerPrimitive.Unstable_MentionRoot adapter={myAdapter}>
|
|
87
|
+
<ComposerPrimitive.Root>
|
|
88
|
+
<ComposerPrimitive.Input placeholder="Type @ to mention..." />
|
|
89
|
+
</ComposerPrimitive.Root>
|
|
90
|
+
</ComposerPrimitive.Unstable_MentionRoot>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Built-in Tool Adapter
|
|
94
|
+
|
|
95
|
+
For the common case of mentioning registered tools, use `unstable_useToolMentionAdapter`:
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
import { unstable_useToolMentionAdapter } from "@assistant-ui/react";
|
|
99
|
+
|
|
100
|
+
const adapter = unstable_useToolMentionAdapter({
|
|
101
|
+
// Format tool names for display (default: raw name)
|
|
102
|
+
formatLabel: (name) =>
|
|
103
|
+
name.replaceAll("_", " ").replace(/\b\w/g, (c) => c.toUpperCase()),
|
|
104
|
+
|
|
105
|
+
// Custom category label (default: "Tools")
|
|
106
|
+
categoryLabel: "Tools",
|
|
107
|
+
|
|
108
|
+
// Explicit tool list (overrides model context tools)
|
|
109
|
+
// tools: [{ id: "search", type: "tool", label: "Search" }],
|
|
110
|
+
|
|
111
|
+
// Include model context tools alongside explicit tools
|
|
112
|
+
// includeModelContextTools: true,
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The adapter automatically reads tools from the model context (registered via `Tools()` or `useAssistantTool`). When `tools` is provided, model context tools are excluded unless `includeModelContextTools` is set to `true`.
|
|
117
|
+
|
|
118
|
+
## Directive Format
|
|
119
|
+
|
|
120
|
+
When a user selects a mention item, it is serialized into the composer text as a **directive**. The default format is:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
:type[label]{name=id}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
For example, selecting a tool named "get_weather" with label "Get Weather" produces:
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
:tool[Get Weather]{name=get_weather}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
When `id` equals `label`, the `{name=…}` attribute is omitted for brevity:
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
:tool[search]
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Custom Formatter
|
|
139
|
+
|
|
140
|
+
Implement `Unstable_DirectiveFormatter` to use a different format:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import type { Unstable_DirectiveFormatter } from "@assistant-ui/core";
|
|
144
|
+
|
|
145
|
+
const slashFormatter: Unstable_DirectiveFormatter = {
|
|
146
|
+
serialize(item) {
|
|
147
|
+
return `/${item.id}`;
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
parse(text) {
|
|
151
|
+
const segments = [];
|
|
152
|
+
const re = /\/(\w+)/g;
|
|
153
|
+
let lastIndex = 0;
|
|
154
|
+
let match;
|
|
155
|
+
|
|
156
|
+
while ((match = re.exec(text)) !== null) {
|
|
157
|
+
if (match.index > lastIndex) {
|
|
158
|
+
segments.push({ kind: "text" as const, text: text.slice(lastIndex, match.index) });
|
|
159
|
+
}
|
|
160
|
+
segments.push({
|
|
161
|
+
kind: "mention" as const,
|
|
162
|
+
type: "tool",
|
|
163
|
+
label: match[1]!,
|
|
164
|
+
id: match[1]!,
|
|
165
|
+
});
|
|
166
|
+
lastIndex = re.lastIndex;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (lastIndex < text.length) {
|
|
170
|
+
segments.push({ kind: "text" as const, text: text.slice(lastIndex) });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return segments;
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Pass it to both the mention root and the message renderer:
|
|
179
|
+
|
|
180
|
+
```tsx
|
|
181
|
+
// Composer
|
|
182
|
+
<ComposerPrimitive.Unstable_MentionRoot adapter={adapter} formatter={slashFormatter}>
|
|
183
|
+
...
|
|
184
|
+
</ComposerPrimitive.Unstable_MentionRoot>
|
|
185
|
+
|
|
186
|
+
// User messages
|
|
187
|
+
const SlashDirectiveText = createDirectiveText(slashFormatter);
|
|
188
|
+
<MessagePrimitive.Parts components={{ Text: SlashDirectiveText }} />
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Textarea vs Lexical
|
|
192
|
+
|
|
193
|
+
The mention system supports two input modes:
|
|
194
|
+
|
|
195
|
+
| | Textarea (default) | Lexical |
|
|
196
|
+
| --- | --- | --- |
|
|
197
|
+
| **Input component** | `ComposerPrimitive.Input` | `LexicalComposerInput` |
|
|
198
|
+
| **Mention display in composer** | Raw directive text (`:tool[Label]`) | Inline chips (atomic nodes) |
|
|
199
|
+
| **Dependencies** | None | `@assistant-ui/react-lexical`, `lexical`, `@lexical/react` |
|
|
200
|
+
| **Best for** | Simple setups, minimal bundle | Rich editing, polished UX |
|
|
201
|
+
|
|
202
|
+
With **textarea**, selecting a mention inserts the directive string directly into the text. The user sees `:tool[Get Weather]{name=get_weather}` in the input.
|
|
203
|
+
|
|
204
|
+
With **Lexical**, selected mentions appear as styled inline chips that behave as atomic units — they can be selected, deleted, and undone as a whole. The underlying text still uses the directive format.
|
|
205
|
+
|
|
206
|
+
```tsx
|
|
207
|
+
import { LexicalComposerInput } from "@assistant-ui/react-lexical";
|
|
208
|
+
|
|
209
|
+
<ComposerPrimitive.Unstable_MentionRoot adapter={adapter}>
|
|
210
|
+
<ComposerPrimitive.Root>
|
|
211
|
+
<LexicalComposerInput placeholder="Type @ to mention..." />
|
|
212
|
+
<ComposerPrimitive.Send />
|
|
213
|
+
</ComposerPrimitive.Root>
|
|
214
|
+
</ComposerPrimitive.Unstable_MentionRoot>
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
`LexicalComposerInput` auto-wires to the mention context — no extra props needed.
|
|
218
|
+
|
|
219
|
+
## Rendering Mentions in Messages
|
|
220
|
+
|
|
221
|
+
Use `DirectiveText` as the `Text` component for user messages so directives render as inline chips instead of raw syntax:
|
|
222
|
+
|
|
223
|
+
```tsx
|
|
224
|
+
import { DirectiveText } from "@/components/assistant-ui/composer-mention";
|
|
225
|
+
|
|
226
|
+
<MessagePrimitive.Parts
|
|
227
|
+
components={{
|
|
228
|
+
Text: DirectiveText,
|
|
229
|
+
}}
|
|
230
|
+
/>
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
For assistant messages, keep using your markdown renderer (e.g. `MarkdownText`) — the LLM typically does not emit directive syntax.
|
|
234
|
+
|
|
235
|
+
For a custom formatter, use `createDirectiveText`:
|
|
236
|
+
|
|
237
|
+
```tsx
|
|
238
|
+
import { createDirectiveText } from "@/components/assistant-ui/composer-mention";
|
|
239
|
+
|
|
240
|
+
const MyDirectiveText = createDirectiveText(myFormatter);
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Processing Mentions on the Backend
|
|
244
|
+
|
|
245
|
+
The message text arrives at your backend with directives inline. Parse them to extract mentioned items:
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
// Default format: :type[label]{name=id}
|
|
249
|
+
const DIRECTIVE_RE = /:([\w-]+)\[([^\]]+)\](?:\{name=([^}]+)\})?/g;
|
|
250
|
+
|
|
251
|
+
function parseMentions(text: string) {
|
|
252
|
+
const mentions = [];
|
|
253
|
+
let match;
|
|
254
|
+
while ((match = DIRECTIVE_RE.exec(text)) !== null) {
|
|
255
|
+
mentions.push({
|
|
256
|
+
type: match[1], // e.g. "tool"
|
|
257
|
+
label: match[2], // e.g. "Get Weather"
|
|
258
|
+
id: match[3] ?? match[2], // e.g. "get_weather"
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
return mentions;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Example:
|
|
265
|
+
// parseMentions("Use :tool[Get Weather]{name=get_weather} to check")
|
|
266
|
+
// → [{ type: "tool", label: "Get Weather", id: "get_weather" }]
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
You can use the extracted mentions to:
|
|
270
|
+
- Force-enable specific tools for the LLM call
|
|
271
|
+
- Add context about mentioned users or documents to the system prompt
|
|
272
|
+
- Log which tools users request most often
|
|
273
|
+
|
|
274
|
+
## Reading Mention State
|
|
275
|
+
|
|
276
|
+
Use `unstable_useMentionContext` to programmatically access the mention popover state:
|
|
277
|
+
|
|
278
|
+
```tsx
|
|
279
|
+
import { unstable_useMentionContext } from "@assistant-ui/react";
|
|
280
|
+
|
|
281
|
+
function MyComponent() {
|
|
282
|
+
const mention = unstable_useMentionContext();
|
|
283
|
+
|
|
284
|
+
// mention.open — whether the popover is visible
|
|
285
|
+
// mention.query — current search text after "@"
|
|
286
|
+
// mention.categories — filtered category list
|
|
287
|
+
// mention.items — filtered item list
|
|
288
|
+
// mention.highlightedIndex — keyboard-navigated index
|
|
289
|
+
// mention.isSearchMode — true when global search is active
|
|
290
|
+
// mention.selectItem(item) — programmatically select an item
|
|
291
|
+
// mention.close() — close the popover
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
This hook must be used within a `ComposerPrimitive.Unstable_MentionRoot`.
|
|
296
|
+
|
|
297
|
+
## Building a Custom Popover
|
|
298
|
+
|
|
299
|
+
Use the mention primitives to build a fully custom popover:
|
|
300
|
+
|
|
301
|
+
```tsx
|
|
302
|
+
<ComposerPrimitive.Unstable_MentionRoot adapter={adapter}>
|
|
303
|
+
<ComposerPrimitive.Root>
|
|
304
|
+
<ComposerPrimitive.Input />
|
|
305
|
+
|
|
306
|
+
<ComposerPrimitive.Unstable_MentionPopover className="popover">
|
|
307
|
+
<ComposerPrimitive.Unstable_MentionBack>
|
|
308
|
+
← Back
|
|
309
|
+
</ComposerPrimitive.Unstable_MentionBack>
|
|
310
|
+
|
|
311
|
+
<ComposerPrimitive.Unstable_MentionCategories>
|
|
312
|
+
{(categories) =>
|
|
313
|
+
categories.map((cat) => (
|
|
314
|
+
<ComposerPrimitive.Unstable_MentionCategoryItem
|
|
315
|
+
key={cat.id}
|
|
316
|
+
categoryId={cat.id}
|
|
317
|
+
>
|
|
318
|
+
{cat.label}
|
|
319
|
+
</ComposerPrimitive.Unstable_MentionCategoryItem>
|
|
320
|
+
))
|
|
321
|
+
}
|
|
322
|
+
</ComposerPrimitive.Unstable_MentionCategories>
|
|
323
|
+
|
|
324
|
+
<ComposerPrimitive.Unstable_MentionItems>
|
|
325
|
+
{(items) =>
|
|
326
|
+
items.map((item) => (
|
|
327
|
+
<ComposerPrimitive.Unstable_MentionItem
|
|
328
|
+
key={item.id}
|
|
329
|
+
item={item}
|
|
330
|
+
>
|
|
331
|
+
{item.label}
|
|
332
|
+
</ComposerPrimitive.Unstable_MentionItem>
|
|
333
|
+
))
|
|
334
|
+
}
|
|
335
|
+
</ComposerPrimitive.Unstable_MentionItems>
|
|
336
|
+
</ComposerPrimitive.Unstable_MentionPopover>
|
|
337
|
+
|
|
338
|
+
<ComposerPrimitive.Send />
|
|
339
|
+
</ComposerPrimitive.Root>
|
|
340
|
+
</ComposerPrimitive.Unstable_MentionRoot>
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Primitives Reference
|
|
344
|
+
|
|
345
|
+
| Primitive | Description |
|
|
346
|
+
| --- | --- |
|
|
347
|
+
| `Unstable_MentionRoot` | Provider — wraps the composer with trigger detection, keyboard navigation, and popover state |
|
|
348
|
+
| `Unstable_MentionPopover` | Container — only renders when a trigger is active (`role="listbox"`) |
|
|
349
|
+
| `Unstable_MentionCategories` | Render-function for the top-level category list |
|
|
350
|
+
| `Unstable_MentionCategoryItem` | Button that drills into a category (`role="option"`, auto `data-highlighted`) |
|
|
351
|
+
| `Unstable_MentionItems` | Render-function for items within the active category or search results |
|
|
352
|
+
| `Unstable_MentionItem` | Button that inserts a mention (`role="option"`, auto `data-highlighted`) |
|
|
353
|
+
| `Unstable_MentionBack` | Button that navigates back from items to categories |
|
|
354
|
+
|
|
355
|
+
See the [Composer API reference](/docs/api-reference/primitives/composer) for full prop details.
|
|
356
|
+
|
|
357
|
+
## Combining with Slash Commands
|
|
358
|
+
|
|
359
|
+
Mentions and [slash commands](/docs/guides/slash-commands) can coexist on the same composer. Both are built on the same [trigger popover architecture](/docs/guides/slash-commands#trigger-popover-architecture) — nest both roots and they work independently:
|
|
360
|
+
|
|
361
|
+
```tsx
|
|
362
|
+
<ComposerPrimitive.Unstable_MentionRoot adapter={mentionAdapter}>
|
|
363
|
+
<ComposerPrimitive.Unstable_SlashCommandRoot adapter={slashAdapter}>
|
|
364
|
+
<ComposerPrimitive.Root>
|
|
365
|
+
<ComposerPrimitive.Input placeholder="Type @ to mention, / for commands..." />
|
|
366
|
+
<ComposerPrimitive.Send />
|
|
367
|
+
|
|
368
|
+
{/* Mention popover (shows on @) */}
|
|
369
|
+
<ComposerPrimitive.Unstable_MentionPopover>
|
|
370
|
+
<ComposerPrimitive.Unstable_MentionCategories>
|
|
371
|
+
{(categories) => categories.map((cat) => (
|
|
372
|
+
<ComposerPrimitive.Unstable_MentionCategoryItem key={cat.id} categoryId={cat.id}>
|
|
373
|
+
{cat.label}
|
|
374
|
+
</ComposerPrimitive.Unstable_MentionCategoryItem>
|
|
375
|
+
))}
|
|
376
|
+
</ComposerPrimitive.Unstable_MentionCategories>
|
|
377
|
+
<ComposerPrimitive.Unstable_MentionItems>
|
|
378
|
+
{(items) => items.map((item) => (
|
|
379
|
+
<ComposerPrimitive.Unstable_MentionItem key={item.id} item={item}>
|
|
380
|
+
{item.label}
|
|
381
|
+
</ComposerPrimitive.Unstable_MentionItem>
|
|
382
|
+
))}
|
|
383
|
+
</ComposerPrimitive.Unstable_MentionItems>
|
|
384
|
+
</ComposerPrimitive.Unstable_MentionPopover>
|
|
385
|
+
|
|
386
|
+
{/* Slash command popover (shows on /) */}
|
|
387
|
+
<ComposerPrimitive.Unstable_TriggerPopoverPopover>
|
|
388
|
+
<ComposerPrimitive.Unstable_TriggerPopoverItems>
|
|
389
|
+
{(items) => items.map((item, index) => (
|
|
390
|
+
<ComposerPrimitive.Unstable_TriggerPopoverItem key={item.id} item={item} index={index}>
|
|
391
|
+
{item.label}
|
|
392
|
+
</ComposerPrimitive.Unstable_TriggerPopoverItem>
|
|
393
|
+
))}
|
|
394
|
+
</ComposerPrimitive.Unstable_TriggerPopoverItems>
|
|
395
|
+
</ComposerPrimitive.Unstable_TriggerPopoverPopover>
|
|
396
|
+
</ComposerPrimitive.Root>
|
|
397
|
+
</ComposerPrimitive.Unstable_SlashCommandRoot>
|
|
398
|
+
</ComposerPrimitive.Unstable_MentionRoot>
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
## Related
|
|
402
|
+
|
|
403
|
+
- [Mention UI Component](/docs/ui/mention) — pre-built shadcn component
|
|
404
|
+
- [Slash Commands Guide](/docs/guides/slash-commands) — `/` command system built on the same architecture
|
|
405
|
+
- [Tools Guide](/docs/guides/tools) — register tools that appear in the mention picker
|
|
406
|
+
- [Composer Primitives](/docs/primitives/composer) — underlying composer primitives
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Slash Commands
|
|
3
|
+
description: Let users type / in the composer to trigger predefined actions from a popover picker.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Slash commands let users type `/` in the composer to open a popover, browse available commands, and execute one. Unlike [mentions](/docs/guides/mentions) (which insert text into the message), slash commands trigger **actions** — the `/command` text is removed from the composer and a callback fires.
|
|
7
|
+
|
|
8
|
+
## How It Works
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
User types "/" → Trigger detected → Adapter provides commands
|
|
12
|
+
↓
|
|
13
|
+
Command executed ← User selects command from popover
|
|
14
|
+
↓
|
|
15
|
+
"/command" text removed from composer
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The slash command system is built on the same [trigger popover architecture](#trigger-popover-architecture) as mentions. It has two layers:
|
|
19
|
+
|
|
20
|
+
1. **Adapter** — provides the list of available commands (flat list by default, or categorized for advanced use)
|
|
21
|
+
2. **SlashCommandRoot** — a convenience wrapper that pre-configures the trigger character (`/`) and action-based select behavior
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
### 1. Define Commands
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import {
|
|
29
|
+
unstable_useSlashCommandAdapter,
|
|
30
|
+
ComposerPrimitive,
|
|
31
|
+
} from "@assistant-ui/react";
|
|
32
|
+
|
|
33
|
+
// Define commands outside the component for a stable reference
|
|
34
|
+
const COMMANDS = [
|
|
35
|
+
{
|
|
36
|
+
name: "summarize",
|
|
37
|
+
description: "Summarize the conversation",
|
|
38
|
+
execute: () => console.log("Summarize!"),
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: "translate",
|
|
42
|
+
description: "Translate text to another language",
|
|
43
|
+
execute: () => console.log("Translate!"),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: "help",
|
|
47
|
+
description: "List all available commands",
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
function MyComposer() {
|
|
52
|
+
const slashAdapter = unstable_useSlashCommandAdapter({
|
|
53
|
+
commands: COMMANDS,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<ComposerPrimitive.Unstable_SlashCommandRoot adapter={slashAdapter}>
|
|
58
|
+
<ComposerPrimitive.Root>
|
|
59
|
+
<ComposerPrimitive.Input placeholder="Type / for commands..." />
|
|
60
|
+
<ComposerPrimitive.Send>Send</ComposerPrimitive.Send>
|
|
61
|
+
|
|
62
|
+
<ComposerPrimitive.Unstable_TriggerPopoverPopover className="popover">
|
|
63
|
+
<ComposerPrimitive.Unstable_TriggerPopoverItems>
|
|
64
|
+
{(items) =>
|
|
65
|
+
items.map((item, index) => (
|
|
66
|
+
<ComposerPrimitive.Unstable_TriggerPopoverItem
|
|
67
|
+
key={item.id}
|
|
68
|
+
item={item}
|
|
69
|
+
index={index}
|
|
70
|
+
className="popover-item"
|
|
71
|
+
>
|
|
72
|
+
<strong>{item.label}</strong>
|
|
73
|
+
{item.description && <span>{item.description}</span>}
|
|
74
|
+
</ComposerPrimitive.Unstable_TriggerPopoverItem>
|
|
75
|
+
))
|
|
76
|
+
}
|
|
77
|
+
</ComposerPrimitive.Unstable_TriggerPopoverItems>
|
|
78
|
+
</ComposerPrimitive.Unstable_TriggerPopoverPopover>
|
|
79
|
+
</ComposerPrimitive.Root>
|
|
80
|
+
</ComposerPrimitive.Unstable_SlashCommandRoot>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 2. Handle Command Selection
|
|
86
|
+
|
|
87
|
+
There are two ways to handle command execution:
|
|
88
|
+
|
|
89
|
+
**Via `execute` on each item** — define the action inline in the command definition:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
{ name: "summarize", execute: () => runSummarize() }
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Via `onSelect` prop** — handle all commands in one place:
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
<ComposerPrimitive.Unstable_SlashCommandRoot
|
|
99
|
+
adapter={slashAdapter}
|
|
100
|
+
onSelect={(item) => {
|
|
101
|
+
switch (item.id) {
|
|
102
|
+
case "summarize": runSummarize(); break;
|
|
103
|
+
case "translate": runTranslate(); break;
|
|
104
|
+
}
|
|
105
|
+
}}
|
|
106
|
+
>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Both `execute` and `onSelect` fire when a command is selected. Use whichever pattern fits your code.
|
|
110
|
+
|
|
111
|
+
## Custom Adapter
|
|
112
|
+
|
|
113
|
+
The `unstable_useSlashCommandAdapter` hook uses a **flat list** — all commands show immediately when `/` is typed, with search filtering as the user types. This is the recommended UX for most cases.
|
|
114
|
+
|
|
115
|
+
For **categorized navigation** (drill-down into groups), build the adapter manually. Return categories from `categories()` and items from `categoryItems()`. The popover will show categories first, then items within the selected category:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import type { Unstable_SlashCommandAdapter } from "@assistant-ui/core";
|
|
119
|
+
|
|
120
|
+
const adapter: Unstable_SlashCommandAdapter = {
|
|
121
|
+
categories() {
|
|
122
|
+
return [
|
|
123
|
+
{ id: "actions", label: "Actions" },
|
|
124
|
+
{ id: "export", label: "Export" },
|
|
125
|
+
];
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
categoryItems(categoryId) {
|
|
129
|
+
if (categoryId === "actions") {
|
|
130
|
+
return [
|
|
131
|
+
{ id: "summarize", type: "command", label: "/summarize", description: "Summarize the conversation" },
|
|
132
|
+
{ id: "translate", type: "command", label: "/translate", description: "Translate text" },
|
|
133
|
+
];
|
|
134
|
+
}
|
|
135
|
+
if (categoryId === "export") {
|
|
136
|
+
return [
|
|
137
|
+
{ id: "pdf", type: "command", label: "/export pdf", description: "Export as PDF" },
|
|
138
|
+
{ id: "markdown", type: "command", label: "/export md", description: "Export as Markdown" },
|
|
139
|
+
];
|
|
140
|
+
}
|
|
141
|
+
return [];
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
// Optional — enables search across all categories
|
|
145
|
+
search(query) {
|
|
146
|
+
const lower = query.toLowerCase();
|
|
147
|
+
const all = [...this.categoryItems("actions"), ...this.categoryItems("export")];
|
|
148
|
+
return all.filter(
|
|
149
|
+
(item) => item.label.toLowerCase().includes(lower) || item.description?.toLowerCase().includes(lower),
|
|
150
|
+
);
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
When using a categorized adapter, add `TriggerPopoverCategories` to your popover UI:
|
|
156
|
+
|
|
157
|
+
```tsx
|
|
158
|
+
<ComposerPrimitive.Unstable_TriggerPopoverPopover>
|
|
159
|
+
<ComposerPrimitive.Unstable_TriggerPopoverBack>← Back</ComposerPrimitive.Unstable_TriggerPopoverBack>
|
|
160
|
+
<ComposerPrimitive.Unstable_TriggerPopoverCategories>
|
|
161
|
+
{(categories) => categories.map((cat) => (
|
|
162
|
+
<ComposerPrimitive.Unstable_TriggerPopoverCategoryItem key={cat.id} categoryId={cat.id}>
|
|
163
|
+
{cat.label}
|
|
164
|
+
</ComposerPrimitive.Unstable_TriggerPopoverCategoryItem>
|
|
165
|
+
))}
|
|
166
|
+
</ComposerPrimitive.Unstable_TriggerPopoverCategories>
|
|
167
|
+
<ComposerPrimitive.Unstable_TriggerPopoverItems>
|
|
168
|
+
{(items) => items.map((item, index) => (
|
|
169
|
+
<ComposerPrimitive.Unstable_TriggerPopoverItem key={item.id} item={item} index={index}>
|
|
170
|
+
{item.label}
|
|
171
|
+
</ComposerPrimitive.Unstable_TriggerPopoverItem>
|
|
172
|
+
))}
|
|
173
|
+
</ComposerPrimitive.Unstable_TriggerPopoverItems>
|
|
174
|
+
</ComposerPrimitive.Unstable_TriggerPopoverPopover>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Combining with Mentions
|
|
178
|
+
|
|
179
|
+
Slash commands and mentions can coexist on the same composer. Nest both roots — the [plugin protocol](#trigger-popover-architecture) ensures they don't conflict:
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
<ComposerPrimitive.Unstable_MentionRoot adapter={mentionAdapter}>
|
|
183
|
+
<ComposerPrimitive.Unstable_SlashCommandRoot adapter={slashAdapter}>
|
|
184
|
+
<ComposerPrimitive.Root>
|
|
185
|
+
<ComposerPrimitive.Input placeholder="Type @ to mention, / for commands..." />
|
|
186
|
+
<ComposerPrimitive.Send>Send</ComposerPrimitive.Send>
|
|
187
|
+
|
|
188
|
+
{/* Mention popover — shows when @ is typed */}
|
|
189
|
+
<ComposerPrimitive.Unstable_MentionPopover>
|
|
190
|
+
<ComposerPrimitive.Unstable_MentionCategories>
|
|
191
|
+
{(categories) => categories.map((cat) => (
|
|
192
|
+
<ComposerPrimitive.Unstable_MentionCategoryItem key={cat.id} categoryId={cat.id}>
|
|
193
|
+
{cat.label}
|
|
194
|
+
</ComposerPrimitive.Unstable_MentionCategoryItem>
|
|
195
|
+
))}
|
|
196
|
+
</ComposerPrimitive.Unstable_MentionCategories>
|
|
197
|
+
<ComposerPrimitive.Unstable_MentionItems>
|
|
198
|
+
{(items) => items.map((item) => (
|
|
199
|
+
<ComposerPrimitive.Unstable_MentionItem key={item.id} item={item}>
|
|
200
|
+
{item.label}
|
|
201
|
+
</ComposerPrimitive.Unstable_MentionItem>
|
|
202
|
+
))}
|
|
203
|
+
</ComposerPrimitive.Unstable_MentionItems>
|
|
204
|
+
</ComposerPrimitive.Unstable_MentionPopover>
|
|
205
|
+
|
|
206
|
+
{/* Slash command popover — shows when / is typed */}
|
|
207
|
+
<ComposerPrimitive.Unstable_TriggerPopoverPopover>
|
|
208
|
+
<ComposerPrimitive.Unstable_TriggerPopoverItems>
|
|
209
|
+
{(items) => items.map((item, index) => (
|
|
210
|
+
<ComposerPrimitive.Unstable_TriggerPopoverItem key={item.id} item={item} index={index}>
|
|
211
|
+
{item.label}
|
|
212
|
+
</ComposerPrimitive.Unstable_TriggerPopoverItem>
|
|
213
|
+
))}
|
|
214
|
+
</ComposerPrimitive.Unstable_TriggerPopoverItems>
|
|
215
|
+
</ComposerPrimitive.Unstable_TriggerPopoverPopover>
|
|
216
|
+
</ComposerPrimitive.Root>
|
|
217
|
+
</ComposerPrimitive.Unstable_SlashCommandRoot>
|
|
218
|
+
</ComposerPrimitive.Unstable_MentionRoot>
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Each root provides its own `TriggerPopoverContext`. When the user types `@`, the mention popover opens. When they type `/`, the slash command popover opens. Keyboard events route to whichever popover is active.
|
|
222
|
+
|
|
223
|
+
## Keyboard Navigation
|
|
224
|
+
|
|
225
|
+
Same keyboard bindings as mentions:
|
|
226
|
+
|
|
227
|
+
| Key | Action |
|
|
228
|
+
| --- | --- |
|
|
229
|
+
| <Kbd>ArrowDown</Kbd> | Highlight next item |
|
|
230
|
+
| <Kbd>ArrowUp</Kbd> | Highlight previous item |
|
|
231
|
+
| <Kbd>Enter</Kbd> | Execute highlighted command / drill into category |
|
|
232
|
+
| <Kbd>Escape</Kbd> | Close popover |
|
|
233
|
+
| <Kbd>Backspace</Kbd> | Go back to categories (when query is empty) |
|
|
234
|
+
|
|
235
|
+
## Trigger Popover Architecture
|
|
236
|
+
|
|
237
|
+
Both mentions and slash commands are built on a generic **trigger popover** system:
|
|
238
|
+
|
|
239
|
+
- `ComposerPrimitive.Unstable_TriggerPopoverRoot` — the generic root, parameterized by trigger character and select behavior
|
|
240
|
+
- `ComposerPrimitive.Unstable_MentionRoot` — preset with `trigger="@"` and `onSelect: insertDirective`
|
|
241
|
+
- `ComposerPrimitive.Unstable_SlashCommandRoot` — preset with `trigger="/"` and `onSelect: action`
|
|
242
|
+
|
|
243
|
+
The trigger popover primitives (`TriggerPopoverPopover`, `TriggerPopoverItems`, etc.) are shared across both. You can also use `TriggerPopoverRoot` directly to build custom trigger systems with other characters (e.g. `:` for emoji).
|
|
244
|
+
|
|
245
|
+
### ComposerInput Plugin Protocol
|
|
246
|
+
|
|
247
|
+
Under the hood, each trigger root registers a **ComposerInputPlugin** with the composer input. This is a generic protocol that decouples the input from any specific trigger:
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
type ComposerInputPlugin = {
|
|
251
|
+
handleKeyDown(e: KeyboardEvent): boolean;
|
|
252
|
+
setCursorPosition(pos: number): void;
|
|
253
|
+
};
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
The input iterates over registered plugins for keyboard events and cursor changes. This is what enables multiple trigger roots to coexist without conflict.
|
|
257
|
+
|
|
258
|
+
## Primitives Reference
|
|
259
|
+
|
|
260
|
+
| Primitive | Description |
|
|
261
|
+
| --- | --- |
|
|
262
|
+
| `Unstable_SlashCommandRoot` | Convenience wrapper — `TriggerPopoverRoot` with `trigger="/"` and action behavior |
|
|
263
|
+
| `Unstable_TriggerPopoverRoot` | Generic root — configurable trigger character and select behavior |
|
|
264
|
+
| `Unstable_TriggerPopoverPopover` | Container — only renders when a trigger is active (`role="listbox"`) |
|
|
265
|
+
| `Unstable_TriggerPopoverCategories` | Render-function for the top-level category list |
|
|
266
|
+
| `Unstable_TriggerPopoverCategoryItem` | Button that drills into a category (`role="option"`, auto `data-highlighted`) |
|
|
267
|
+
| `Unstable_TriggerPopoverItems` | Render-function for items within the active category or search results |
|
|
268
|
+
| `Unstable_TriggerPopoverItem` | Button that selects an item (`role="option"`, auto `data-highlighted`) |
|
|
269
|
+
| `Unstable_TriggerPopoverBack` | Button that navigates back from items to categories |
|
|
270
|
+
|
|
271
|
+
## Related
|
|
272
|
+
|
|
273
|
+
- [Mentions Guide](/docs/guides/mentions) — `@`-mention system built on the same architecture
|
|
274
|
+
- [Suggestions Guide](/docs/guides/suggestions) — static follow-up prompts (different from slash commands)
|
|
275
|
+
- [Composer Primitives](/docs/primitives/composer) — underlying composer primitives
|