@assistant-ui/mcp-docs-server 0.1.22 → 0.1.24

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.
Files changed (75) hide show
  1. package/.docs/organized/code-examples/waterfall.md +801 -0
  2. package/.docs/organized/code-examples/with-ag-ui.md +39 -27
  3. package/.docs/organized/code-examples/with-ai-sdk-v6.md +39 -29
  4. package/.docs/organized/code-examples/with-artifacts.md +467 -0
  5. package/.docs/organized/code-examples/with-assistant-transport.md +32 -25
  6. package/.docs/organized/code-examples/with-chain-of-thought.md +42 -33
  7. package/.docs/organized/code-examples/with-cloud-standalone.md +674 -0
  8. package/.docs/organized/code-examples/with-cloud.md +35 -28
  9. package/.docs/organized/code-examples/with-custom-thread-list.md +35 -28
  10. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +42 -31
  11. package/.docs/organized/code-examples/with-expo.md +2012 -0
  12. package/.docs/organized/code-examples/with-external-store.md +32 -26
  13. package/.docs/organized/code-examples/with-ffmpeg.md +32 -28
  14. package/.docs/organized/code-examples/with-langgraph.md +97 -39
  15. package/.docs/organized/code-examples/with-parent-id-grouping.md +33 -26
  16. package/.docs/organized/code-examples/with-react-hook-form.md +63 -61
  17. package/.docs/organized/code-examples/with-react-router.md +38 -31
  18. package/.docs/organized/code-examples/with-store.md +17 -25
  19. package/.docs/organized/code-examples/with-tanstack.md +36 -26
  20. package/.docs/organized/code-examples/with-tap-runtime.md +11 -25
  21. package/.docs/raw/docs/(docs)/cli.mdx +13 -6
  22. package/.docs/raw/docs/(docs)/guides/attachments.mdx +26 -3
  23. package/.docs/raw/docs/(docs)/guides/chain-of-thought.mdx +5 -5
  24. package/.docs/raw/docs/(docs)/guides/context-api.mdx +53 -52
  25. package/.docs/raw/docs/(docs)/guides/dictation.mdx +0 -2
  26. package/.docs/raw/docs/(docs)/guides/message-timing.mdx +169 -0
  27. package/.docs/raw/docs/(docs)/guides/quoting.mdx +327 -0
  28. package/.docs/raw/docs/(docs)/guides/speech.mdx +0 -1
  29. package/.docs/raw/docs/(docs)/index.mdx +12 -2
  30. package/.docs/raw/docs/(docs)/installation.mdx +8 -2
  31. package/.docs/raw/docs/(docs)/llm.mdx +9 -7
  32. package/.docs/raw/docs/(reference)/api-reference/primitives/action-bar-more.mdx +1 -1
  33. package/.docs/raw/docs/(reference)/api-reference/primitives/action-bar.mdx +2 -2
  34. package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-if.mdx +27 -27
  35. package/.docs/raw/docs/(reference)/api-reference/primitives/composer.mdx +60 -0
  36. package/.docs/raw/docs/(reference)/api-reference/primitives/message-part.mdx +78 -4
  37. package/.docs/raw/docs/(reference)/api-reference/primitives/message.mdx +32 -0
  38. package/.docs/raw/docs/(reference)/api-reference/primitives/selection-toolbar.mdx +61 -0
  39. package/.docs/raw/docs/(reference)/api-reference/primitives/thread.mdx +1 -1
  40. package/.docs/raw/docs/(reference)/legacy/styled/assistant-modal.mdx +1 -6
  41. package/.docs/raw/docs/(reference)/legacy/styled/decomposition.mdx +2 -2
  42. package/.docs/raw/docs/(reference)/legacy/styled/markdown.mdx +1 -6
  43. package/.docs/raw/docs/(reference)/legacy/styled/thread.mdx +1 -5
  44. package/.docs/raw/docs/(reference)/migrations/v0-12.mdx +17 -17
  45. package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +209 -0
  46. package/.docs/raw/docs/cloud/ai-sdk.mdx +296 -0
  47. package/.docs/raw/docs/cloud/authorization.mdx +178 -79
  48. package/.docs/raw/docs/cloud/{persistence/langgraph.mdx → langgraph.mdx} +2 -2
  49. package/.docs/raw/docs/cloud/overview.mdx +29 -39
  50. package/.docs/raw/docs/react-native/adapters.mdx +118 -0
  51. package/.docs/raw/docs/react-native/custom-backend.mdx +210 -0
  52. package/.docs/raw/docs/react-native/hooks.mdx +364 -0
  53. package/.docs/raw/docs/react-native/index.mdx +332 -0
  54. package/.docs/raw/docs/react-native/primitives.mdx +653 -0
  55. package/.docs/raw/docs/runtimes/ai-sdk/v6.mdx +60 -15
  56. package/.docs/raw/docs/runtimes/assistant-transport.mdx +103 -0
  57. package/.docs/raw/docs/runtimes/custom/external-store.mdx +25 -2
  58. package/.docs/raw/docs/runtimes/data-stream.mdx +1 -3
  59. package/.docs/raw/docs/runtimes/langgraph/index.mdx +113 -9
  60. package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +1 -4
  61. package/.docs/raw/docs/ui/attachment.mdx +4 -2
  62. package/.docs/raw/docs/ui/context-display.mdx +147 -0
  63. package/.docs/raw/docs/ui/message-timing.mdx +92 -0
  64. package/.docs/raw/docs/ui/part-grouping.mdx +1 -1
  65. package/.docs/raw/docs/ui/reasoning.mdx +4 -4
  66. package/.docs/raw/docs/ui/scrollbar.mdx +2 -2
  67. package/.docs/raw/docs/ui/syntax-highlighting.mdx +55 -50
  68. package/.docs/raw/docs/ui/thread.mdx +16 -9
  69. package/dist/index.d.ts +1 -1
  70. package/dist/index.d.ts.map +1 -1
  71. package/package.json +3 -3
  72. package/src/tools/tests/integration.test.ts +2 -2
  73. package/src/tools/tests/json-parsing.test.ts +1 -1
  74. package/src/tools/tests/mcp-protocol.test.ts +1 -3
  75. package/.docs/raw/docs/cloud/persistence/ai-sdk.mdx +0 -108
