@assistant-ui/mcp-docs-server 0.1.3 → 0.1.5
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/local-ollama.md +13 -13
- package/.docs/organized/code-examples/search-agent-for-e-commerce.md +18 -18
- package/.docs/organized/code-examples/{with-vercel-ai-rsc.md → with-ai-sdk-v5.md} +225 -230
- package/.docs/organized/code-examples/with-ai-sdk.md +13 -13
- package/.docs/organized/code-examples/with-cloud.md +12 -12
- package/.docs/organized/code-examples/with-external-store.md +9 -9
- package/.docs/organized/code-examples/with-ffmpeg.md +19 -19
- package/.docs/organized/code-examples/with-langgraph.md +14 -14
- package/.docs/organized/code-examples/with-openai-assistants.md +12 -12
- package/.docs/organized/code-examples/with-parent-id-grouping.md +1374 -0
- package/.docs/organized/code-examples/with-react-hook-form.md +18 -18
- package/.docs/raw/docs/about-assistantui.mdx +9 -0
- package/.docs/raw/docs/api-reference/context-providers/{TextContentPartProvider.mdx → TextMessagePartProvider.mdx} +3 -3
- package/.docs/raw/docs/api-reference/integrations/react-hook-form.mdx +2 -2
- package/.docs/raw/docs/api-reference/overview.mdx +23 -23
- package/.docs/raw/docs/api-reference/primitives/Error.mdx +5 -3
- package/.docs/raw/docs/api-reference/primitives/Message.mdx +32 -0
- package/.docs/raw/docs/api-reference/primitives/{ContentPart.mdx → MessagePart.mdx} +41 -41
- package/.docs/raw/docs/api-reference/runtimes/MessagePartRuntime.mdx +22 -0
- package/.docs/raw/docs/api-reference/runtimes/ThreadListRuntime.mdx +1 -0
- package/.docs/raw/docs/api-reference/runtimes/ThreadRuntime.mdx +1 -0
- package/.docs/raw/docs/cloud/persistence/ai-sdk.mdx +89 -32
- package/.docs/raw/docs/cloud/persistence/langgraph.mdx +187 -32
- package/.docs/raw/docs/concepts/runtime-layer.mdx +7 -7
- package/.docs/raw/docs/copilots/make-assistant-tool-ui.mdx +22 -13
- package/.docs/raw/docs/copilots/make-assistant-tool.mdx +20 -14
- package/.docs/raw/docs/getting-started.mdx +11 -10
- package/.docs/raw/docs/guides/Attachments.mdx +24 -21
- package/.docs/raw/docs/guides/Latex.mdx +81 -0
- package/.docs/raw/docs/guides/ToolUI.mdx +13 -8
- package/.docs/raw/docs/migrations/v0-11.mdx +169 -0
- package/.docs/raw/docs/migrations/v0-7.mdx +8 -8
- package/.docs/raw/docs/migrations/v0-8.mdx +14 -14
- package/.docs/raw/docs/migrations/v0-9.mdx +3 -3
- package/.docs/raw/docs/runtimes/ai-sdk/rsc.mdx +2 -2
- package/.docs/raw/docs/runtimes/ai-sdk/use-assistant-hook.mdx +1 -1
- package/.docs/raw/docs/runtimes/ai-sdk/use-chat-hook.mdx +2 -2
- package/.docs/raw/docs/runtimes/ai-sdk/use-chat-v5.mdx +129 -0
- package/.docs/raw/docs/runtimes/ai-sdk/use-chat.mdx +3 -3
- package/.docs/raw/docs/runtimes/custom/external-store.mdx +5 -5
- package/.docs/raw/docs/runtimes/langgraph/tutorial/part-2.mdx +2 -2
- package/.docs/raw/docs/ui/Attachment.mdx +5 -2
- package/.docs/raw/docs/ui/Markdown.mdx +2 -3
- package/.docs/raw/docs/ui/PartGrouping.mdx +540 -0
- package/.docs/raw/docs/ui/ToolFallback.mdx +2 -2
- package/.docs/raw/docs/ui/ToolGroup.mdx +96 -0
- package/package.json +8 -8
- package/.docs/raw/docs/api-reference/runtimes/ContentPartRuntime.mdx +0 -22
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Message Part Grouping
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
import { Steps, Step } from "fumadocs-ui/components/steps";
|
|
6
|
+
import { Callout } from "fumadocs-ui/components/callout";
|
|
7
|
+
import { ParametersTable } from "@/components/docs";
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
<Callout type="warn">
|
|
12
|
+
This feature is experimental and the API may change in future versions.
|
|
13
|
+
</Callout>
|
|
14
|
+
|
|
15
|
+
The Message Part Grouping feature allows you to organize and display message parts in custom groups using a flexible grouping function. This is useful for creating visual hierarchies, organizing related content together, or building custom UI patterns based on message part characteristics.
|
|
16
|
+
|
|
17
|
+
## Basic Usage
|
|
18
|
+
|
|
19
|
+
Use the `MessagePrimitive.Unstable_PartsGrouped` component with a custom grouping function:
|
|
20
|
+
|
|
21
|
+
```tsx twoslash title="/components/assistant-ui/thread.tsx"
|
|
22
|
+
import { FC, PropsWithChildren } from "react";
|
|
23
|
+
import { MessagePrimitive } from "@assistant-ui/react";
|
|
24
|
+
|
|
25
|
+
const AssistantActionBar: FC = () => null;
|
|
26
|
+
const BranchPicker: FC<{ className?: string }> = () => null;
|
|
27
|
+
|
|
28
|
+
// ---cut---
|
|
29
|
+
const AssistantMessage: FC = () => {
|
|
30
|
+
return (
|
|
31
|
+
<MessagePrimitive.Root className="...">
|
|
32
|
+
<div className="...">
|
|
33
|
+
<MessagePrimitive.Unstable_PartsGrouped
|
|
34
|
+
groupingFunction={(parts) => {
|
|
35
|
+
// Your custom grouping logic here
|
|
36
|
+
return [{ groupKey: undefined, indices: [0, 1, 2] }];
|
|
37
|
+
}}
|
|
38
|
+
components={{
|
|
39
|
+
Group: ({ groupKey, indices, children }) => {
|
|
40
|
+
// Your custom group rendering
|
|
41
|
+
return <div className="group">{children}</div>;
|
|
42
|
+
},
|
|
43
|
+
}}
|
|
44
|
+
/>
|
|
45
|
+
</div>
|
|
46
|
+
<AssistantActionBar />
|
|
47
|
+
<BranchPicker className="..." />
|
|
48
|
+
</MessagePrimitive.Root>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## How Grouping Works
|
|
54
|
+
|
|
55
|
+
The grouping function receives all message parts and returns an array of groups. Each group contains:
|
|
56
|
+
|
|
57
|
+
- `groupKey`: A string identifier for the group (or `undefined` for ungrouped parts)
|
|
58
|
+
- `indices`: An array of indices indicating which parts belong to this group
|
|
59
|
+
|
|
60
|
+
## Use Cases & Examples
|
|
61
|
+
|
|
62
|
+
### Group by Parent ID
|
|
63
|
+
|
|
64
|
+
Group related content together using a parent-child relationship:
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
import { FC, PropsWithChildren, useState } from "react";
|
|
68
|
+
import { ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
|
69
|
+
|
|
70
|
+
const groupByParentId = (parts: readonly any[]) => {
|
|
71
|
+
const groups = new Map<string, number[]>();
|
|
72
|
+
|
|
73
|
+
for (let i = 0; i < parts.length; i++) {
|
|
74
|
+
const part = parts[i];
|
|
75
|
+
const groupId = part?.parentId ?? `__ungrouped_${i}`;
|
|
76
|
+
|
|
77
|
+
const indices = groups.get(groupId) ?? [];
|
|
78
|
+
indices.push(i);
|
|
79
|
+
groups.set(groupId, indices);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return Array.from(groups.entries()).map(([groupId, indices]) => ({
|
|
83
|
+
groupKey: groupId.startsWith("__ungrouped_") ? undefined : groupId,
|
|
84
|
+
indices,
|
|
85
|
+
}));
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Usage with collapsible groups
|
|
89
|
+
const CollapsibleGroup: FC<
|
|
90
|
+
PropsWithChildren<{ groupKey: string | undefined; indices: number[] }>
|
|
91
|
+
> = ({ groupKey, indices, children }) => {
|
|
92
|
+
const [isCollapsed, setIsCollapsed] = useState(false);
|
|
93
|
+
|
|
94
|
+
if (!groupKey) return <>{children}</>;
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<div className="my-2 overflow-hidden rounded-lg border">
|
|
98
|
+
<button
|
|
99
|
+
onClick={() => setIsCollapsed(!isCollapsed)}
|
|
100
|
+
className="hover:bg-muted/50 flex w-full items-center justify-between p-3"
|
|
101
|
+
>
|
|
102
|
+
<span>
|
|
103
|
+
Group {groupKey} ({indices.length} items)
|
|
104
|
+
</span>
|
|
105
|
+
{isCollapsed ? <ChevronDownIcon /> : <ChevronUpIcon />}
|
|
106
|
+
</button>
|
|
107
|
+
{!isCollapsed && <div className="border-t p-3">{children}</div>}
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Group by Tool Name
|
|
114
|
+
|
|
115
|
+
Organize tool calls by their type:
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
import { FC, PropsWithChildren } from "react";
|
|
119
|
+
|
|
120
|
+
const groupByToolName = (parts: readonly any[]) => {
|
|
121
|
+
const groups = new Map<string, number[]>();
|
|
122
|
+
|
|
123
|
+
for (let i = 0; i < parts.length; i++) {
|
|
124
|
+
const part = parts[i];
|
|
125
|
+
// Group tool calls by tool name, everything else ungrouped
|
|
126
|
+
const groupKey = part.type === "tool-call" ? part.toolName : `__other_${i}`;
|
|
127
|
+
|
|
128
|
+
const indices = groups.get(groupKey) ?? [];
|
|
129
|
+
indices.push(i);
|
|
130
|
+
groups.set(groupKey, indices);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return Array.from(groups.entries()).map(([groupKey, indices]) => ({
|
|
134
|
+
groupKey: groupKey.startsWith("__other_") ? undefined : groupKey,
|
|
135
|
+
indices,
|
|
136
|
+
}));
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// Render tool groups with custom styling
|
|
140
|
+
const ToolGroup: FC<
|
|
141
|
+
PropsWithChildren<{ groupKey: string | undefined; indices: number[] }>
|
|
142
|
+
> = ({ groupKey, indices, children }) => {
|
|
143
|
+
if (!groupKey) return <>{children}</>;
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<div className="tool-group my-2 rounded-lg border">
|
|
147
|
+
<div className="bg-muted/50 px-4 py-2 text-sm font-medium">
|
|
148
|
+
Tool: {groupKey} ({indices.length} calls)
|
|
149
|
+
</div>
|
|
150
|
+
<div className="p-4">{children}</div>
|
|
151
|
+
</div>
|
|
152
|
+
);
|
|
153
|
+
};
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Group Consecutive Text Parts
|
|
157
|
+
|
|
158
|
+
Combine multiple text parts into cohesive blocks:
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
import { FC, PropsWithChildren } from "react";
|
|
162
|
+
|
|
163
|
+
type MessagePartGroup = {
|
|
164
|
+
groupKey: string | undefined;
|
|
165
|
+
indices: number[];
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const groupConsecutiveText = (parts: readonly any[]) => {
|
|
169
|
+
const groups: MessagePartGroup[] = [];
|
|
170
|
+
let currentGroup: number[] = [];
|
|
171
|
+
let isTextGroup = false;
|
|
172
|
+
|
|
173
|
+
for (let i = 0; i < parts.length; i++) {
|
|
174
|
+
const isText = parts[i].type === "text";
|
|
175
|
+
|
|
176
|
+
if (isText === isTextGroup && currentGroup.length > 0) {
|
|
177
|
+
currentGroup.push(i);
|
|
178
|
+
} else {
|
|
179
|
+
if (currentGroup.length > 0) {
|
|
180
|
+
groups.push({
|
|
181
|
+
groupKey: isTextGroup ? "text-block" : undefined,
|
|
182
|
+
indices: currentGroup,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
currentGroup = [i];
|
|
186
|
+
isTextGroup = isText;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (currentGroup.length > 0) {
|
|
191
|
+
groups.push({
|
|
192
|
+
groupKey: isTextGroup ? "text-block" : undefined,
|
|
193
|
+
indices: currentGroup,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return groups;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// Render text blocks with special formatting
|
|
201
|
+
const TextBlockGroup: FC<
|
|
202
|
+
PropsWithChildren<{ groupKey: string | undefined; indices: number[] }>
|
|
203
|
+
> = ({ groupKey, indices, children }) => {
|
|
204
|
+
if (groupKey === "text-block") {
|
|
205
|
+
return (
|
|
206
|
+
<div className="prose prose-sm my-2 rounded-lg bg-gray-50 p-4">
|
|
207
|
+
{children}
|
|
208
|
+
</div>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
return <>{children}</>;
|
|
212
|
+
};
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Group by Content Type
|
|
216
|
+
|
|
217
|
+
Separate different types of content for distinct visual treatment:
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
type MessagePartGroup = {
|
|
221
|
+
groupKey: string | undefined;
|
|
222
|
+
indices: number[];
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const groupByContentType = (parts: readonly any[]) => {
|
|
226
|
+
const typeGroups = new Map<string, number[]>();
|
|
227
|
+
|
|
228
|
+
for (let i = 0; i < parts.length; i++) {
|
|
229
|
+
const part = parts[i];
|
|
230
|
+
const type = part.type;
|
|
231
|
+
|
|
232
|
+
const indices = typeGroups.get(type) ?? [];
|
|
233
|
+
indices.push(i);
|
|
234
|
+
typeGroups.set(type, indices);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Order groups by type priority
|
|
238
|
+
const typeOrder = [
|
|
239
|
+
"reasoning",
|
|
240
|
+
"tool-call",
|
|
241
|
+
"source",
|
|
242
|
+
"text",
|
|
243
|
+
"image",
|
|
244
|
+
"file",
|
|
245
|
+
];
|
|
246
|
+
const orderedGroups: MessagePartGroup[] = [];
|
|
247
|
+
|
|
248
|
+
for (const type of typeOrder) {
|
|
249
|
+
const indices = typeGroups.get(type);
|
|
250
|
+
if (indices && indices.length > 0) {
|
|
251
|
+
orderedGroups.push({ groupKey: type, indices });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Add any remaining types
|
|
256
|
+
for (const [type, indices] of typeGroups) {
|
|
257
|
+
if (!typeOrder.includes(type)) {
|
|
258
|
+
orderedGroups.push({ groupKey: type, indices });
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return orderedGroups;
|
|
263
|
+
};
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Group by Custom Metadata
|
|
267
|
+
|
|
268
|
+
Use any custom metadata in your parts for grouping:
|
|
269
|
+
|
|
270
|
+
```tsx
|
|
271
|
+
import { FC, PropsWithChildren } from "react";
|
|
272
|
+
|
|
273
|
+
type MessagePartGroup = {
|
|
274
|
+
groupKey: string | undefined;
|
|
275
|
+
indices: number[];
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const groupByPriority = (parts: readonly any[]) => {
|
|
279
|
+
const priorityGroups = new Map<string, number[]>();
|
|
280
|
+
|
|
281
|
+
for (let i = 0; i < parts.length; i++) {
|
|
282
|
+
const part = parts[i];
|
|
283
|
+
// Assume parts have a custom priority field
|
|
284
|
+
const priority = part.priority || "normal";
|
|
285
|
+
|
|
286
|
+
const indices = priorityGroups.get(priority) ?? [];
|
|
287
|
+
indices.push(i);
|
|
288
|
+
priorityGroups.set(priority, indices);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Order by priority
|
|
292
|
+
const priorityOrder = ["high", "normal", "low"];
|
|
293
|
+
const orderedGroups: MessagePartGroup[] = [];
|
|
294
|
+
|
|
295
|
+
for (const priority of priorityOrder) {
|
|
296
|
+
const indices = priorityGroups.get(priority);
|
|
297
|
+
if (indices) {
|
|
298
|
+
orderedGroups.push({ groupKey: priority, indices });
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return orderedGroups;
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
// Render with priority indicators
|
|
306
|
+
const PriorityGroup: FC<
|
|
307
|
+
PropsWithChildren<{ groupKey: string | undefined; indices: number[] }>
|
|
308
|
+
> = ({ groupKey, indices, children }) => {
|
|
309
|
+
if (!groupKey) return <>{children}</>;
|
|
310
|
+
|
|
311
|
+
const priorityStyles = {
|
|
312
|
+
high: "border-red-500 bg-red-50",
|
|
313
|
+
normal: "border-gray-300 bg-white",
|
|
314
|
+
low: "border-gray-200 bg-gray-50",
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
return (
|
|
318
|
+
<div
|
|
319
|
+
className={`my-2 rounded-lg border-2 p-4 ${priorityStyles[groupKey] || ""}`}
|
|
320
|
+
>
|
|
321
|
+
<div className="mb-2 text-xs font-semibold uppercase text-gray-600">
|
|
322
|
+
{groupKey} Priority
|
|
323
|
+
</div>
|
|
324
|
+
{children}
|
|
325
|
+
</div>
|
|
326
|
+
);
|
|
327
|
+
};
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## Integration with Assistant Streams
|
|
331
|
+
|
|
332
|
+
When using assistant-stream libraries, you can add custom metadata to parts:
|
|
333
|
+
|
|
334
|
+
### Python (assistant-stream)
|
|
335
|
+
|
|
336
|
+
```python
|
|
337
|
+
from assistant_stream import create_run
|
|
338
|
+
|
|
339
|
+
async def my_run(controller):
|
|
340
|
+
# Add parts with custom parentId
|
|
341
|
+
research_controller = controller.with_parent_id("research-123")
|
|
342
|
+
|
|
343
|
+
await research_controller.add_tool_call("search", {"query": "climate data"})
|
|
344
|
+
research_controller.append_text("Key findings from the research:")
|
|
345
|
+
|
|
346
|
+
# Add parts with custom metadata
|
|
347
|
+
controller.append_part({
|
|
348
|
+
"type": "text",
|
|
349
|
+
"text": "High priority finding",
|
|
350
|
+
"priority": "high",
|
|
351
|
+
"category": "findings"
|
|
352
|
+
})
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### TypeScript (assistant-stream)
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
import { createAssistantStream } from "@assistant-ui/react/assistant-stream";
|
|
359
|
+
|
|
360
|
+
const stream = createAssistantStream(async (controller) => {
|
|
361
|
+
// Add parts with parentId
|
|
362
|
+
const researchController = controller.withParentId("research-123");
|
|
363
|
+
|
|
364
|
+
await researchController.addToolCallPart({
|
|
365
|
+
toolName: "search",
|
|
366
|
+
args: { query: "climate data" },
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
// Add parts with custom metadata
|
|
370
|
+
controller.appendPart({
|
|
371
|
+
type: "text",
|
|
372
|
+
text: "High priority finding",
|
|
373
|
+
priority: "high",
|
|
374
|
+
category: "findings",
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
## API Reference
|
|
380
|
+
|
|
381
|
+
### MessagePrimitive.Unstable_PartsGrouped
|
|
382
|
+
|
|
383
|
+
<ParametersTable
|
|
384
|
+
type="MessagePrimitiveUnstable_PartsGroupedProps"
|
|
385
|
+
parameters={[
|
|
386
|
+
{
|
|
387
|
+
name: "groupingFunction",
|
|
388
|
+
type: "(parts: readonly any[]) => MessagePartGroup[]",
|
|
389
|
+
description:
|
|
390
|
+
"Function that takes an array of message parts and returns an array of groups. Each group contains a groupKey (for identification) and an array of indices.",
|
|
391
|
+
required: true,
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
name: "components",
|
|
395
|
+
type: "object",
|
|
396
|
+
description:
|
|
397
|
+
"Component configuration for rendering different types of message content and groups.",
|
|
398
|
+
children: [
|
|
399
|
+
{
|
|
400
|
+
type: "Components",
|
|
401
|
+
parameters: [
|
|
402
|
+
{
|
|
403
|
+
name: "Empty",
|
|
404
|
+
type: "EmptyMessagePartComponent",
|
|
405
|
+
description: "Component for rendering empty messages",
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
name: "Text",
|
|
409
|
+
type: "TextMessagePartComponent",
|
|
410
|
+
description: "Component for rendering text content",
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
name: "Reasoning",
|
|
414
|
+
type: "ReasoningMessagePartComponent",
|
|
415
|
+
description:
|
|
416
|
+
"Component for rendering reasoning content (typically hidden)",
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
name: "Source",
|
|
420
|
+
type: "SourceMessagePartComponent",
|
|
421
|
+
description: "Component for rendering source content",
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
name: "Image",
|
|
425
|
+
type: "ImageMessagePartComponent",
|
|
426
|
+
description: "Component for rendering image content",
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
name: "File",
|
|
430
|
+
type: "FileMessagePartComponent",
|
|
431
|
+
description: "Component for rendering file content",
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
name: "Unstable_Audio",
|
|
435
|
+
type: "Unstable_AudioMessagePartComponent",
|
|
436
|
+
description:
|
|
437
|
+
"Component for rendering audio content (experimental)",
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
name: "tools",
|
|
441
|
+
type: "object | { Override: ComponentType }",
|
|
442
|
+
description:
|
|
443
|
+
"Configuration for tool call rendering. Can be an object with by_name map and Fallback component, or an Override component.",
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
name: "Group",
|
|
447
|
+
type: "ComponentType<PropsWithChildren<{ groupKey: string | undefined; indices: number[] }>>",
|
|
448
|
+
description:
|
|
449
|
+
"Component for rendering grouped message parts. Receives groupKey, indices array, and children to render.",
|
|
450
|
+
},
|
|
451
|
+
],
|
|
452
|
+
},
|
|
453
|
+
],
|
|
454
|
+
},
|
|
455
|
+
]}
|
|
456
|
+
/>
|
|
457
|
+
|
|
458
|
+
### MessagePartGroup Type
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
type MessagePartGroup = {
|
|
462
|
+
groupKey: string | undefined; // The group identifier (undefined for ungrouped parts)
|
|
463
|
+
indices: number[]; // Array of part indices belonging to this group
|
|
464
|
+
};
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### Group Component Props
|
|
468
|
+
|
|
469
|
+
The Group component receives:
|
|
470
|
+
|
|
471
|
+
- `groupKey`: The group identifier (or `undefined` for ungrouped parts)
|
|
472
|
+
- `indices`: Array of indices for the parts in this group
|
|
473
|
+
- `children`: The rendered message part components
|
|
474
|
+
|
|
475
|
+
## Best Practices
|
|
476
|
+
|
|
477
|
+
1. **Keep grouping logic simple**: Complex grouping functions can impact performance
|
|
478
|
+
2. **Handle ungrouped parts**: Always consider parts that don't match any group criteria
|
|
479
|
+
3. **Maintain order**: Consider the visual flow when ordering groups
|
|
480
|
+
4. **Use meaningful keys**: Group keys should be descriptive for debugging
|
|
481
|
+
5. **Test edge cases**: Empty messages, single parts, and large numbers of parts
|
|
482
|
+
|
|
483
|
+
## Common Patterns
|
|
484
|
+
|
|
485
|
+
### Conditional Grouping
|
|
486
|
+
|
|
487
|
+
Only group when certain conditions are met:
|
|
488
|
+
|
|
489
|
+
```tsx
|
|
490
|
+
const conditionalGrouping = (parts: readonly any[]) => {
|
|
491
|
+
// Only group if there are enough parts
|
|
492
|
+
if (parts.length < 5) {
|
|
493
|
+
return [{ groupKey: undefined, indices: parts.map((_, i) => i) }];
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Your grouping logic here
|
|
497
|
+
};
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### Nested Grouping
|
|
501
|
+
|
|
502
|
+
Create hierarchical groups:
|
|
503
|
+
|
|
504
|
+
```tsx
|
|
505
|
+
const nestedGrouping = (parts: readonly any[]) => {
|
|
506
|
+
// First level: group by type
|
|
507
|
+
// Second level: group by subtype or metadata
|
|
508
|
+
// Implementation depends on your specific needs
|
|
509
|
+
};
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
### Dynamic Group Rendering
|
|
513
|
+
|
|
514
|
+
Adjust group appearance based on content:
|
|
515
|
+
|
|
516
|
+
```tsx
|
|
517
|
+
import { FC, PropsWithChildren } from "react";
|
|
518
|
+
import { useMessage } from "@assistant-ui/react";
|
|
519
|
+
|
|
520
|
+
const DynamicGroup: FC<
|
|
521
|
+
PropsWithChildren<{ groupKey: string | undefined; indices: number[] }>
|
|
522
|
+
> = ({ groupKey, indices, children }) => {
|
|
523
|
+
const parts = useMessage((m) => m.content);
|
|
524
|
+
const groupParts = indices.map((i) => parts[i]);
|
|
525
|
+
|
|
526
|
+
// Analyze group content
|
|
527
|
+
const hasErrors = groupParts.some((p) => p.error);
|
|
528
|
+
const isLoading = groupParts.some((p) => p.status?.type === "running");
|
|
529
|
+
|
|
530
|
+
if (!groupKey) return <>{children}</>;
|
|
531
|
+
|
|
532
|
+
return (
|
|
533
|
+
<div
|
|
534
|
+
className={`my-2 rounded-lg border p-4 ${hasErrors ? "border-red-500 bg-red-50" : ""} ${isLoading ? "animate-pulse" : ""} `}
|
|
535
|
+
>
|
|
536
|
+
{children}
|
|
537
|
+
</div>
|
|
538
|
+
);
|
|
539
|
+
};
|
|
540
|
+
```
|
|
@@ -26,7 +26,7 @@ This adds a `/components/assistant-ui/tool-fallback.tsx` file to your project, w
|
|
|
26
26
|
|
|
27
27
|
### Use it in your application
|
|
28
28
|
|
|
29
|
-
Pass the `ToolFallback` component to the `MessagePrimitive.
|
|
29
|
+
Pass the `ToolFallback` component to the `MessagePrimitive.Parts` component
|
|
30
30
|
|
|
31
31
|
```tsx twoslash title="/components/assistant-ui/thread.tsx" {1,11}
|
|
32
32
|
// @filename: /components/assistant-ui/tool-fallback.tsx
|
|
@@ -48,7 +48,7 @@ const AssistantMessage: FC = () => {
|
|
|
48
48
|
return (
|
|
49
49
|
<MessagePrimitive.Root className="...">
|
|
50
50
|
<div className="...">
|
|
51
|
-
<MessagePrimitive.
|
|
51
|
+
<MessagePrimitive.Parts
|
|
52
52
|
components={{ tools: { Fallback: ToolFallback } }}
|
|
53
53
|
/>
|
|
54
54
|
</div>
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: ToolGroup
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
import { Steps, Step } from "fumadocs-ui/components/steps";
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
The ToolGroup component wraps consecutive tool-call message parts, enabling you to create custom presentations for grouped tool calls such as collapsible sections and custom styling.
|
|
10
|
+
|
|
11
|
+
### Use it in your application
|
|
12
|
+
|
|
13
|
+
Pass the `ToolGroup` component to the `MessagePrimitive.Parts` component
|
|
14
|
+
|
|
15
|
+
```tsx twoslash title="/components/assistant-ui/thread.tsx"
|
|
16
|
+
import { FC, PropsWithChildren } from "react";
|
|
17
|
+
import { MessagePrimitive } from "@assistant-ui/react";
|
|
18
|
+
|
|
19
|
+
const AssistantActionBar: FC = () => null;
|
|
20
|
+
const BranchPicker: FC<{ className?: string }> = () => null;
|
|
21
|
+
|
|
22
|
+
// ---cut---
|
|
23
|
+
const ToolGroup: FC<
|
|
24
|
+
PropsWithChildren<{ startIndex: number; endIndex: number }>
|
|
25
|
+
> = ({ startIndex, endIndex, children }) => {
|
|
26
|
+
const toolCount = endIndex - startIndex + 1;
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<details className="my-2">
|
|
30
|
+
<summary className="cursor-pointer font-medium">
|
|
31
|
+
{toolCount} tool {toolCount === 1 ? "call" : "calls"}
|
|
32
|
+
</summary>
|
|
33
|
+
<div className="space-y-2 pl-4">{children}</div>
|
|
34
|
+
</details>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const AssistantMessage: FC = () => {
|
|
39
|
+
return (
|
|
40
|
+
<MessagePrimitive.Root className="...">
|
|
41
|
+
<div className="...">
|
|
42
|
+
<MessagePrimitive.Parts components={{ ToolGroup }} />
|
|
43
|
+
</div>
|
|
44
|
+
<AssistantActionBar />
|
|
45
|
+
|
|
46
|
+
<BranchPicker className="..." />
|
|
47
|
+
</MessagePrimitive.Root>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Props
|
|
53
|
+
|
|
54
|
+
The ToolGroup component receives the following props:
|
|
55
|
+
|
|
56
|
+
- `startIndex`: The index of the first tool call in the group
|
|
57
|
+
- `endIndex`: The index of the last tool call in the group
|
|
58
|
+
- `children`: The rendered tool call components
|
|
59
|
+
|
|
60
|
+
## Examples
|
|
61
|
+
|
|
62
|
+
### Collapsible Tool Group
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
const ToolGroup: FC<
|
|
66
|
+
PropsWithChildren<{ startIndex: number; endIndex: number }>
|
|
67
|
+
> = ({ startIndex, endIndex, children }) => {
|
|
68
|
+
const toolCount = endIndex - startIndex + 1;
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<details className="my-2">
|
|
72
|
+
<summary className="cursor-pointer font-medium">
|
|
73
|
+
{toolCount} tool {toolCount === 1 ? "call" : "calls"}
|
|
74
|
+
</summary>
|
|
75
|
+
<div className="space-y-2 pl-4">{children}</div>
|
|
76
|
+
</details>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Styled Tool Group with Header
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
const ToolGroup: FC<
|
|
85
|
+
PropsWithChildren<{ startIndex: number; endIndex: number }>
|
|
86
|
+
> = ({ startIndex, endIndex, children }) => {
|
|
87
|
+
return (
|
|
88
|
+
<div className="bg-muted/50 my-2 rounded-lg border p-4">
|
|
89
|
+
<div className="text-muted-foreground mb-2 text-sm">
|
|
90
|
+
Tool execution #{startIndex + 1}-{endIndex + 1}
|
|
91
|
+
</div>
|
|
92
|
+
<div className="space-y-2">{children}</div>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/mcp-docs-server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "MCP server for assistant-ui documentation and examples",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -8,18 +8,18 @@
|
|
|
8
8
|
"assistant-ui-mcp": "./dist/stdio.js"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
12
|
-
"zod": "^
|
|
11
|
+
"@modelcontextprotocol/sdk": "^1.17.1",
|
|
12
|
+
"zod": "^4.0.14",
|
|
13
13
|
"gray-matter": "^4.0.3",
|
|
14
|
-
"cross-env": "^
|
|
14
|
+
"cross-env": "^10.0.0"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"@types/node": "^24.0
|
|
17
|
+
"@types/node": "^24.1.0",
|
|
18
18
|
"tsup": "^8.5.0",
|
|
19
19
|
"tsx": "^4.20.3",
|
|
20
|
-
"typescript": "^5.
|
|
21
|
-
"vitest": "^3.2.
|
|
22
|
-
"eslint": "^9.
|
|
20
|
+
"typescript": "^5.9.2",
|
|
21
|
+
"vitest": "^3.2.4",
|
|
22
|
+
"eslint": "^9.32.0"
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
25
|
"dist",
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: ContentPartRuntime
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
import { ParametersTable } from "@/components/docs";
|
|
6
|
-
import { ContentPartState } from "@/components/docs/parameters/context";
|
|
7
|
-
|
|
8
|
-
### `useContentPart`
|
|
9
|
-
|
|
10
|
-
Access the content part state:
|
|
11
|
-
|
|
12
|
-
```tsx
|
|
13
|
-
import { useContentPart } from "@assistant-ui/react";
|
|
14
|
-
|
|
15
|
-
const part = useContentPart();
|
|
16
|
-
const partState = useContentPart.getState();
|
|
17
|
-
|
|
18
|
-
const status = useContentPart((m) => m.status);
|
|
19
|
-
const statusAgain = useContentPart.getState().status;
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
<ParametersTable {...ContentPartState} />
|