@assistant-ui/mcp-docs-server 0.1.34 → 0.1.36
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 +4 -4
- package/.docs/organized/code-examples/with-a2a.md +5 -5
- package/.docs/organized/code-examples/with-ag-ui.md +6 -6
- package/.docs/organized/code-examples/with-ai-sdk-v6.md +7 -7
- package/.docs/organized/code-examples/with-artifacts.md +7 -7
- package/.docs/organized/code-examples/with-assistant-transport.md +73 -57
- package/.docs/organized/code-examples/with-browser-extension.md +7 -7
- package/.docs/organized/code-examples/with-chain-of-thought.md +44 -93
- package/.docs/organized/code-examples/with-cloud-standalone.md +7 -7
- package/.docs/organized/code-examples/with-cloud.md +7 -7
- package/.docs/organized/code-examples/with-custom-thread-list.md +7 -7
- package/.docs/organized/code-examples/with-elevenlabs-conversational.md +9 -9
- package/.docs/organized/code-examples/with-elevenlabs-scribe.md +9 -9
- package/.docs/organized/code-examples/with-eve.md +343 -0
- package/.docs/organized/code-examples/with-expo.md +943 -940
- package/.docs/organized/code-examples/with-external-store.md +5 -5
- package/.docs/organized/code-examples/with-ffmpeg.md +8 -11
- package/.docs/organized/code-examples/with-generative-ui.md +33 -309
- package/.docs/organized/code-examples/with-google-adk.md +5 -5
- package/.docs/organized/code-examples/with-heat-graph.md +4 -4
- package/.docs/organized/code-examples/with-image-generation.md +7 -7
- package/.docs/organized/code-examples/with-interactables.md +169 -341
- package/.docs/organized/code-examples/with-langchain.md +7 -7
- package/.docs/organized/code-examples/with-langgraph.md +23 -160
- package/.docs/organized/code-examples/with-livekit.md +10 -10
- package/.docs/organized/code-examples/with-mcp.md +7 -7
- package/.docs/organized/code-examples/with-opencode.md +106 -580
- package/.docs/organized/code-examples/with-pi.md +2046 -0
- package/.docs/organized/code-examples/with-react-hook-form.md +16 -9
- package/.docs/organized/code-examples/with-react-ink-web.md +691 -0
- package/.docs/organized/code-examples/with-react-ink.md +29 -17
- package/.docs/organized/code-examples/with-react-router.md +12 -12
- package/.docs/organized/code-examples/with-resumable-stream.md +8 -8
- package/.docs/organized/code-examples/with-store.md +14 -10
- package/.docs/organized/code-examples/with-tanstack.md +21 -7
- package/.docs/organized/code-examples/with-tap-runtime.md +5 -5
- package/.docs/organized/code-examples/with-virtualized-thread.md +676 -0
- package/.docs/raw/docs/(docs)/architecture.mdx +13 -12
- package/.docs/raw/docs/(docs)/cli.mdx +4 -2
- package/.docs/raw/docs/(docs)/devtools.mdx +25 -2
- package/.docs/raw/docs/(docs)/index.mdx +5 -2
- package/.docs/raw/docs/(docs)/installation.mdx +5 -2
- package/.docs/raw/docs/(reference)/api-reference/external-store/message-conversion.mdx +16 -16
- package/.docs/raw/docs/(reference)/api-reference/hooks/composer-triggers.mdx +73 -42
- package/.docs/raw/docs/(reference)/api-reference/hooks/model-context.mdx +3 -20
- package/.docs/raw/docs/(reference)/api-reference/hooks/primitives.mdx +234 -123
- package/.docs/raw/docs/(reference)/api-reference/integrations/eve.mdx +51 -0
- package/.docs/raw/docs/(reference)/api-reference/integrations/index.mdx +4 -1
- package/.docs/raw/docs/(reference)/api-reference/integrations/react-ai-sdk.mdx +70 -38
- package/.docs/raw/docs/(reference)/api-reference/model-context/context.mdx +1 -1
- package/.docs/raw/docs/(reference)/api-reference/primitives/action-bar.mdx +30 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/attachment.mdx +7 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/branch-picker.mdx +26 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/chain-of-thought.mdx +15 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/composer.mdx +103 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/message-part.mdx +16 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/message.mdx +56 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/queue-item.mdx +12 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/selection-toolbar.mdx +12 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/suggestion.mdx +14 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/thread.mdx +57 -1
- package/.docs/raw/docs/(reference)/api-reference/tools/index.mdx +6 -0
- package/.docs/raw/docs/(reference)/api-reference/tools/interactables-legacy.mdx +55 -0
- package/.docs/raw/docs/(reference)/api-reference/tools/interactables.mdx +151 -0
- package/.docs/raw/docs/(reference)/api-reference/tools/rendering.mdx +17 -38
- package/.docs/raw/docs/(reference)/api-reference/tools/status.mdx +22 -0
- package/.docs/raw/docs/(reference)/api-reference/tools/toolkits.mdx +20 -27
- package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +40 -25
- package/.docs/raw/docs/guides/chain-of-thought.mdx +1 -1
- package/.docs/raw/docs/guides/headless-composer-input.mdx +113 -0
- package/.docs/raw/docs/guides/index.mdx +3 -0
- package/.docs/raw/docs/guides/input-history.mdx +55 -0
- package/.docs/raw/docs/guides/latex.mdx +28 -22
- package/.docs/raw/docs/guides/mentions.mdx +32 -7
- package/.docs/raw/docs/guides/slash-commands.mdx +3 -6
- package/.docs/raw/docs/guides/speech.mdx +5 -7
- package/.docs/raw/docs/guides/virtualization.mdx +133 -0
- package/.docs/raw/docs/guides/voice.mdx +3 -2
- package/.docs/raw/docs/ink/hooks.mdx +2 -2
- package/.docs/raw/docs/integrations/attachments/custom-adapter.mdx +4 -12
- package/.docs/raw/docs/integrations/auth/better-auth.mdx +7 -10
- package/.docs/raw/docs/integrations/auth/clerk.mdx +3 -9
- package/.docs/raw/docs/integrations/auth/next-auth.mdx +3 -8
- package/.docs/raw/docs/integrations/frameworks/ai-sdk.mdx +8 -5
- package/.docs/raw/docs/integrations/index.mdx +5 -12
- package/.docs/raw/docs/integrations/observability/helicone.mdx +3 -4
- package/.docs/raw/docs/integrations/observability/langfuse.mdx +3 -5
- package/.docs/raw/docs/integrations/observability/langsmith.mdx +3 -2
- package/.docs/raw/docs/integrations/persistence/custom-adapter.mdx +4 -3
- package/.docs/raw/docs/primitives/composer.mdx +8 -0
- package/.docs/raw/docs/primitives/thread.mdx +24 -0
- package/.docs/raw/docs/react-native/hooks.mdx +1 -1
- package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +22 -3
- package/.docs/raw/docs/runtimes/concepts/stability.mdx +1 -1
- package/.docs/raw/docs/runtimes/custom/data-stream.mdx +1 -1
- package/.docs/raw/docs/runtimes/custom/external-store.mdx +54 -0
- package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +108 -4
- package/.docs/raw/docs/runtimes/eve/overview.mdx +100 -0
- package/.docs/raw/docs/runtimes/eve/quickstart.mdx +143 -0
- package/.docs/raw/docs/runtimes/langchain.mdx +386 -18
- package/.docs/raw/docs/runtimes/langgraph/quickstart.mdx +1 -1
- package/.docs/raw/docs/runtimes/langgraph/tutorial/part-1.mdx +1 -1
- package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +12 -6
- package/.docs/raw/docs/tools/defining-tools.mdx +4 -0
- package/.docs/raw/docs/tools/interactables-legacy.mdx +410 -0
- package/.docs/raw/docs/tools/interactables.mdx +892 -223
- package/.docs/raw/docs/tools/mcp.mdx +4 -4
- package/.docs/raw/docs/tools/multi-agent.mdx +5 -0
- package/.docs/raw/docs/tools/tool-ui.mdx +37 -1
- package/.docs/raw/docs/ui/composer-trigger-popover.mdx +2 -0
- package/.docs/raw/docs/ui/dot-matrix.mdx +133 -0
- package/.docs/raw/docs/ui/file.mdx +1 -1
- package/.docs/raw/docs/ui/model-selector.mdx +219 -52
- package/.docs/raw/docs/ui/number-roll.mdx +154 -0
- package/.docs/raw/docs/ui/part-grouping.mdx +38 -0
- package/.docs/raw/docs/ui/reasoning.mdx +3 -3
- package/.docs/raw/docs/ui/streamdown.mdx +2 -0
- package/.docs/raw/docs/ui/syntax-highlighting.mdx +1 -1
- package/.docs/raw/docs/ui/thread.mdx +52 -0
- package/.docs/raw/docs/ui/tool-fallback.mdx +2 -2
- package/.docs/raw/docs/utilities/react-o11y.mdx +92 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -2
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +14 -6
- package/src/tools/tests/mcp-protocol.test.ts +9 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Headless Composer Input
|
|
3
|
+
description: Build a custom composer input while keeping assistant-ui composer state and send gating.
|
|
4
|
+
platforms: ["react"]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
`ComposerPrimitive.Input` is still the recommended composer input for most apps. It owns autosize, keyboard shortcuts, IME handling, paste-to-attachment, focus behavior, and trigger-popover keyboard integration.
|
|
8
|
+
|
|
9
|
+
Use `unstable_useComposerInput` when you already own the input surface, such as a custom editor, a `contentEditable` surface, or a textarea wrapper whose behavior cannot be expressed through `ComposerPrimitive.Input`'s props, `asChild`, or `render` APIs.
|
|
10
|
+
|
|
11
|
+
<Callout type="warn">
|
|
12
|
+
This API is marked unstable and may change without notice. It is a thin bridge
|
|
13
|
+
to composer text and send state, not a replacement implementation of
|
|
14
|
+
`ComposerPrimitive.Input`.
|
|
15
|
+
</Callout>
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
Render the custom input inside a composer and mirror text changes into the assistant-ui composer state:
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
"use client";
|
|
23
|
+
|
|
24
|
+
import {
|
|
25
|
+
ComposerPrimitive,
|
|
26
|
+
unstable_useComposerInput,
|
|
27
|
+
unstable_useTriggerPopoverAriaProps,
|
|
28
|
+
} from "@assistant-ui/react";
|
|
29
|
+
|
|
30
|
+
function HeadlessComposer() {
|
|
31
|
+
const composer = unstable_useComposerInput();
|
|
32
|
+
const popoverAria = unstable_useTriggerPopoverAriaProps();
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<ComposerPrimitive.Root>
|
|
36
|
+
<textarea
|
|
37
|
+
aria-label="Message"
|
|
38
|
+
value={composer.value}
|
|
39
|
+
disabled={composer.isDisabled}
|
|
40
|
+
onChange={(event) => {
|
|
41
|
+
composer.setText(event.currentTarget.value);
|
|
42
|
+
}}
|
|
43
|
+
onKeyDown={(event) => {
|
|
44
|
+
if (event.nativeEvent.isComposing) return;
|
|
45
|
+
|
|
46
|
+
if (event.key === "Enter" && !event.shiftKey) {
|
|
47
|
+
event.preventDefault();
|
|
48
|
+
|
|
49
|
+
if (composer.canSend) {
|
|
50
|
+
composer.send();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}}
|
|
54
|
+
{...popoverAria}
|
|
55
|
+
/>
|
|
56
|
+
<ComposerPrimitive.Send />
|
|
57
|
+
</ComposerPrimitive.Root>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`composer.send()` exposes the same send action used by `ComposerPrimitive.Send`, including send options such as `composer.send({ steer: true })`. It is a no-op unless `composer.canSend` is `true`; check `canSend` in custom keyboard handlers to keep your event handling explicit.
|
|
63
|
+
|
|
64
|
+
## Hook Result
|
|
65
|
+
|
|
66
|
+
| Field | Description |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| `value` | Current composer text. Returns `""` when the composer is not editing. |
|
|
69
|
+
| `setText(text)` | Writes text into the composer while it is editing. |
|
|
70
|
+
| `send(options?)` | Sends the current composer message when `canSend` is `true`; otherwise a no-op. |
|
|
71
|
+
| `isDisabled` | Combines the hook's `disabled` option with assistant-ui disabled sources, such as thread disabled state and active dictation input lock. |
|
|
72
|
+
| `canSend` | Matches `ComposerPrimitive.Send` gating, then also respects `isDisabled`. |
|
|
73
|
+
|
|
74
|
+
Pass `disabled` when your editor has an additional read-only state:
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
const composer = unstable_useComposerInput({
|
|
78
|
+
disabled: editorReadOnly,
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## What You Still Own
|
|
83
|
+
|
|
84
|
+
The hook does not recreate the behavior of `ComposerPrimitive.Input`. Your input or editor remains responsible for:
|
|
85
|
+
|
|
86
|
+
- Enter/newline shortcuts, steer shortcuts, escape/cancel behavior, and any other keyboard behavior.
|
|
87
|
+
- IME and composition handling.
|
|
88
|
+
- Cursor and selection tracking.
|
|
89
|
+
- Autosize and focus management.
|
|
90
|
+
- Paste/drop attachment behavior.
|
|
91
|
+
- Rich text state, serialization, and DOM synchronization for `contentEditable` or editor-library integrations.
|
|
92
|
+
|
|
93
|
+
For styled textareas, prefer `ComposerPrimitive.Input`. Reach for the headless hook when the editor has its own model or DOM lifecycle and assistant-ui should only supply composer state and send gating.
|
|
94
|
+
|
|
95
|
+
## Trigger Popovers
|
|
96
|
+
|
|
97
|
+
`unstable_useTriggerPopoverAriaProps` returns the combobox ARIA attributes for the currently open trigger popover:
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
const popoverAria = unstable_useTriggerPopoverAriaProps();
|
|
101
|
+
|
|
102
|
+
<textarea {...popoverAria} />;
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Spread these props last so they can mirror `ComposerPrimitive.Input` when a popover is open.
|
|
106
|
+
|
|
107
|
+
This helper only describes the open popover to assistive technology. It does not wire a custom editor into mention or slash-command keyboard handling, cursor tracking, or item insertion. For the full built-in trigger-popover experience, use `ComposerPrimitive.Input`; for richer editors, integrate the trigger UI with the editor's own selection and keyboard model.
|
|
108
|
+
|
|
109
|
+
## Related
|
|
110
|
+
|
|
111
|
+
- [Composer primitives](/docs/primitives/composer)
|
|
112
|
+
- [Composer Trigger Popover](/docs/ui/composer-trigger-popover)
|
|
113
|
+
- [Input History](/docs/guides/input-history)
|
|
@@ -68,6 +68,9 @@ Render specialized message content.
|
|
|
68
68
|
<Card title="LaTeX" href="/docs/guides/latex">
|
|
69
69
|
Render math via React Markdown or Streamdown, with streaming-safe escape rules.
|
|
70
70
|
</Card>
|
|
71
|
+
<Card title="Thread Virtualization" href="/docs/guides/virtualization">
|
|
72
|
+
Render very long threads with @tanstack/react-virtual and per-index message rendering.
|
|
73
|
+
</Card>
|
|
71
74
|
</Cards>
|
|
72
75
|
|
|
73
76
|
## Audio
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Input History
|
|
3
|
+
description: Terminal-style ArrowUp/ArrowDown recall of previously sent messages in the assistant-ui React composer.
|
|
4
|
+
platforms: ["react"]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Input history lets users press ArrowUp in an empty composer to recall previously sent messages, newest first, like a shell prompt. ArrowDown steps back toward the newest entry and finally restores the draft that was being typed when browsing started.
|
|
8
|
+
|
|
9
|
+
<Callout type="warn">
|
|
10
|
+
This API is marked unstable and may change without notice.
|
|
11
|
+
</Callout>
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
Spread the hook's bundle onto `ComposerPrimitive.Input`:
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import {
|
|
19
|
+
ComposerPrimitive,
|
|
20
|
+
unstable_useComposerInputHistory,
|
|
21
|
+
} from "@assistant-ui/react";
|
|
22
|
+
|
|
23
|
+
const Composer = () => {
|
|
24
|
+
const history = unstable_useComposerInputHistory();
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<ComposerPrimitive.Root>
|
|
28
|
+
<ComposerPrimitive.Input {...history} />
|
|
29
|
+
<ComposerPrimitive.Send />
|
|
30
|
+
</ComposerPrimitive.Root>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The history ring is derived live from the current thread's user messages (trimmed, with adjacent duplicates collapsed), so it needs no persistence or configuration.
|
|
36
|
+
|
|
37
|
+
## Behavior
|
|
38
|
+
|
|
39
|
+
- ArrowUp starts recall only when the composer is empty (whitespace-only drafts count as empty and are restored on the way back down).
|
|
40
|
+
- While a recalled multi-line message is shown, arrows move the caret line by line; recall only steps when the caret is on the first line (ArrowUp) or last line (ArrowDown).
|
|
41
|
+
- An open mention or slash-command popover keeps owning the arrow keys; the hook yields whenever a trigger popover is active.
|
|
42
|
+
- IME composition, modifier keys, text selections, and events a preceding handler already `preventDefault`ed are left untouched.
|
|
43
|
+
- Switching threads or sending a message resets the browse position.
|
|
44
|
+
- The hook is inert on edit composers.
|
|
45
|
+
|
|
46
|
+
To interleave your own ArrowUp handling ahead of history (for example, stepping through queued messages), compose your handler before the hook's and call `preventDefault` when you consume the key:
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
<ComposerPrimitive.Input
|
|
50
|
+
onKeyDown={(e) => {
|
|
51
|
+
myHandler(e);
|
|
52
|
+
history.onKeyDown(e);
|
|
53
|
+
}}
|
|
54
|
+
/>
|
|
55
|
+
```
|
|
@@ -106,47 +106,53 @@ By default, remark-math (react-markdown path) supports:
|
|
|
106
106
|
|
|
107
107
|
## Supporting Alternative LaTeX Delimiters
|
|
108
108
|
|
|
109
|
-
Many language models
|
|
110
|
-
- `\(...\)` for inline math
|
|
111
|
-
-
|
|
112
|
-
- Custom formats like `[/math]...[/math]`
|
|
109
|
+
Many language models emit math in delimiters that remark-math does not recognize:
|
|
110
|
+
- `\(...\)` for inline math and `\[...\]` for display math
|
|
111
|
+
- custom tags like `[/math]...[/math]` and `[/inline]...[/inline]`
|
|
113
112
|
|
|
114
|
-
|
|
113
|
+
`@assistant-ui/react-markdown` exports `normalizeMathDelimiters`, which rewrites these to the `$...$` and `$$...$$` form remark-math parses. Pass it to the `preprocess` prop of `MarkdownTextPrimitive`:
|
|
115
114
|
|
|
116
115
|
```tsx title="/components/assistant-ui/markdown-text.tsx"
|
|
117
|
-
import {
|
|
116
|
+
import {
|
|
117
|
+
MarkdownTextPrimitive,
|
|
118
|
+
normalizeMathDelimiters, // [!code ++]
|
|
119
|
+
} from "@assistant-ui/react-markdown";
|
|
118
120
|
|
|
119
121
|
const MarkdownTextImpl = () => {
|
|
120
122
|
return (
|
|
121
123
|
<MarkdownTextPrimitive
|
|
122
124
|
remarkPlugins={[remarkGfm, remarkMath]}
|
|
123
125
|
rehypePlugins={[rehypeKatex]}
|
|
124
|
-
preprocess={
|
|
126
|
+
preprocess={normalizeMathDelimiters} // [!code ++]
|
|
125
127
|
className="aui-md"
|
|
126
128
|
components={defaultComponents}
|
|
127
129
|
/>
|
|
128
130
|
);
|
|
129
131
|
};
|
|
132
|
+
```
|
|
130
133
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
.replace(/\[\/math\]([\s\S]*?)\[\/math\]/g, (_, content) => `$$${content.trim()}$$`)
|
|
134
|
+
The individual transforms `rewriteLatexBracketDelimiters` and `rewriteCustomMathTags` are exported too, for finer control over which delimiters are normalized.
|
|
135
|
+
|
|
136
|
+
<Callout type="info">
|
|
137
|
+
Using [Streamdown](/docs/ui/streamdown) as your renderer? The same helpers are exported from `@assistant-ui/react-streamdown` and accepted by the `preprocess` prop of `StreamdownTextPrimitive`.
|
|
138
|
+
</Callout>
|
|
137
139
|
|
|
138
|
-
|
|
139
|
-
.replace(/\[\/inline\]([\s\S]*?)\[\/inline\]/g, (_, content) => `$${content.trim()}$`)
|
|
140
|
+
### Currency amounts
|
|
140
141
|
|
|
141
|
-
|
|
142
|
-
.replace(/\\{1,2}\(([\s\S]*?)\\{1,2}\)/g, (_, content) => `$${content.trim()}$`)
|
|
142
|
+
With single-dollar inline math enabled (the default on the react-markdown path), remark-math reads a lone `$` as a math delimiter, so prose such as `$5 ... $10` is parsed as math. `escapeCurrencyDollars` escapes a `$` immediately followed by a digit so currency survives, while leaving the `$$` of display math intact. Compose it with the delimiter normalization:
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
144
|
+
```tsx title="/components/assistant-ui/markdown-text.tsx"
|
|
145
|
+
import {
|
|
146
|
+
normalizeMathDelimiters,
|
|
147
|
+
escapeCurrencyDollars, // [!code ++]
|
|
148
|
+
} from "@assistant-ui/react-markdown";
|
|
149
|
+
|
|
150
|
+
<MarkdownTextPrimitive
|
|
151
|
+
preprocess={(text) => escapeCurrencyDollars(normalizeMathDelimiters(text))} // [!code ++]
|
|
152
|
+
// ...
|
|
153
|
+
/>;
|
|
148
154
|
```
|
|
149
155
|
|
|
150
156
|
<Callout type="tip">
|
|
151
|
-
Inside `MarkdownTextPrimitive`, the streamed text first passes through `preprocess` (delimiter normalization) and then through `useSmooth` (character
|
|
157
|
+
Inside `MarkdownTextPrimitive`, the streamed text first passes through `preprocess` (delimiter normalization) and then through `useSmooth` (character by character accumulation), and only then reaches the markdown parser. Both run before remark-math sees the text, so delimiter replacement and the streaming smoothing stay streaming safe; a partially received delimiter is accumulated in the smoothing buffer rather than parsed mid fragment.
|
|
152
158
|
</Callout>
|
|
@@ -8,12 +8,9 @@ Mentions let users type `@` in the composer to open a popover picker, select an
|
|
|
8
8
|
|
|
9
9
|
## How It Works
|
|
10
10
|
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
Directive inserted ← User selects item from popover
|
|
15
|
-
↓
|
|
16
|
-
Message sent with ":tool[Label]{name=id}" in text
|
|
11
|
+
```mermaid
|
|
12
|
+
flowchart LR
|
|
13
|
+
type["User types @"] --> trigger["Trigger detected"] --> adapter["Adapter provides<br/>categories / items"] --> select["User selects item<br/>from popover"] --> insert["Directive inserted"] --> sent["Message sent with<br/>:tool[Label]{name=id} text"]
|
|
17
14
|
```
|
|
18
15
|
|
|
19
16
|
The mention system has three layers:
|
|
@@ -85,7 +82,35 @@ const myAdapter: Unstable_TriggerAdapter = {
|
|
|
85
82
|
|
|
86
83
|
### Async Mention Search
|
|
87
84
|
|
|
88
|
-
The
|
|
85
|
+
The built-in `unstable_useLiveCompletionAdapter` wraps an async fetcher with debouncing, stale-request cancellation (results for an outdated query are dropped), and a single-entry cache. Its `search` returns the last results synchronously and schedules a debounced fetch when the query changes; when results arrive the returned `adapter` re-creates, which re-runs the popover lookup so the fresh items render. It also reports `isLoading`, which you pass to the popover to show a loading state.
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
import {
|
|
89
|
+
unstable_useLiveCompletionAdapter,
|
|
90
|
+
unstable_defaultDirectiveFormatter,
|
|
91
|
+
} from "@assistant-ui/react";
|
|
92
|
+
import { ComposerTriggerPopover } from "@/components/assistant-ui/composer-trigger-popover";
|
|
93
|
+
|
|
94
|
+
function MentionPopover() {
|
|
95
|
+
const mentions = unstable_useLiveCompletionAdapter({
|
|
96
|
+
fetcher: async (query) => {
|
|
97
|
+
const users = await fetchUsers(query);
|
|
98
|
+
return users.map((u) => ({ id: u.id, type: "user", label: u.name }));
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<ComposerTriggerPopover
|
|
104
|
+
char="@"
|
|
105
|
+
adapter={mentions.adapter}
|
|
106
|
+
isLoading={mentions.isLoading}
|
|
107
|
+
directive={{ formatter: unstable_defaultDirectiveFormatter }}
|
|
108
|
+
/>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The adapter interface is itself synchronous, so you can also wire async data by hand when you need a custom cache, no debounce, or an existing query client. Load results into React state (or a query cache) and read the current snapshot inside the adapter methods. The adapter re-creates on each render, so the popover always sees the latest results.
|
|
89
114
|
|
|
90
115
|
**With React state and `useEffect`:**
|
|
91
116
|
|
|
@@ -8,12 +8,9 @@ Slash commands let users type `/` in the composer to open a popover, browse avai
|
|
|
8
8
|
|
|
9
9
|
## How It Works
|
|
10
10
|
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
Callback fired ← User selects command from popover
|
|
15
|
-
↓
|
|
16
|
-
Directive chip left in composer (or removed if removeOnExecute)
|
|
11
|
+
```mermaid
|
|
12
|
+
flowchart LR
|
|
13
|
+
type["User types /"] --> trigger["Trigger detected"] --> adapter["Adapter provides<br/>commands"] --> select["User selects command<br/>from popover"] --> callback["Callback fired"] --> chip["Directive chip left in composer<br/>(or removed if removeOnExecute)"]
|
|
17
14
|
```
|
|
18
15
|
|
|
19
16
|
The slash command system is built on the same [trigger popover architecture](#trigger-popover-architecture) as mentions. A slash command declares its behavior with a `<TriggerPopover.Action>` sub-primitive whose `onExecute` callback fires when an item is chosen.
|
|
@@ -57,24 +57,22 @@ const runtime = useChatRuntime({
|
|
|
57
57
|
The default action bar does not include a speech button. Add `ActionBarPrimitive.Speak` and `ActionBarPrimitive.StopSpeaking` to your assistant message action bar:
|
|
58
58
|
|
|
59
59
|
```tsx
|
|
60
|
-
import { ActionBarPrimitive,
|
|
60
|
+
import { ActionBarPrimitive, AuiIf } from "@assistant-ui/react";
|
|
61
61
|
import { AudioLinesIcon, StopCircleIcon } from "lucide-react";
|
|
62
62
|
|
|
63
63
|
const AssistantActionBar = () => {
|
|
64
|
-
const isSpeaking = useMessageTTS();
|
|
65
|
-
|
|
66
64
|
return (
|
|
67
65
|
<ActionBarPrimitive.Root>
|
|
68
|
-
{
|
|
66
|
+
<AuiIf condition={(s) => s.message.speech == null}>
|
|
69
67
|
<ActionBarPrimitive.Speak>
|
|
70
68
|
<AudioLinesIcon />
|
|
71
69
|
</ActionBarPrimitive.Speak>
|
|
72
|
-
|
|
73
|
-
{
|
|
70
|
+
</AuiIf>
|
|
71
|
+
<AuiIf condition={(s) => s.message.speech != null}>
|
|
74
72
|
<ActionBarPrimitive.StopSpeaking>
|
|
75
73
|
<StopCircleIcon />
|
|
76
74
|
</ActionBarPrimitive.StopSpeaking>
|
|
77
|
-
|
|
75
|
+
</AuiIf>
|
|
78
76
|
<ActionBarPrimitive.Copy />
|
|
79
77
|
</ActionBarPrimitive.Root>
|
|
80
78
|
);
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Thread Virtualization
|
|
3
|
+
description: Render very long threads with @tanstack/react-virtual, with ThreadPrimitive.Unstable_MessageById and ThreadPrimitive.MessageByIndex.
|
|
4
|
+
platforms: ["react"]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Virtualization mounts only the messages near the viewport and represents the rest as empty space, so a thread with thousands of messages scrolls like one with twenty. assistant-ui does not ship a virtualized thread component; this guide shows the supported composition, extracted from a production consumer and available as a runnable example: [`examples/with-virtualized-thread`](https://github.com/assistant-ui/assistant-ui/tree/main/examples/with-virtualized-thread).
|
|
8
|
+
|
|
9
|
+
## Do you need this?
|
|
10
|
+
|
|
11
|
+
Probably not. The default kit already renders message bodies with `content-visibility: auto` and `contain-intrinsic-size`, which skips paint work for off-screen messages, and `ThreadPrimitive.Viewport` handles auto-scroll. That covers typical threads. Reach for virtualization when React mount and update cost itself becomes the bottleneck: threads with hundreds to thousands of messages, or very heavy per-message content, where typing latency degrades because every message stays mounted.
|
|
12
|
+
|
|
13
|
+
## Rendering Messages By Id
|
|
14
|
+
|
|
15
|
+
For virtualized rows, use message ids as the row identity. `unstable_useThreadMessageIds` returns the thread's ids with stable array identity across content-only updates, and `ThreadPrimitive.Unstable_MessageById` renders one message with the same `components` surface as `MessageByIndex`. Unknown ids render `null`, which is useful when a virtual row unmounts after a message was removed.
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
const MESSAGE_COMPONENTS = { UserMessage, AssistantMessage };
|
|
19
|
+
|
|
20
|
+
const messageIds = unstable_useThreadMessageIds();
|
|
21
|
+
|
|
22
|
+
return messageIds.map((messageId) => (
|
|
23
|
+
<ThreadPrimitive.Unstable_MessageById
|
|
24
|
+
key={messageId}
|
|
25
|
+
messageId={messageId}
|
|
26
|
+
components={MESSAGE_COMPONENTS}
|
|
27
|
+
/>
|
|
28
|
+
));
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
<Callout type="warn">
|
|
32
|
+
`unstable_useThreadMessageIds` and `ThreadPrimitive.Unstable_MessageById` are
|
|
33
|
+
experimental and may change in any release.
|
|
34
|
+
</Callout>
|
|
35
|
+
|
|
36
|
+
## Rendering Messages By Index
|
|
37
|
+
|
|
38
|
+
`ThreadPrimitive.MessageByIndex` is still supported and is the smaller API when
|
|
39
|
+
you already have a stable index from a fixed-order list. It renders a single
|
|
40
|
+
message at that index and memoizes on the index plus the per-field `components`
|
|
41
|
+
identity:
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
const MESSAGE_COMPONENTS = { UserMessage, AssistantMessage };
|
|
45
|
+
|
|
46
|
+
<ThreadPrimitive.MessageByIndex index={index} components={MESSAGE_COMPONENTS} />;
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
For virtualizers, prefer the id-based API above when you can. Index rows are more
|
|
50
|
+
fragile when messages are inserted, removed, reordered, or when a virtual row
|
|
51
|
+
briefly outlives the item it was created for.
|
|
52
|
+
|
|
53
|
+
## Grouping into turns
|
|
54
|
+
|
|
55
|
+
The example virtualizes per user turn (a user message plus the responses that follow it), which gives the virtualizer stable, meaningfully sized items. Keep the id and role together in a stable row shape so the turn array only rebuilds when message membership or roles change:
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
type MessageRow = {
|
|
59
|
+
id: string;
|
|
60
|
+
role: "user" | "assistant" | "system";
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const useThreadMessageRows = (): readonly MessageRow[] => {
|
|
64
|
+
const prevRowsRef = useRef<readonly MessageRow[]>([]);
|
|
65
|
+
|
|
66
|
+
return useAuiState((s) => {
|
|
67
|
+
const messages = s.thread.messages;
|
|
68
|
+
const prev = prevRowsRef.current;
|
|
69
|
+
if (
|
|
70
|
+
prev.length === messages.length &&
|
|
71
|
+
prev.every((row, index) => {
|
|
72
|
+
const message = messages[index]!;
|
|
73
|
+
return row.id === message.id && row.role === message.role;
|
|
74
|
+
})
|
|
75
|
+
) {
|
|
76
|
+
return prev;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const next = messages.map(({ id, role }) => ({ id, role }));
|
|
80
|
+
prevRowsRef.current = next;
|
|
81
|
+
return next;
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const messageRows = useThreadMessageRows();
|
|
86
|
+
const turns = useMemo(
|
|
87
|
+
() => buildTurns(messageRows),
|
|
88
|
+
[messageRows],
|
|
89
|
+
);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Then each virtual turn renders the ids it owns:
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
{turn.messageIds.map((messageId) => (
|
|
96
|
+
<ThreadPrimitive.Unstable_MessageById
|
|
97
|
+
key={messageId}
|
|
98
|
+
messageId={messageId}
|
|
99
|
+
components={MESSAGE_COMPONENTS}
|
|
100
|
+
/>
|
|
101
|
+
))}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Padding spacers, not absolute positioning
|
|
105
|
+
|
|
106
|
+
Render the virtual items in normal document flow inside a spacer div whose `paddingTop`/`paddingBottom` represent the unmounted regions. Items remain regular flow children, so message CSS (including `position: sticky` patterns and the kit styling) keeps working, and `virtualizer.measureElement` records real heights as items mount:
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
const items = virtualizer.getVirtualItems();
|
|
110
|
+
const paddingTop = items[0]?.start ?? 0;
|
|
111
|
+
const paddingBottom = Math.max(
|
|
112
|
+
0,
|
|
113
|
+
virtualizer.getTotalSize() - (items.at(-1)?.end ?? 0),
|
|
114
|
+
);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Owning the scroll element
|
|
118
|
+
|
|
119
|
+
The composition owns its scroll container instead of using `ThreadPrimitive.Viewport`: the built-in auto-scroll assumes every message is mounted, and its resize-driven re-pin can fight the virtualizer's measurement adjustments. Three pieces replace it:
|
|
120
|
+
|
|
121
|
+
1. **Auto-follow.** A ResizeObserver on the content wrapper re-pins the scroller to the bottom while a sticky flag is armed. The flag disarms when the user scrolls up (detected as `scrollTop` decreasing while `scrollHeight` and `clientHeight` are stable, the same heuristic the built-in viewport uses, plus wheel-up and touchmove) and re-arms when the user returns to the bottom.
|
|
122
|
+
2. **Measurement guard.** While pinned at the bottom, the virtualizer's own scroll adjustments on item re-measurement are suppressed via a custom `scrollToFn`; without this the two scroll writers fight and the view rubber-bands during streaming.
|
|
123
|
+
3. **Run-start jump.** A `useLayoutEffect` observes `s.thread.isRunning` flipping to true and jumps to the bottom before paint, so a just-sent message never flashes below the fold. The `thread.runStart` event is deprecated; deriving the transition from state is the supported path.
|
|
124
|
+
|
|
125
|
+
The production consumer this is extracted from disables streaming auto-follow entirely as a product choice; the example keeps following because it matches `ThreadPrimitive.Viewport`'s default behavior. Both are valid, and the disarm guard makes either safe.
|
|
126
|
+
|
|
127
|
+
## Try it
|
|
128
|
+
|
|
129
|
+
```sh
|
|
130
|
+
npx assistant-ui@latest create my-app
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Then copy `app/VirtualizedThread.tsx`, `app/MyRuntimeProvider.tsx`, and `app/seed-messages.ts` from [the example](https://github.com/assistant-ui/assistant-ui/tree/main/examples/with-virtualized-thread), or clone the repo and run `pnpm --filter with-virtualized-thread dev`.
|
|
@@ -156,8 +156,9 @@ class MyVoiceAdapter implements RealtimeVoiceAdapter {
|
|
|
156
156
|
|
|
157
157
|
The session status follows the same pattern as other adapters:
|
|
158
158
|
|
|
159
|
-
```
|
|
160
|
-
|
|
159
|
+
```mermaid
|
|
160
|
+
flowchart LR
|
|
161
|
+
starting["starting"] --> running["running"] --> ended["ended"]
|
|
161
162
|
```
|
|
162
163
|
|
|
163
164
|
The `ended` status includes a `reason`:
|
|
@@ -62,7 +62,7 @@ useAuiEvent("thread.runStart", (payload) => {
|
|
|
62
62
|
|
|
63
63
|
### useNotification
|
|
64
64
|
|
|
65
|
-
Ring the terminal bell and emit an OSC desktop notification when the assistant finishes a run, stops with an error, or pauses for
|
|
65
|
+
Ring the terminal bell and emit an OSC desktop notification when the assistant finishes a run, stops with an error, or pauses for user input.
|
|
66
66
|
|
|
67
67
|
```tsx
|
|
68
68
|
import { useNotification } from "@assistant-ui/react-ink";
|
|
@@ -130,7 +130,7 @@ const runtime = useLocalRuntime(chatModel, {
|
|
|
130
130
|
| `adapters.feedback` | `FeedbackAdapter` | Adapter for message feedback (thumbs up/down) |
|
|
131
131
|
| `adapters.suggestion` | `SuggestionAdapter` | Adapter for suggested prompts |
|
|
132
132
|
| `cloud` | `AssistantCloud` | Cloud instance for thread persistence via `@assistant-ui/cloud` |
|
|
133
|
-
| `unstable_humanToolNames` | `string[]` | Tool names that
|
|
133
|
+
| `unstable_humanToolNames` | `string[]` | Tool names that pause the run until a result is added via `addResult` |
|
|
134
134
|
|
|
135
135
|
### useRemoteThreadListRuntime
|
|
136
136
|
|
|
@@ -9,18 +9,10 @@ For the adapter contract itself, see [adapters](/docs/runtimes/concepts/adapters
|
|
|
9
9
|
|
|
10
10
|
## How it works
|
|
11
11
|
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
PendingAttachment with the public URL
|
|
17
|
-
│
|
|
18
|
-
composer send ◄────────────────────────────────────┘
|
|
19
|
-
│
|
|
20
|
-
└─► send() emits a content part with the URL
|
|
21
|
-
│
|
|
22
|
-
▼
|
|
23
|
-
AI SDK passes URL to the model
|
|
12
|
+
```mermaid
|
|
13
|
+
flowchart LR
|
|
14
|
+
add["composer add"] --> presign["POST /api/upload<br/>(presign)"] --> put["PUT to object storage"] --> pending["PendingAttachment<br/>with the public URL"]
|
|
15
|
+
pending --> send["composer send"] --> part["send() emits a<br/>content part with the URL"] --> model["AI SDK passes<br/>URL to the model"]
|
|
24
16
|
```
|
|
25
17
|
|
|
26
18
|
Three ideas to internalize before reading the code:
|
|
@@ -9,16 +9,13 @@ This page covers the non-cloud path. AssistantCloud users should see [cloud auth
|
|
|
9
9
|
|
|
10
10
|
## How it works
|
|
11
11
|
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
│
|
|
20
|
-
▼
|
|
21
|
-
scope queries by userId
|
|
12
|
+
```mermaid
|
|
13
|
+
flowchart LR
|
|
14
|
+
browser["browser"] --> routes["/api/auth/[...all]<br/>/api/chat<br/>/api/threads/*"]
|
|
15
|
+
routes --> handlers["better-auth handlers"]
|
|
16
|
+
routes --> session["auth.api.getSession({ headers })"]
|
|
17
|
+
handlers --> session
|
|
18
|
+
session --> uid["session.user.id"] --> scope["scope queries by userId"]
|
|
22
19
|
```
|
|
23
20
|
|
|
24
21
|
Three integration points:
|
|
@@ -9,15 +9,9 @@ If you use AssistantCloud, see [cloud authorization](/docs/cloud/authorization)
|
|
|
9
9
|
|
|
10
10
|
## How it works
|
|
11
11
|
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
│
|
|
16
|
-
▼
|
|
17
|
-
auth() returns { userId }
|
|
18
|
-
│
|
|
19
|
-
▼
|
|
20
|
-
scope queries by userId
|
|
12
|
+
```mermaid
|
|
13
|
+
flowchart LR
|
|
14
|
+
browser["browser"] --> mw["clerkMiddleware<br/>(proxy.ts)"] --> routes["/api/chat<br/>/api/threads/*"] --> auth["auth() returns { userId }"] --> scope["scope queries by userId"]
|
|
21
15
|
```
|
|
22
16
|
|
|
23
17
|
Three places Clerk touches the integration:
|
|
@@ -9,14 +9,9 @@ If you use AssistantCloud, see [cloud authorization](/docs/cloud) instead; cloud
|
|
|
9
9
|
|
|
10
10
|
## How it works
|
|
11
11
|
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
▼
|
|
16
|
-
scope queries by session.user.id
|
|
17
|
-
│
|
|
18
|
-
▼
|
|
19
|
-
database
|
|
12
|
+
```mermaid
|
|
13
|
+
flowchart LR
|
|
14
|
+
browser["browser"] --> routes["/api/chat<br/>/api/threads/*"] --> auth["auth() returns session"] --> scope["scope queries by<br/>session.user.id"] --> db["database"]
|
|
20
15
|
```
|
|
21
16
|
|
|
22
17
|
Three places auth touches the integration:
|
|
@@ -13,11 +13,14 @@ If you arrived here looking to wire up your first chat: jump to [AI SDK v6 quick
|
|
|
13
13
|
|
|
14
14
|
## Where it slots in
|
|
15
15
|
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
```mermaid
|
|
17
|
+
flowchart LR
|
|
18
|
+
client["client"]
|
|
19
|
+
runtime["useChatRuntime (react-ai-sdk)<br/>(thread state · tool calls · attachments<br/>speech · dictation · feedback adapters)"]
|
|
20
|
+
api["/api/chat<br/>(streamText)"]
|
|
21
|
+
provider["provider"]
|
|
22
|
+
|
|
23
|
+
client --> runtime --> api --> provider
|
|
21
24
|
```
|
|
22
25
|
|
|
23
26
|
`@assistant-ui/react-ai-sdk` wraps the AI SDK's `useChat` hook and exposes it as an assistant-ui runtime. The runtime owns conversation state on the client; your `/api/chat` route returns a UI message stream from `streamText`. Everything else (tools, attachments, observability, gateways, custom persistence) layers on top of this base.
|