@@ -0,0 +1,327 @@
1
+ ---
2
+ title: Quote Selected Text
3
+ description: Let users select and quote text from messages, similar to Claude's quoting experience.
4
+ ---
5
+
6
+ Allow users to select text in assistant messages and reply with a quote reference — just like Claude, ChatGPT, and other modern AI interfaces.
7
+
8
+ ## How It Works
9
+
10
+ 1. User selects text in a message
11
+ 2. A floating toolbar appears near the selection with a **Quote** button
12
+ 3. User clicks it — a quote preview appears in the composer
13
+ 4. User types their reply and sends
14
+ 5. The sent message displays the quoted text above the user's reply
15
+
16
+ Quote data is stored in `metadata.custom.quote` and is **not** injected into message content. Your backend decides how to present the quoted context to the LLM.
17
+
18
+ ## Quick Start
19
+
20
+ ### 1. Add the Floating Selection Toolbar
21
+
22
+ Place `SelectionToolbarPrimitive` inside your `ThreadPrimitive.Root`. It renders a floating toolbar near the user's text selection — only when text is selected within a message.
23
+
24
+ ```tsx {1,13-20}
25
+ import { SelectionToolbarPrimitive, ThreadPrimitive } from "@assistant-ui/react";
26
+ import { QuoteIcon } from "lucide-react";
27
+
28
+ const Thread = () => {
29
+ return (
30
+ <ThreadPrimitive.Root>
31
+ <ThreadPrimitive.Viewport>
32
+ <ThreadPrimitive.Messages components={{ ... }} />
33
+ ...
34
+ </ThreadPrimitive.Viewport>
35
+
36
+ {/* Floating toolbar — appears on text selection */}
37
+ <SelectionToolbarPrimitive.Root className="flex items-center gap-1 rounded-lg border bg-popover px-1 py-1 shadow-md">
38
+ <SelectionToolbarPrimitive.Quote className="flex items-center gap-1.5 rounded-md px-2.5 py-1 text-sm hover:bg-accent">
39
+ <QuoteIcon className="size-3.5" />
40
+ Quote
41
+ </SelectionToolbarPrimitive.Quote>
42
+ </SelectionToolbarPrimitive.Root>
43
+ </ThreadPrimitive.Root>
44
+ );
45
+ };
46
+ ```
47
+
48
+ The `Root` component:
49
+ - Listens for `mouseup` and `keyup` events to detect text selections
50
+ - Validates the selection is within a single message (cross-message selections are ignored)
51
+ - Renders a portal positioned above the selection
52
+ - Prevents `mousedown` from clearing the selection when clicking the toolbar
53
+ - Hides automatically on scroll or when the selection is cleared
54
+
55
+ ### 2. Show Quote Preview in Composer
56
+
57
+ Add `ComposerPrimitive.Quote` inside the composer to show what's being quoted:
58
+
59
+ ```tsx {1,8-13}
60
+ import { ComposerPrimitive } from "@assistant-ui/react";
61
+
62
+ const Composer = () => {
63
+ return (
64
+ <ComposerPrimitive.Root>
65
+ {/* Quote preview — only renders when a quote is set */}
66
+ <ComposerPrimitive.Quote className="flex items-start gap-2 border-l-4 border-primary/40 bg-muted/50 px-3 py-2 text-sm">
67
+ <ComposerPrimitive.QuoteText className="line-clamp-2 flex-1 italic text-muted-foreground" />
68
+ <ComposerPrimitive.QuoteDismiss>
69
+ ×
70
+ </ComposerPrimitive.QuoteDismiss>
71
+ </ComposerPrimitive.Quote>
72
+
73
+ <ComposerPrimitive.Input placeholder="Send a message..." />
74
+ <ComposerPrimitive.Send />
75
+ </ComposerPrimitive.Root>
76
+ );
77
+ };
78
+ ```
79
+
80
+ ### 3. Display Quotes in Sent Messages
81
+
82
+ Use `useMessageQuote()` to render quoted text in user messages:
83
+
84
+ ```tsx {1,4-5,11}
85
+ import { MessagePrimitive, useMessageQuote } from "@assistant-ui/react";
86
+
87
+ const QuoteBlock = () => {
88
+ const quote = useMessageQuote();
89
+ if (!quote) return null;
90
+
91
+ return (
92
+ <div className="mb-1 border-l-4 border-primary/40 pl-3 text-xs italic text-muted-foreground">
93
+ {quote.text}
94
+ </div>
95
+ );
96
+ };
97
+
98
+ const UserMessage = () => {
99
+ return (
100
+ <MessagePrimitive.Root>
101
+ <QuoteBlock />
102
+ <MessagePrimitive.Parts />
103
+ </MessagePrimitive.Root>
104
+ );
105
+ };
106
+ ```
107
+
108
+ ## Backend Handling
109
+
110
+ The quote is stored in message metadata — **not** in message content. This gives your backend full control over how to present quoted context to the LLM.
111
+
112
+ ### Data Shape
113
+
114
+ ```typescript
115
+ type QuoteInfo = {
116
+ readonly text: string; // selected plain text
117
+ readonly messageId: string; // source message ID
118
+ };
119
+
120
+ // Stored at: message.metadata.custom.quote
121
+ ```
122
+
123
+ ### Example: Prepend as Markdown Blockquote
124
+
125
+ A simple approach is to prepend the quoted text as a `>` blockquote before converting to model messages:
126
+
127
+ ```typescript title="app/api/chat/route.ts" {1,9}
128
+ import { convertToModelMessages, streamText } from "ai";
129
+ import type { UIMessage } from "ai";
130
+
131
+ export async function POST(req: Request) {
132
+ const { messages } = await req.json();
133
+
134
+ const result = streamText({
135
+ model: myModel,
136
+ messages: await convertToModelMessages(injectQuoteContext(messages)),
137
+ });
138
+
139
+ return result.toUIMessageStreamResponse();
140
+ }
141
+
142
+ function injectQuoteContext(messages: UIMessage[]): UIMessage[] {
143
+ return messages.map((msg) => {
144
+ const quote = (msg.metadata as Record<string, unknown>)?.custom;
145
+ if (
146
+ !quote ||
147
+ typeof quote !== "object" ||
148
+ !("quote" in (quote as Record<string, unknown>))
149
+ )
150
+ return msg;
151
+
152
+ const q = (quote as Record<string, unknown>).quote;
153
+ if (
154
+ !q ||
155
+ typeof q !== "object" ||
156
+ !("text" in (q as Record<string, unknown>))
157
+ )
158
+ return msg;
159
+
160
+ const text = (q as { text: unknown }).text;
161
+ if (typeof text !== "string") return msg;
162
+
163
+ return {
164
+ ...msg,
165
+ parts: [{ type: "text" as const, text: `> ${text}\n\n` }, ...msg.parts],
166
+ };
167
+ });
168
+ }
169
+ ```
170
+
171
+ ### Example: Claude-Style Citation Source
172
+
173
+ For Claude's API, you can pass the quoted text as a citation source. This enables Claude to produce citations that reference the quoted text:
174
+
175
+ ```typescript title="app/api/chat/route.ts"
176
+ import Anthropic from "@anthropic-ai/sdk";
177
+
178
+ const client = new Anthropic();
179
+
180
+ export async function POST(req: Request) {
181
+ const { messages } = await req.json();
182
+
183
+ // Transform messages: extract quotes into Claude source blocks
184
+ const claudeMessages = messages.map((msg) => {
185
+ const quote = msg.metadata?.custom?.quote;
186
+ if (!quote?.text) {
187
+ return { role: msg.role, content: extractText(msg) };
188
+ }
189
+
190
+ return {
191
+ role: "user",
192
+ content: [
193
+ {
194
+ type: "text",
195
+ text: quote.text,
196
+ cache_control: { type: "ephemeral" },
197
+ citations: { enabled: true },
198
+ },
199
+ {
200
+ type: "text",
201
+ text: extractText(msg),
202
+ },
203
+ ],
204
+ };
205
+ });
206
+
207
+ const response = await client.messages.create({
208
+ model: "claude-sonnet-4-5-20250929",
209
+ max_tokens: 1024,
210
+ messages: claudeMessages,
211
+ });
212
+
213
+ // ... stream response back
214
+ }
215
+ ```
216
+
217
+ ### Example: OpenAI-Style System Context
218
+
219
+ For OpenAI's API, inject the quote as additional context in the user message:
220
+
221
+ ```typescript title="app/api/chat/route.ts"
222
+ function injectQuoteForOpenAI(messages) {
223
+ return messages.map((msg) => {
224
+ const quote = msg.metadata?.custom?.quote;
225
+ if (!quote?.text || msg.role !== "user") return msg;
226
+
227
+ return {
228
+ ...msg,
229
+ content: `[Referring to: "${quote.text}"]\n\n${msg.content}`,
230
+ };
231
+ });
232
+ }
233
+ ```
234
+
235
+ ## Programmatic API
236
+
237
+ You can set or clear quotes programmatically via the composer runtime:
238
+
239
+ ```tsx
240
+ import { useAui } from "@assistant-ui/react";
241
+
242
+ function MyComponent() {
243
+ const aui = useAui();
244
+
245
+ const quoteText = () => {
246
+ aui.thread().composer.setQuote({
247
+ text: "The text to quote",
248
+ messageId: "msg-123",
249
+ });
250
+ };
251
+
252
+ const clearQuote = () => {
253
+ aui.thread().composer.setQuote(undefined);
254
+ };
255
+
256
+ return (
257
+ <>
258
+ <button onClick={quoteText}>Set Quote</button>
259
+ <button onClick={clearQuote}>Clear Quote</button>
260
+ </>
261
+ );
262
+ }
263
+ ```
264
+
265
+ ## API Reference
266
+
267
+ ### SelectionToolbarPrimitive.Root
268
+
269
+ A floating container that appears when text is selected within a message. Renders as a portal positioned above the selection.
270
+
271
+ - Listens for `mouseup` / `keyup` to detect selection
272
+ - Validates selection is within a single message
273
+ - Hides on scroll or when selection is cleared
274
+ - Prevents `mousedown` from clearing the selection
275
+ - Provides selection context to child components
276
+
277
+ ### SelectionToolbarPrimitive.Quote
278
+
279
+ A button inside the floating toolbar that captures the selection as a quote.
280
+
281
+ - Reads selection info from the toolbar context (not `window.getSelection()`)
282
+ - Stores `{ text, messageId }` in the thread composer
283
+ - Clears the text selection after quoting
284
+
285
+ ### ComposerPrimitive.Quote
286
+
287
+ A container that only renders when a quote is set.
288
+
289
+ ### ComposerPrimitive.QuoteText
290
+
291
+ Renders the quoted text. Defaults to `<span>`.
292
+
293
+ ### ComposerPrimitive.QuoteDismiss
294
+
295
+ A button that clears the quote by calling `setQuote(undefined)`. Supports `asChild`.
296
+
297
+ ### useMessageQuote()
298
+
299
+ ```tsx
300
+ const quote: QuoteInfo | undefined = useMessageQuote();
301
+ ```
302
+
303
+ Returns the quote attached to the current message, or `undefined`.
304
+
305
+ ### ComposerRuntime.setQuote()
306
+
307
+ ```tsx
308
+ setQuote(quote: QuoteInfo | undefined): void
309
+ ```
310
+
311
+ Set or clear the quote on the composer. The quote is automatically cleared when the message is sent.
312
+
313
+ ## Design Notes
314
+
315
+ - **Single quote** — `setQuote` replaces, not appends. Only one quote at a time.
316
+ - **Snapshot text** — The selected text is captured at quote time, not linked to the source message.
317
+ - **Cross-message selection** — Rejected. The toolbar only appears when the selection is within a single message.
318
+ - **Streaming messages** — The floating toolbar works during streaming since it reads from the captured selection, not message status.
319
+ - **`isEmpty` unchanged** — A quote alone doesn't make the composer non-empty. The user must type a reply.
320
+ - **Scroll hides toolbar** — The floating toolbar hides when any scroll event occurs, since the position would become stale.
321
+
322
+ ## Related
323
+
324
+ - [Message Editing](/docs/guides/editing) — Edit user messages
325
+ - [Thread Component](/docs/ui/thread) — Main chat container
326
+ - [ComposerPrimitive](/docs/reference/primitives/Composer) — Composer primitive reference
327
+ - [ActionBarPrimitive](/docs/reference/primitives/ActionBar) — Action bar primitive reference
@@ -34,7 +34,6 @@ The following example uses the `WebSpeechSynthesisAdapter`.
34
34
  import { WebSpeechSynthesisAdapter } from "@assistant-ui/react";
