@assistant-ui/mcp-docs-server 0.1.22 → 0.1.23
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 +801 -0
- package/.docs/organized/code-examples/with-ag-ui.md +38 -26
- package/.docs/organized/code-examples/with-ai-sdk-v6.md +38 -28
- package/.docs/organized/code-examples/with-artifacts.md +467 -0
- package/.docs/organized/code-examples/with-assistant-transport.md +31 -24
- package/.docs/organized/code-examples/with-chain-of-thought.md +41 -32
- package/.docs/organized/code-examples/with-cloud-standalone.md +675 -0
- package/.docs/organized/code-examples/with-cloud.md +34 -27
- package/.docs/organized/code-examples/with-custom-thread-list.md +34 -27
- package/.docs/organized/code-examples/with-elevenlabs-scribe.md +41 -30
- package/.docs/organized/code-examples/with-expo.md +2031 -0
- package/.docs/organized/code-examples/with-external-store.md +32 -25
- package/.docs/organized/code-examples/with-ffmpeg.md +31 -27
- package/.docs/organized/code-examples/with-langgraph.md +96 -38
- package/.docs/organized/code-examples/with-parent-id-grouping.md +32 -25
- package/.docs/organized/code-examples/with-react-hook-form.md +63 -58
- package/.docs/organized/code-examples/with-react-router.md +38 -30
- package/.docs/organized/code-examples/with-store.md +16 -24
- package/.docs/organized/code-examples/with-tanstack.md +36 -26
- package/.docs/organized/code-examples/with-tap-runtime.md +10 -24
- package/.docs/raw/docs/(docs)/cli.mdx +13 -6
- package/.docs/raw/docs/(docs)/guides/attachments.mdx +26 -3
- package/.docs/raw/docs/(docs)/guides/chain-of-thought.mdx +5 -5
- package/.docs/raw/docs/(docs)/guides/context-api.mdx +53 -52
- package/.docs/raw/docs/(docs)/guides/dictation.mdx +0 -2
- package/.docs/raw/docs/(docs)/guides/message-timing.mdx +169 -0
- package/.docs/raw/docs/(docs)/guides/quoting.mdx +327 -0
- package/.docs/raw/docs/(docs)/guides/speech.mdx +0 -1
- package/.docs/raw/docs/(docs)/index.mdx +12 -2
- package/.docs/raw/docs/(docs)/installation.mdx +8 -2
- package/.docs/raw/docs/(docs)/llm.mdx +9 -7
- package/.docs/raw/docs/(reference)/api-reference/primitives/action-bar-more.mdx +1 -1
- package/.docs/raw/docs/(reference)/api-reference/primitives/action-bar.mdx +2 -2
- package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-if.mdx +27 -27
- package/.docs/raw/docs/(reference)/api-reference/primitives/composer.mdx +60 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/message-part.mdx +78 -4
- package/.docs/raw/docs/(reference)/api-reference/primitives/message.mdx +32 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/selection-toolbar.mdx +61 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/thread.mdx +1 -1
- package/.docs/raw/docs/(reference)/legacy/styled/assistant-modal.mdx +1 -6
- package/.docs/raw/docs/(reference)/legacy/styled/decomposition.mdx +2 -2
- package/.docs/raw/docs/(reference)/legacy/styled/markdown.mdx +1 -6
- package/.docs/raw/docs/(reference)/legacy/styled/thread.mdx +1 -5
- package/.docs/raw/docs/(reference)/migrations/v0-12.mdx +17 -17
- package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +205 -0
- package/.docs/raw/docs/cloud/ai-sdk.mdx +292 -0
- package/.docs/raw/docs/cloud/authorization.mdx +178 -79
- package/.docs/raw/docs/cloud/{persistence/langgraph.mdx → langgraph.mdx} +2 -2
- package/.docs/raw/docs/cloud/overview.mdx +29 -39
- package/.docs/raw/docs/react-native/adapters.mdx +118 -0
- package/.docs/raw/docs/react-native/custom-backend.mdx +210 -0
- package/.docs/raw/docs/react-native/hooks.mdx +364 -0
- package/.docs/raw/docs/react-native/index.mdx +332 -0
- package/.docs/raw/docs/react-native/primitives.mdx +653 -0
- package/.docs/raw/docs/runtimes/ai-sdk/v6.mdx +7 -15
- package/.docs/raw/docs/runtimes/assistant-transport.mdx +103 -0
- package/.docs/raw/docs/runtimes/custom/external-store.mdx +25 -2
- package/.docs/raw/docs/runtimes/data-stream.mdx +1 -3
- package/.docs/raw/docs/runtimes/langgraph/index.mdx +113 -9
- package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +1 -4
- package/.docs/raw/docs/ui/attachment.mdx +4 -2
- package/.docs/raw/docs/ui/message-timing.mdx +92 -0
- package/.docs/raw/docs/ui/part-grouping.mdx +1 -1
- package/.docs/raw/docs/ui/reasoning.mdx +4 -4
- package/.docs/raw/docs/ui/scrollbar.mdx +2 -2
- package/.docs/raw/docs/ui/syntax-highlighting.mdx +55 -50
- package/.docs/raw/docs/ui/thread.mdx +16 -9
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/tools/tests/integration.test.ts +2 -2
- package/src/tools/tests/json-parsing.test.ts +1 -1
- package/src/tools/tests/mcp-protocol.test.ts +1 -3
- package/.docs/raw/docs/cloud/persistence/ai-sdk.mdx +0 -108
|
@@ -45,9 +45,7 @@ For `useChatRuntime`, attachments work automatically without additional configur
|
|
|
45
45
|
```tsx title="/app/MyRuntimeProvider.tsx"
|
|
46
46
|
import { useChatRuntime } from "@assistant-ui/react-ai-sdk";
|
|
47
47
|
|
|
48
|
-
const runtime = useChatRuntime(
|
|
49
|
-
api: "/api/chat",
|
|
50
|
-
});
|
|
48
|
+
const runtime = useChatRuntime();
|
|
51
49
|
```
|
|
52
50
|
|
|
53
51
|
<Callout type="info">
|
|
@@ -479,6 +477,31 @@ class ValidatedImageAdapter implements AttachmentAdapter {
|
|
|
479
477
|
}
|
|
480
478
|
```
|
|
481
479
|
|
|
480
|
+
### External Source Attachments
|
|
481
|
+
|
|
482
|
+
Add attachments from external sources (URLs, API data, CMS references) without needing a `File` object or an `AttachmentAdapter`:
|
|
483
|
+
|
|
484
|
+
```tsx
|
|
485
|
+
const aui = useAui();
|
|
486
|
+
|
|
487
|
+
// Add an attachment from an external source
|
|
488
|
+
await aui.composer().addAttachment({
|
|
489
|
+
name: "report.pdf",
|
|
490
|
+
contentType: "application/pdf",
|
|
491
|
+
content: [{ type: "text", text: "Extracted document content..." }],
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
// Optionally provide id and type
|
|
495
|
+
await aui.composer().addAttachment({
|
|
496
|
+
id: "cms-doc-123",
|
|
497
|
+
type: "document",
|
|
498
|
+
name: "Product Spec",
|
|
499
|
+
content: [{ type: "text", text: "Product specification content..." }],
|
|
500
|
+
});
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
External attachments are added as complete attachments directly — they skip the `AttachmentAdapter` entirely and can be removed without one.
|
|
504
|
+
|
|
482
505
|
### Multiple File Selection
|
|
483
506
|
|
|
484
507
|
Enable multi-file selection with custom limits:
|
|
@@ -46,7 +46,7 @@ const ChainOfThought: FC = () => {
|
|
|
46
46
|
<ChainOfThoughtPrimitive.AccordionTrigger className="flex w-full cursor-pointer items-center gap-2 px-4 py-2 font-medium text-sm hover:bg-muted/50">
|
|
47
47
|
Thinking
|
|
48
48
|
</ChainOfThoughtPrimitive.AccordionTrigger>
|
|
49
|
-
<AuiIf condition={(
|
|
49
|
+
<AuiIf condition={(s) => !s.chainOfThought.collapsed}>
|
|
50
50
|
<ChainOfThoughtPrimitive.Parts
|
|
51
51
|
components={{ Reasoning, tools: { Fallback: ToolFallback } }}
|
|
52
52
|
/>
|
|
@@ -110,7 +110,7 @@ A button that toggles the collapsed/expanded state. Collapsed by default.
|
|
|
110
110
|
Renders the grouped parts when expanded (nothing when collapsed).
|
|
111
111
|
|
|
112
112
|
```tsx
|
|
113
|
-
<AuiIf condition={(
|
|
113
|
+
<AuiIf condition={(s) => !s.chainOfThought.collapsed}>
|
|
114
114
|
<ChainOfThoughtPrimitive.Parts
|
|
115
115
|
components={{
|
|
116
116
|
Reasoning,
|
|
@@ -140,10 +140,10 @@ import { ChevronDownIcon, ChevronRightIcon } from "lucide-react";
|
|
|
140
140
|
const ChainOfThoughtAccordionTrigger = () => {
|
|
141
141
|
return (
|
|
142
142
|
<ChainOfThoughtPrimitive.AccordionTrigger className="flex w-full cursor-pointer items-center gap-2 px-4 py-2 text-sm">
|
|
143
|
-
<AuiIf condition={(
|
|
143
|
+
<AuiIf condition={(s) => s.chainOfThought.collapsed}>
|
|
144
144
|
<ChevronRightIcon className="size-4" />
|
|
145
145
|
</AuiIf>
|
|
146
|
-
<AuiIf condition={(
|
|
146
|
+
<AuiIf condition={(s) => !s.chainOfThought.collapsed}>
|
|
147
147
|
<ChevronDownIcon className="size-4" />
|
|
148
148
|
</AuiIf>
|
|
149
149
|
Thinking
|
|
@@ -154,7 +154,7 @@ const ChainOfThoughtAccordionTrigger = () => {
|
|
|
154
154
|
|
|
155
155
|
## Full Example
|
|
156
156
|
|
|
157
|
-
See the complete [with-chain-of-thought example](https://github.com/
|
|
157
|
+
See the complete [with-chain-of-thought example](https://github.com/assistant-ui/assistant-ui/tree/main/examples/with-chain-of-thought) for a working implementation with tool calls and reasoning.
|
|
158
158
|
|
|
159
159
|
## Related Guides
|
|
160
160
|
|
|
@@ -50,10 +50,10 @@ assistant-ui organizes state into **scopes** - logical boundaries that provide a
|
|
|
50
50
|
// Inside a message component
|
|
51
51
|
function MessageButton() {
|
|
52
52
|
// ✅ Available: message scope (current message)
|
|
53
|
-
const role = useAuiState((
|
|
53
|
+
const role = useAuiState((s) => s.message.role);
|
|
54
54
|
|
|
55
55
|
// ✅ Available: thread scope (parent)
|
|
56
|
-
const isRunning = useAuiState((
|
|
56
|
+
const isRunning = useAuiState((s) => s.thread.isRunning);
|
|
57
57
|
}
|
|
58
58
|
```
|
|
59
59
|
|
|
@@ -76,14 +76,14 @@ Read state reactively with automatic re-renders when values change. This hook wo
|
|
|
76
76
|
import { useAuiState } from "@assistant-ui/react";
|
|
77
77
|
|
|
78
78
|
// Basic usage - extract a single property
|
|
79
|
-
const role = useAuiState((
|
|
80
|
-
const isRunning = useAuiState((
|
|
79
|
+
const role = useAuiState((s) => s.message.role); // "user" | "assistant"
|
|
80
|
+
const isRunning = useAuiState((s) => s.thread.isRunning); // boolean
|
|
81
81
|
|
|
82
82
|
// Access nested data
|
|
83
83
|
const attachmentCount = useAuiState(
|
|
84
|
-
(
|
|
84
|
+
(s) => s.composer.attachments.length,
|
|
85
85
|
);
|
|
86
|
-
const lastMessage = useAuiState((
|
|
86
|
+
const lastMessage = useAuiState((s) => s.thread.messages.at(-1));
|
|
87
87
|
```
|
|
88
88
|
|
|
89
89
|
The selector function receives all available scopes for your component's location and should return a specific value. The component re-renders only when that returned value changes.
|
|
@@ -93,25 +93,25 @@ The selector function receives all available scopes for your component's locatio
|
|
|
93
93
|
```tsx
|
|
94
94
|
// Access multiple scopes
|
|
95
95
|
const canSend = useAuiState(
|
|
96
|
-
(
|
|
96
|
+
(s) => !s.thread.isRunning && s.composer.text.length > 0,
|
|
97
97
|
);
|
|
98
98
|
|
|
99
99
|
// Compute derived state
|
|
100
|
-
const messageCount = useAuiState((
|
|
100
|
+
const messageCount = useAuiState((s) => s.thread.messages.length);
|
|
101
101
|
```
|
|
102
102
|
|
|
103
103
|
**Important:** Never create new objects in selectors. Return primitive values or stable references to avoid infinite re-renders.
|
|
104
104
|
|
|
105
105
|
```tsx
|
|
106
106
|
// ❌ Bad - creates new object every time
|
|
107
|
-
const data = useAuiState((
|
|
108
|
-
role: message.role,
|
|
109
|
-
content: message.content,
|
|
107
|
+
const data = useAuiState((s) => ({
|
|
108
|
+
role: s.message.role,
|
|
109
|
+
content: s.message.content,
|
|
110
110
|
}));
|
|
111
111
|
|
|
112
112
|
// ✅ Good - returns stable values
|
|
113
|
-
const role = useAuiState((
|
|
114
|
-
const content = useAuiState((
|
|
113
|
+
const role = useAuiState((s) => s.message.role);
|
|
114
|
+
const content = useAuiState((s) => s.message.content);
|
|
115
115
|
```
|
|
116
116
|
|
|
117
117
|
### useAui
|
|
@@ -191,7 +191,8 @@ api.part().getState();
|
|
|
191
191
|
aui.composer().send();
|
|
192
192
|
aui.composer().setText(text);
|
|
193
193
|
aui.composer().setRole(role);
|
|
194
|
-
aui.composer().addAttachment(file);
|
|
194
|
+
aui.composer().addAttachment(file); // File object
|
|
195
|
+
aui.composer().addAttachment({ name, content }); // external source
|
|
195
196
|
aui.composer().clearAttachments();
|
|
196
197
|
aui.composer().reset();
|
|
197
198
|
aui.composer().getState();
|
|
@@ -335,7 +336,7 @@ const threadItem = aui.threads().item({ id: "thread_123" });
|
|
|
335
336
|
|
|
336
337
|
```tsx
|
|
337
338
|
function RunIndicator() {
|
|
338
|
-
const isRunning = useAuiState((
|
|
339
|
+
const isRunning = useAuiState((s) => s.thread.isRunning);
|
|
339
340
|
|
|
340
341
|
if (!isRunning) return null;
|
|
341
342
|
return <div>Assistant is thinking...</div>;
|
|
@@ -361,8 +362,8 @@ function CopyButton() {
|
|
|
361
362
|
```tsx
|
|
362
363
|
function SmartComposer() {
|
|
363
364
|
const aui = useAui();
|
|
364
|
-
const isRunning = useAuiState((
|
|
365
|
-
const text = useAuiState((
|
|
365
|
+
const isRunning = useAuiState((s) => s.thread.isRunning);
|
|
366
|
+
const text = useAuiState((s) => s.composer.text);
|
|
366
367
|
|
|
367
368
|
const canSend = !isRunning && text.length > 0;
|
|
368
369
|
|
|
@@ -424,11 +425,11 @@ For most use cases, this behavior is intuitive. In advanced scenarios where you
|
|
|
424
425
|
```tsx
|
|
425
426
|
// ❌ Expensive computation in selector (runs on every store update)
|
|
426
427
|
const result = useAuiState(
|
|
427
|
-
(
|
|
428
|
+
(s) => s.thread.messages.filter((m) => m.role === "user").length,
|
|
428
429
|
);
|
|
429
430
|
|
|
430
431
|
// ✅ Memoize expensive computations
|
|
431
|
-
const messages = useAuiState((
|
|
432
|
+
const messages = useAuiState((s) => s.thread.messages);
|
|
432
433
|
const userCount = useMemo(
|
|
433
434
|
() => messages.filter((m) => m.role === "user").length,
|
|
434
435
|
[messages],
|
|
@@ -439,20 +440,20 @@ const userCount = useMemo(
|
|
|
439
440
|
|
|
440
441
|
```tsx
|
|
441
442
|
// ❌ Subscribes to entire thread state
|
|
442
|
-
const thread = useAuiState((
|
|
443
|
+
const thread = useAuiState((s) => s.thread);
|
|
443
444
|
|
|
444
445
|
// ✅ Subscribe only to needed values
|
|
445
|
-
const isRunning = useAuiState((
|
|
446
|
+
const isRunning = useAuiState((s) => s.thread.isRunning);
|
|
446
447
|
```
|
|
447
448
|
|
|
448
449
|
## API Reference
|
|
449
450
|
|
|
450
451
|
### Hooks
|
|
451
452
|
|
|
452
|
-
| Hook
|
|
453
|
-
|
|
|
453
|
+
| Hook | Purpose | Returns |
|
|
454
|
+
| ----------------------------- | -------------------------- | -------------- |
|
|
454
455
|
| `useAuiState(selector)` | Subscribe to state changes | Selected value |
|
|
455
|
-
| `useAui()`
|
|
456
|
+
| `useAui()` | Get API instance | API object |
|
|
456
457
|
| `useAuiEvent(event, handler)` | Subscribe to events | void |
|
|
457
458
|
|
|
458
459
|
### Scope States
|
|
@@ -469,28 +470,28 @@ const isRunning = useAuiState(({ thread }) => thread.isRunning);
|
|
|
469
470
|
|
|
470
471
|
### Available Actions by Scope
|
|
471
472
|
|
|
472
|
-
| Scope | Actions
|
|
473
|
-
| -------------- |
|
|
474
|
-
| ThreadList | `switchToNewThread()`, `switchToThread(id)`, `getState()`
|
|
475
|
-
| ThreadListItem | `switchTo()`, `rename(title)`, `archive()`, `unarchive()`, `delete()`
|
|
476
|
-
| Thread | `append(message)`, `startRun()`, `cancelRun()`, `switchToNewThread()`
|
|
477
|
-
| Message | `reload()`, `speak()`, `stopSpeaking()`, `submitFeedback(feedback)`
|
|
478
|
-
| Composer | `send()`, `setText(text)`, `addAttachment(file)`, `reset()`
|
|
479
|
-
| Part | `addResult(result)`, `getState()`
|
|
480
|
-
| Attachment | `remove()`, `getState()`
|
|
473
|
+
| Scope | Actions | Use Cases |
|
|
474
|
+
| -------------- | ------------------------------------------------------------------------- | ----------------------------------------- |
|
|
475
|
+
| ThreadList | `switchToNewThread()`, `switchToThread(id)`, `getState()` | Thread navigation and creation |
|
|
476
|
+
| ThreadListItem | `switchTo()`, `rename(title)`, `archive()`, `unarchive()`, `delete()` | Thread management operations |
|
|
477
|
+
| Thread | `append(message)`, `startRun()`, `cancelRun()`, `switchToNewThread()` | Message handling and conversation control |
|
|
478
|
+
| Message | `reload()`, `speak()`, `stopSpeaking()`, `submitFeedback(feedback)` | Message interactions and regeneration |
|
|
479
|
+
| Composer | `send()`, `setText(text)`, `addAttachment(file \| attachment)`, `reset()` | Text input and message composition |
|
|
480
|
+
| Part | `addResult(result)`, `getState()` | Tool call result handling |
|
|
481
|
+
| Attachment | `remove()`, `getState()` | File management |
|
|
481
482
|
|
|
482
483
|
### Common Events
|
|
483
484
|
|
|
484
|
-
| Event
|
|
485
|
-
|
|
|
486
|
-
| `thread.runStart`
|
|
487
|
-
| `thread.runEnd`
|
|
488
|
-
| `thread.initialize`
|
|
489
|
-
| `thread.modelContextUpdate`
|
|
490
|
-
| `composer.send`
|
|
491
|
-
| `composer.attachmentAdd`
|
|
492
|
-
| `threadListItem.switchedTo`
|
|
493
|
-
| `threadListItem.switchedAway`
|
|
485
|
+
| Event | Description |
|
|
486
|
+
| ----------------------------- | ----------------------------- |
|
|
487
|
+
| `thread.runStart` | Assistant starts generating |
|
|
488
|
+
| `thread.runEnd` | Assistant finishes generating |
|
|
489
|
+
| `thread.initialize` | Thread is initialized |
|
|
490
|
+
| `thread.modelContextUpdate` | Model context is updated |
|
|
491
|
+
| `composer.send` | Message is sent |
|
|
492
|
+
| `composer.attachmentAdd` | Attachment added to composer |
|
|
493
|
+
| `threadListItem.switchedTo` | Switched to a thread |
|
|
494
|
+
| `threadListItem.switchedAway` | Switched away from a thread |
|
|
494
495
|
|
|
495
496
|
## Troubleshooting
|
|
496
497
|
|
|
@@ -500,14 +501,14 @@ const isRunning = useAuiState(({ thread }) => thread.isRunning);
|
|
|
500
501
|
|
|
501
502
|
```tsx
|
|
502
503
|
// ❌ This will throw if not inside a message component
|
|
503
|
-
const role = useAuiState((
|
|
504
|
+
const role = useAuiState((s) => s.message.role);
|
|
504
505
|
|
|
505
506
|
// ✅ Check scope availability first
|
|
506
507
|
function SafeMessageButton() {
|
|
507
508
|
const aui = useAui();
|
|
508
509
|
|
|
509
|
-
const role = useAuiState((
|
|
510
|
-
api.message.source !== undefined ? message.role : "none",
|
|
510
|
+
const role = useAuiState((s) =>
|
|
511
|
+
api.message.source !== undefined ? s.message.role : "none",
|
|
511
512
|
);
|
|
512
513
|
|
|
513
514
|
return <div>Role: {role}</div>;
|
|
@@ -518,14 +519,14 @@ function SafeMessageButton() {
|
|
|
518
519
|
|
|
519
520
|
```tsx
|
|
520
521
|
// ❌ Creating new objects in selectors causes infinite re-renders
|
|
521
|
-
const data = useAuiState((
|
|
522
|
-
role: message.role,
|
|
523
|
-
content: message.content, // New object every time!
|
|
522
|
+
const data = useAuiState((s) => ({
|
|
523
|
+
role: s.message.role,
|
|
524
|
+
content: s.message.content, // New object every time!
|
|
524
525
|
}));
|
|
525
526
|
|
|
526
527
|
// ✅ Return primitive values or use separate selectors
|
|
527
|
-
const role = useAuiState((
|
|
528
|
-
const content = useAuiState((
|
|
528
|
+
const role = useAuiState((s) => s.message.role);
|
|
529
|
+
const content = useAuiState((s) => s.message.content);
|
|
529
530
|
```
|
|
530
531
|
|
|
531
532
|
**"Scope resolution failed" / Stale scope references**
|
|
@@ -553,7 +554,7 @@ useEffect(() => {
|
|
|
553
554
|
|
|
554
555
|
```tsx
|
|
555
556
|
// Read state
|
|
556
|
-
const value = useAuiState((
|
|
557
|
+
const value = useAuiState((s) => s.scope.property);
|
|
557
558
|
|
|
558
559
|
// Perform action
|
|
559
560
|
const aui = useAui();
|
|
@@ -22,7 +22,6 @@ The `WebSpeechDictationAdapter` is supported in Chrome, Edge, and Safari. Check
|
|
|
22
22
|
import { WebSpeechDictationAdapter } from "@assistant-ui/react";
|
|
23
23
|
|
|
24
24
|
const runtime = useChatRuntime({
|
|
25
|
-
api: "/api/chat",
|
|
26
25
|
adapters: {
|
|
27
26
|
dictation: new WebSpeechDictationAdapter({
|
|
28
27
|
// Optional configuration
|
|
@@ -345,7 +344,6 @@ export class ElevenLabsScribeAdapter implements DictationAdapter {
|
|
|
345
344
|
|
|
346
345
|
```tsx
|
|
347
346
|
const runtime = useChatRuntime({
|
|
348
|
-
api: "/api/chat",
|
|
349
347
|
adapters: {
|
|
350
348
|
dictation: new ElevenLabsScribeAdapter({
|
|
351
349
|
tokenEndpoint: "/api/scribe-token",
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Message Timing
|
|
3
|
+
description: Display stream timing metadata like duration, tokens per second, and time to first token.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Display stream performance metrics — duration, tokens per second, TTFT — on assistant messages.
|
|
7
|
+
|
|
8
|
+
<Callout type="info">
|
|
9
|
+
The [`MessageTiming`](/docs/ui/message-timing) registry component provides a ready-made badge + popover UI. This guide covers the underlying `useMessageTiming()` hook for custom implementations and runtime-specific setup.
|
|
10
|
+
</Callout>
|
|
11
|
+
|
|
12
|
+
## Reading Timing Data
|
|
13
|
+
|
|
14
|
+
Use `useMessageTiming()` inside a message component to access timing data:
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import { useMessageTiming } from "@assistant-ui/react";
|
|
18
|
+
|
|
19
|
+
const MessageTimingDisplay: FC = () => {
|
|
20
|
+
const timing = useMessageTiming();
|
|
21
|
+
if (!timing?.totalStreamTime) return null;
|
|
22
|
+
|
|
23
|
+
const formatMs = (ms: number) =>
|
|
24
|
+
ms < 1000 ? `${Math.round(ms)}ms` : `${(ms / 1000).toFixed(2)}s`;
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<span className="text-xs text-muted-foreground">
|
|
28
|
+
{formatMs(timing.totalStreamTime)}
|
|
29
|
+
{timing.tokensPerSecond !== undefined &&
|
|
30
|
+
` · ${timing.tokensPerSecond.toFixed(1)} tok/s`}
|
|
31
|
+
</span>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Place it inside `MessagePrimitive.Root`, typically near the action bar:
|
|
37
|
+
|
|
38
|
+
```tsx {8}
|
|
39
|
+
const AssistantMessage: FC = () => {
|
|
40
|
+
return (
|
|
41
|
+
<MessagePrimitive.Root>
|
|
42
|
+
<MessagePrimitive.Parts components={{ ... }} />
|
|
43
|
+
<ActionBarPrimitive.Root>
|
|
44
|
+
<ActionBarPrimitive.Copy />
|
|
45
|
+
<ActionBarPrimitive.Reload />
|
|
46
|
+
<MessageTimingDisplay />
|
|
47
|
+
</ActionBarPrimitive.Root>
|
|
48
|
+
</MessagePrimitive.Root>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### `useMessageTiming()` Return Fields
|
|
54
|
+
|
|
55
|
+
| Field | Type | Description |
|
|
56
|
+
|-------|------|-------------|
|
|
57
|
+
| `streamStartTime` | `number` | Unix timestamp when stream started |
|
|
58
|
+
| `firstTokenTime` | `number?` | Time to first text token (ms) |
|
|
59
|
+
| `totalStreamTime` | `number?` | Total stream duration (ms) |
|
|
60
|
+
| `tokenCount` | `number?` | Real or estimated output token count |
|
|
61
|
+
| `tokensPerSecond` | `number?` | Throughput (tokens/sec); see [accuracy note](#ai-sdk-usechatruntime) |
|
|
62
|
+
| `totalChunks` | `number` | Total stream chunks received |
|
|
63
|
+
| `toolCallCount` | `number` | Number of tool calls |
|
|
64
|
+
|
|
65
|
+
## Runtime Support
|
|
66
|
+
|
|
67
|
+
| Runtime | Supported | Notes |
|
|
68
|
+
|---------|:-:|-------|
|
|
69
|
+
| DataStream | Yes | Automatic via `AssistantMessageAccumulator` |
|
|
70
|
+
| AI SDK (`useChatRuntime`) | Yes | Automatic via client-side tracking |
|
|
71
|
+
| Local (`useLocalRuntime`) | Yes | Pass timing in `ChatModelRunResult.metadata` |
|
|
72
|
+
| ExternalStore | Yes | Pass timing in `ThreadMessageLike.metadata` |
|
|
73
|
+
| LangGraph | No | Not yet implemented |
|
|
74
|
+
| AG-UI | No | Not yet implemented |
|
|
75
|
+
|
|
76
|
+
### DataStream
|
|
77
|
+
|
|
78
|
+
Timing is tracked automatically inside `AssistantMessageAccumulator`. No setup required.
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
import { useDataStreamRuntime } from "@assistant-ui/react-data-stream";
|
|
82
|
+
|
|
83
|
+
const runtime = useDataStreamRuntime({ api: "/api/chat" });
|
|
84
|
+
// useMessageTiming() works out of the box
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### AI SDK (`useChatRuntime`)
|
|
88
|
+
|
|
89
|
+
Timing is tracked automatically on the client side by observing streaming state transitions and content changes. Timing is finalized when each stream completes.
|
|
90
|
+
|
|
91
|
+
<Callout type="warn">
|
|
92
|
+
`tokenCount` and `tokensPerSecond` are **estimated** using a 4 characters per token approximation — real token counts from the server are not extracted. This can overcount significantly for short messages.
|
|
93
|
+
</Callout>
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
import { useChatRuntime } from "@assistant-ui/react-ai-sdk";
|
|
97
|
+
|
|
98
|
+
const runtime = useChatRuntime();
|
|
99
|
+
// useMessageTiming() works out of the box
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Local (`useLocalRuntime`)
|
|
103
|
+
|
|
104
|
+
Pass timing in the `metadata` field of your `ChatModelRunResult`:
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
import type { ChatModelAdapter } from "@assistant-ui/react";
|
|
108
|
+
|
|
109
|
+
const myAdapter: ChatModelAdapter = {
|
|
110
|
+
async run({ messages, abortSignal }) {
|
|
111
|
+
const startTime = Date.now();
|
|
112
|
+
const result = await callMyAPI(messages, abortSignal);
|
|
113
|
+
const totalStreamTime = Date.now() - startTime;
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
content: [{ type: "text", text: result.text }],
|
|
117
|
+
metadata: {
|
|
118
|
+
timing: {
|
|
119
|
+
streamStartTime: startTime,
|
|
120
|
+
totalStreamTime,
|
|
121
|
+
tokenCount: result.usage?.completionTokens,
|
|
122
|
+
tokensPerSecond:
|
|
123
|
+
result.usage?.completionTokens
|
|
124
|
+
? result.usage.completionTokens / (totalStreamTime / 1000)
|
|
125
|
+
: undefined,
|
|
126
|
+
totalChunks: 1,
|
|
127
|
+
toolCallCount: 0,
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### ExternalStore (`useExternalStoreRuntime`)
|
|
136
|
+
|
|
137
|
+
Pass timing in the `metadata.timing` field of your `ThreadMessageLike` messages:
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
import type { ThreadMessageLike } from "@assistant-ui/react";
|
|
141
|
+
|
|
142
|
+
const message: ThreadMessageLike = {
|
|
143
|
+
role: "assistant",
|
|
144
|
+
content: [{ type: "text", text: fullText }],
|
|
145
|
+
metadata: {
|
|
146
|
+
timing: {
|
|
147
|
+
streamStartTime: startTime,
|
|
148
|
+
firstTokenTime,
|
|
149
|
+
totalStreamTime,
|
|
150
|
+
tokenCount,
|
|
151
|
+
tokensPerSecond,
|
|
152
|
+
totalChunks: chunks,
|
|
153
|
+
toolCallCount: 0,
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## API Reference
|
|
160
|
+
|
|
161
|
+
### `useMessageTiming()`
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
const timing: MessageTiming | undefined = useMessageTiming();
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Returns timing metadata for the current assistant message, or `undefined` for non-assistant messages or when no timing data is available.
|
|
168
|
+
|
|
169
|
+
Must be used inside a `MessagePrimitive.Root` context.
|