35
35
 
36
36
  const runtime = useChatRuntime({
37
- api: "/api/chat",
38
37
  adapters: {
39
38
  speech: new WebSpeechSynthesisAdapter(),
40
39
  },
@@ -7,6 +7,10 @@ import { Sparkles, PanelsTopLeft, Database, Terminal, Bot } from "lucide-react";
7
7
 
8
8
  assistant-ui helps you create beautiful, enterprise-grade AI chat interfaces in minutes. Whether you're building a ChatGPT clone, a customer support chatbot, an AI assistant, or a complex multi-agent application, assistant-ui provides the frontend primitive components and state management layers to focus on what makes your application unique.
9
9
 
10
+ <Callout type="tip">
11
+ Already using the AI SDK with your own UI? Add [cloud persistence with just one hook](/docs/cloud/ai-sdk), no UI library required.
12
+ </Callout>
13
+
10
14
  ## Key Features
11
15
 
12
16
  <Cards>
@@ -38,13 +42,19 @@ npx assistant-ui@latest create
38
42
  This creates a new project with everything configured. Or choose a template:
39
43
 
40
44
  ```sh
45
+ # Minimal starter
46
+ npx assistant-ui@latest create -t minimal
47
+
41
48
  # Assistant Cloud - with persistence and thread management
42
49
  npx assistant-ui@latest create -t cloud
43
50
 
44
- # LangGraph
51
+ # Assistant Cloud + Clerk authentication
52
+ npx assistant-ui@latest create -t cloud-clerk
53
+
54
+ # LangGraph starter template
45
55
  npx assistant-ui@latest create -t langgraph
46
56
 
47
- # MCP support
57
+ # MCP starter template
48
58
  npx assistant-ui@latest create -t mcp
49
59
  ```
50
60
 
@@ -24,13 +24,19 @@ npx assistant-ui@latest create
24
24
  Or choose a template:
25
25
 
26
26
  ```sh
27
+ # Minimal starter
28
+ npx assistant-ui@latest create -t minimal
29
+
27
30
  # Assistant Cloud - with persistence and thread management
28
31
  npx assistant-ui@latest create -t cloud
29
32
 
30
- # LangGraph
33
+ # Assistant Cloud + Clerk authentication
34
+ npx assistant-ui@latest create -t cloud-clerk
35
+
36
+ # LangGraph starter template
31
37
  npx assistant-ui@latest create -t langgraph
32
38
 
33
- # MCP support
39
+ # MCP starter template
34
40
  npx assistant-ui@latest create -t mcp
35
41
  ```
36
42
 
@@ -78,18 +78,20 @@ Once installed, your AI assistant will understand everything about assistant-ui
78
78
  ### Quick Install (CLI)
79
79
 
80
80
  ```bash
81
- npx assistant-ui mcp
81
+ npx add-mcp @assistant-ui/mcp-docs-server
82
82
  ```
83
83
 
84
84
  Or specify your IDE directly:
85
85
 
86
86
  ```bash
87
- npx assistant-ui mcp --cursor
88
- npx assistant-ui mcp --windsurf
89
- npx assistant-ui mcp --vscode
90
- npx assistant-ui mcp --zed
91
- npx assistant-ui mcp --claude-code
92
- npx assistant-ui mcp --claude-desktop
87
+ npx add-mcp @assistant-ui/mcp-docs-server -a claude-code
88
+ npx add-mcp @assistant-ui/mcp-docs-server -a claude-desktop
89
+ npx add-mcp @assistant-ui/mcp-docs-server -a codex
90
+ npx add-mcp @assistant-ui/mcp-docs-server -a cursor
91
+ npx add-mcp @assistant-ui/mcp-docs-server -a gemini-cli
92
+ npx add-mcp @assistant-ui/mcp-docs-server -a opencode
93
+ npx add-mcp @assistant-ui/mcp-docs-server -a vscode
94
+ npx add-mcp @assistant-ui/mcp-docs-server -a zed
93
95
  ```
94
96
 
95
97
  ### Manual Installation
@@ -261,7 +261,7 @@ import { useCallback } from "react";
261
261
 
262
262
  const useEditAction = () => {
263
263
  const aui = useAui();
264
- const disabled = useAuiState(({ composer }) => composer.isEditing);
264
+ const disabled = useAuiState((s) => s.composer.isEditing);
265
265
  const callback = useCallback(() => aui.composer().beginEdit(), [aui]);
266
266
  if (disabled) return null;
267
267
  return callback;
@@ -156,10 +156,10 @@ Show a different icon for a few seconds after the message is copied.
156
156
 
157
157
  ```tsx
158
158
  <ActionBarPrimitive.Copy>
159
- <AuiIf condition={({ message }) => !message.isCopied}>
159
+ <AuiIf condition={(s) => !s.message.isCopied}>
160
160
  <CopyIcon />
161
161
  </AuiIf>
162
- <AuiIf condition={({ message }) => message.isCopied}>
162
+ <AuiIf condition={(s) => s.message.isCopied}>
163
163
  <CopySuccessIcon />
164
164
  </AuiIf>
165
165
  </ActionBarPrimitive.Copy>
@@ -11,7 +11,7 @@ Conditionally render children based on assistant state.
11
11
  ```tsx
12
12
  import { AuiIf } from "@assistant-ui/react";
13
13
 
14
- <AuiIf condition={({ thread }) => thread.isEmpty}>
14
+ <AuiIf condition={(s) => s.thread.isEmpty}>
15
15
  <WelcomeScreen />
16
16
  </AuiIf>
17
17
  ```
@@ -80,20 +80,20 @@ The condition function receives an `AssistantState` object with the following pr
80
80
 
81
81
  ```tsx
82
82
  // Show welcome screen when thread is empty
83
- <AuiIf condition={({ thread }) => thread.isEmpty}>
83
+ <AuiIf condition={(s) => s.thread.isEmpty}>
84
84
  <WelcomeScreen />
85
85
  </AuiIf>
86
86
 
87
87
  // Show loading indicator while running
88
- <AuiIf condition={({ thread }) => thread.isRunning}>
88
+ <AuiIf condition={(s) => s.thread.isRunning}>
89
89
  <LoadingSpinner />
90
90
  </AuiIf>
91
91
 
92
92
  // Conditional send/cancel button
93
- <AuiIf condition={({ thread }) => !thread.isRunning}>
93
+ <AuiIf condition={(s) => !s.thread.isRunning}>
94
94
  <ComposerPrimitive.Send>Send</ComposerPrimitive.Send>
95
95
  </AuiIf>
96
- <AuiIf condition={({ thread }) => thread.isRunning}>
96
+ <AuiIf condition={(s) => s.thread.isRunning}>
97
97
  <ComposerPrimitive.Cancel>Cancel</ComposerPrimitive.Cancel>
98
98
  </AuiIf>
99
99
  ```
@@ -102,32 +102,32 @@ The condition function receives an `AssistantState` object with the following pr
102
102
 
103
103
  ```tsx
104
104
  // Show avatar only for assistant messages
105
- <AuiIf condition={({ message }) => message.role === "assistant"}>
105
+ <AuiIf condition={(s) => s.message.role === "assistant"}>
106
106
  <AssistantAvatar />
107
107
  </AuiIf>
108
108
 
109
109
  // Show disclaimer on last message
110
- <AuiIf condition={({ message }) => message.isLast}>
110
+ <AuiIf condition={(s) => s.message.isLast}>
111
111
  <Disclaimer />
112
112
  </AuiIf>
113
113
 
114
114
  // Toggle copy icon based on copied state
115
115
  <ActionBarPrimitive.Copy>
116
- <AuiIf condition={({ message }) => !message.isCopied}>
116
+ <AuiIf condition={(s) => !s.message.isCopied}>
117
117
  <CopyIcon />
118
118
  </AuiIf>
119
- <AuiIf condition={({ message }) => message.isCopied}>
119
+ <AuiIf condition={(s) => s.message.isCopied}>
120
120
  <CheckIcon />
121
121
  </AuiIf>
122
122
  </ActionBarPrimitive.Copy>
123
123
 
124
124
  // Show speak/stop button based on speech state
125
- <AuiIf condition={({ message }) => message.speech == null}>
125
+ <AuiIf condition={(s) => s.message.speech == null}>
126
126
  <ActionBarPrimitive.Speak>
127
127
  <SpeakIcon />
128
128
  </ActionBarPrimitive.Speak>
129
129
  </AuiIf>
130
- <AuiIf condition={({ message }) => message.speech != null}>
130
+ <AuiIf condition={(s) => s.message.speech != null}>
131
131
  <ActionBarPrimitive.StopSpeaking>
132
132
  <StopIcon />
133
133
  </ActionBarPrimitive.StopSpeaking>
@@ -138,12 +138,12 @@ The condition function receives an `AssistantState` object with the following pr
138
138
 
139
139
  ```tsx
140
140
  // Show placeholder when composer is empty
141
- <AuiIf condition={({ composer }) => composer.isEmpty}>
141
+ <AuiIf condition={(s) => s.composer.isEmpty}>
142
142
  <PlaceholderText />
143
143
  </AuiIf>
144
144
 
145
145
  // Show attachment preview when editing
146
- <AuiIf condition={({ composer }) => composer.isEditing}>
146
+ <AuiIf condition={(s) => s.composer.isEditing}>
147
147
  <EditingIndicator />
148
148
  </AuiIf>
149
149
  ```
@@ -152,15 +152,15 @@ The condition function receives an `AssistantState` object with the following pr
152
152
 
153
153
  ```tsx
154
154
  // Combine multiple conditions
155
- <AuiIf condition={({ thread, message }) =>
156
- !thread.isRunning && message.role === "assistant"
155
+ <AuiIf condition={(s) =>
156
+ !s.thread.isRunning && s.message.role === "assistant"
157
157
  }>
158
158
  <ActionBar />
159
159
  </AuiIf>
160
160
 
161
161
  // Custom logic
162
- <AuiIf condition={({ thread }) =>
163
- thread.messages.length > 0 && !thread.isRunning
162
+ <AuiIf condition={(s) =>
163
+ s.thread.messages.length > 0 && !s.thread.isRunning
164
164
  }>
165
165
  <FollowUpSuggestions />
166
166
  </AuiIf>
@@ -173,7 +173,7 @@ You can import the `AuiIf.Condition` type for typing your condition functions:
173
173
  ```tsx
174
174
  import { AuiIf } from "@assistant-ui/react";
175
175
 
176
- const isThreadEmpty: AuiIf.Condition = ({ thread }) => thread.isEmpty;
176
+ const isThreadEmpty: AuiIf.Condition = (s) => s.thread.isEmpty;
177
177
 
178
178
  <AuiIf condition={isThreadEmpty}>
179
179
  <WelcomeScreen />
@@ -188,12 +188,12 @@ const isThreadEmpty: AuiIf.Condition = ({ thread }) => thread.isEmpty;
188
188
 
189
189
  | Before | After |
190
190
  |--------|-------|
191
- | `<ThreadPrimitive.If empty>` | `<AuiIf condition={({ thread }) => thread.isEmpty}>` |
192
- | `<ThreadPrimitive.If running>` | `<AuiIf condition={({ thread }) => thread.isRunning}>` |
193
- | `<ThreadPrimitive.If running={false}>` | `<AuiIf condition={({ thread }) => !thread.isRunning}>` |
194
- | `<MessagePrimitive.If user>` | `<AuiIf condition={({ message }) => message.role === "user"}>` |
195
- | `<MessagePrimitive.If assistant>` | `<AuiIf condition={({ message }) => message.role === "assistant"}>` |
196
- | `<MessagePrimitive.If copied>` | `<AuiIf condition={({ message }) => message.isCopied}>` |
197
- | `<MessagePrimitive.If speaking>` | `<AuiIf condition={({ message }) => message.speech != null}>` |
198
- | `<MessagePrimitive.If last>` | `<AuiIf condition={({ message }) => message.isLast}>` |
199
- | `<ComposerPrimitive.If editing>` | `<AuiIf condition={({ composer }) => composer.isEditing}>` |
191
+ | `<ThreadPrimitive.If empty>` | `<AuiIf condition={(s) => s.thread.isEmpty}>` |
192
+ | `<ThreadPrimitive.If running>` | `<AuiIf condition={(s) => s.thread.isRunning}>` |
193
+ | `<ThreadPrimitive.If running={false}>` | `<AuiIf condition={(s) => !s.thread.isRunning}>` |
194
+ | `<MessagePrimitive.If user>` | `<AuiIf condition={(s) => s.message.role === "user"}>` |
195
+ | `<MessagePrimitive.If assistant>` | `<AuiIf condition={(s) => s.message.role === "assistant"}>` |
196
+ | `<MessagePrimitive.If copied>` | `<AuiIf condition={(s) => s.message.isCopied}>` |
197
+ | `<MessagePrimitive.If speaking>` | `<AuiIf condition={(s) => s.message.speech != null}>` |
198
+ | `<MessagePrimitive.If last>` | `<AuiIf condition={(s) => s.message.isLast}>` |
199
+ | `<ComposerPrimitive.If editing>` | `<AuiIf condition={(s) => s.composer.isEditing}>` |
@@ -20,6 +20,10 @@ import { ComposerPrimitive } from "@assistant-ui/react";
20
20
  // creating a new message
21
21
  const Composer = () => (
22
22
  <ComposerPrimitive.Root>
23
+ <ComposerPrimitive.Quote>
24
+ <ComposerPrimitive.QuoteText />
25
+ <ComposerPrimitive.QuoteDismiss />
26
+ </ComposerPrimitive.Quote>
23
27
  <ComposerPrimitive.Attachments />
24
28
  <ComposerPrimitive.AddAttachment />
25
29
  <ComposerPrimitive.Input />
@@ -80,14 +84,32 @@ This primitive renders a `<textarea>` element unless `asChild` is set.
80
84
  {
81
85
  name: "asChild",
82
86
  },
87
+ {
88
+ name: "submitMode",
89
+ type: '"enter" | "ctrlEnter" | "none"',
90
+ default: '"enter"',
91
+ description:
92
+ 'Controls how the Enter key submits messages. "enter": plain Enter submits (Shift+Enter for newline). "ctrlEnter": Ctrl/Cmd+Enter submits (plain Enter for newline). "none": keyboard submission disabled.',
93
+ },
83
94
  ]}
84
95
  />
85
96
 
86
97
  #### Keyboard Shortcuts
87
98
 
99
+ Default (`submitMode="enter"`):
100
+
88
101
  | Key | Description |
89
102
  | --- | --- |
90
103
  | <Kbd>Enter</Kbd> | Sends the message. |
104
+ | <Kbd>Shift + Enter</Kbd> | Inserts a newline. |
105
+ | <Kbd>Escape</Kbd> | Sends a cancel action. |
106
+
107
+ With `submitMode="ctrlEnter"`:
108
+
109
+ | Key | Description |
110
+ | --- | --- |
111
+ | <Kbd>Ctrl/Cmd + Enter</Kbd> | Sends the message. |
112
+ | <Kbd>Enter</Kbd> | Inserts a newline. |
91
113
  | <Kbd>Escape</Kbd> | Sends a cancel action. |
92
114
 
93
115
  ### Send
@@ -274,6 +296,44 @@ This primitive renders a `<span>` element.
274
296
  </ComposerPrimitive.If>
275
297
  ```
276
298
 
299
+ ### Quote
300
+
301
+ A container for displaying a quote preview in the composer. Only renders when a quote is set via `composer.setQuote()`.
302
+
303
+ This primitive renders a `<div>` element.
304
+
305
+ ```tsx
306
+ <ComposerPrimitive.Quote className="flex items-start gap-2 bg-muted/60 px-3 py-2">
307
+ <ComposerPrimitive.QuoteText className="line-clamp-2 flex-1 text-sm" />
308
+ <ComposerPrimitive.QuoteDismiss>×</ComposerPrimitive.QuoteDismiss>
309
+ </ComposerPrimitive.Quote>
310
+ ```
311
+
312
+ ### QuoteText
313
+
314
+ Renders the quoted text content. Only renders when a quote is set.
315
+
316
+ This primitive renders a `<span>` element.
317
+
318
+ ### QuoteDismiss
319
+
320
+ A button that clears the current quote from the composer by calling `setQuote(undefined)`.
321
+
322
+ This primitive renders a `<button>` element unless `asChild` is set.
323
+
324
+ <ParametersTable
325
+ type="ComposerPrimitiveQuoteDismissProps"
326
+ parameters={[
327
+ {
328
+ name: "asChild",
329
+ },
330
+ ]}
331
+ />
332
+
333
+ <Callout type="info">
334
+ See the [Quoting guide](/docs/guides/quoting) for a complete walkthrough including the floating selection toolbar and backend handling.
335
+ </Callout>
336
+
277
337
  ### If
278
338
 
279
339
  Renders children if a condition is met.