@assistant-ui/mcp-docs-server 0.1.29 → 0.1.31
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 +15 -7
- package/.docs/organized/code-examples/with-a2a.md +9 -21
- package/.docs/organized/code-examples/with-ag-ui.md +11 -8
- package/.docs/organized/code-examples/with-ai-sdk-v6.md +10 -10
- package/.docs/organized/code-examples/with-artifacts.md +12 -10
- package/.docs/organized/code-examples/with-assistant-transport.md +11 -12
- package/.docs/organized/code-examples/with-chain-of-thought.md +83 -54
- package/.docs/organized/code-examples/with-cloud-standalone.md +14 -11
- package/.docs/organized/code-examples/with-cloud.md +9 -10
- package/.docs/organized/code-examples/with-custom-thread-list.md +61 -16
- package/.docs/organized/code-examples/with-elevenlabs-conversational.md +17 -12
- package/.docs/organized/code-examples/with-elevenlabs-scribe.md +13 -13
- package/.docs/organized/code-examples/with-expo.md +25 -21
- package/.docs/organized/code-examples/with-external-store.md +8 -8
- package/.docs/organized/code-examples/with-ffmpeg.md +17 -12
- package/.docs/organized/code-examples/with-generative-ui.md +9 -9
- package/.docs/organized/code-examples/with-google-adk.md +8 -8
- package/.docs/organized/code-examples/with-heat-graph.md +5 -5
- package/.docs/organized/code-examples/with-interactables.md +10 -25
- package/.docs/organized/code-examples/with-langchain.md +437 -0
- package/.docs/organized/code-examples/with-langgraph.md +16 -16
- package/.docs/organized/code-examples/with-livekit.md +18 -13
- package/.docs/organized/code-examples/with-opencode.md +105 -62
- package/.docs/organized/code-examples/with-parent-id-grouping.md +10 -10
- package/.docs/organized/code-examples/with-react-hook-form.md +220 -148
- package/.docs/organized/code-examples/with-react-ink.md +2 -2
- package/.docs/organized/code-examples/with-react-router.md +12 -12
- package/.docs/organized/code-examples/with-store.md +8 -5
- package/.docs/organized/code-examples/with-tanstack.md +10 -10
- package/.docs/organized/code-examples/with-tap-runtime.md +10 -6
- package/.docs/raw/docs/(docs)/cli.mdx +2 -1
- package/.docs/raw/docs/(docs)/copilots/assistant-frame.mdx +1 -0
- package/.docs/raw/docs/(docs)/copilots/make-assistant-tool-ui.mdx +10 -3
- package/.docs/raw/docs/(docs)/copilots/make-assistant-tool.mdx +8 -3
- package/.docs/raw/docs/(docs)/copilots/make-assistant-visible.mdx +1 -0
- package/.docs/raw/docs/(docs)/copilots/model-context.mdx +1 -0
- package/.docs/raw/docs/(docs)/copilots/motivation.mdx +1 -0
- package/.docs/raw/docs/(docs)/copilots/use-assistant-instructions.mdx +1 -0
- package/.docs/raw/docs/(docs)/devtools.mdx +1 -0
- package/.docs/raw/docs/(docs)/index.mdx +1 -0
- package/.docs/raw/docs/(docs)/installation.mdx +1 -0
- package/.docs/raw/docs/(docs)/rtl.mdx +80 -0
- package/.docs/raw/docs/(reference)/api-reference/adapters/attachments.mdx +34 -0
- package/.docs/raw/docs/(reference)/api-reference/adapters/feedback-speech.mdx +41 -0
- package/.docs/raw/docs/(reference)/api-reference/adapters/index.mdx +26 -0
- package/.docs/raw/docs/(reference)/api-reference/adapters/persistence.mdx +34 -0
- package/.docs/raw/docs/(reference)/api-reference/adapters/runtime.mdx +31 -0
- package/.docs/raw/docs/(reference)/api-reference/context-providers/index.mdx +20 -0
- package/.docs/raw/docs/(reference)/api-reference/hooks/index.mdx +26 -0
- package/.docs/raw/docs/(reference)/api-reference/hooks/model-context.mdx +72 -0
- package/.docs/raw/docs/(reference)/api-reference/hooks/runtimes.mdx +41 -0
- package/.docs/raw/docs/(reference)/api-reference/hooks/state.mdx +48 -0
- package/.docs/raw/docs/(reference)/api-reference/hooks/utilities.mdx +30 -0
- package/.docs/raw/docs/(reference)/api-reference/integrations/index.mdx +23 -0
- package/.docs/raw/docs/(reference)/api-reference/overview.mdx +21 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-if.mdx +7 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/composer.mdx +149 -40
- package/.docs/raw/docs/(reference)/api-reference/primitives/index.mdx +65 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/message-part.mdx +2 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/message.mdx +50 -6
- package/.docs/raw/docs/(reference)/api-reference/primitives/thread-list.mdx +15 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/thread.mdx +36 -1
- package/.docs/raw/docs/(reference)/api-reference/runtimes/index.mdx +38 -0
- package/.docs/raw/docs/(reference)/api-reference/runtimes/thread-list-runtime.mdx +9 -0
- package/.docs/raw/docs/(reference)/migrations/v0-14.mdx +144 -6
- package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +231 -3
- package/.docs/raw/docs/cloud/ai-sdk.mdx +221 -3
- package/.docs/raw/docs/cloud/langgraph.mdx +274 -2
- package/.docs/raw/docs/{(docs)/guides → guides}/attachments.mdx +41 -36
- package/.docs/raw/docs/guides/branching.mdx +76 -0
- package/.docs/raw/docs/guides/chain-of-thought.mdx +166 -0
- package/.docs/raw/docs/{(docs)/guides → guides}/context-api.mdx +50 -22
- package/.docs/raw/docs/{(docs)/guides → guides}/dictation.mdx +2 -0
- package/.docs/raw/docs/guides/editing.mdx +102 -0
- package/.docs/raw/docs/guides/index.mdx +103 -0
- package/.docs/raw/docs/{(docs)/guides → guides}/interactables.mdx +49 -0
- package/.docs/raw/docs/{(docs)/guides → guides}/latex.mdx +51 -8
- package/.docs/raw/docs/guides/mentions.mdx +520 -0
- package/.docs/raw/docs/{(docs)/guides → guides}/message-timing.mdx +8 -2
- package/.docs/raw/docs/{(docs)/guides → guides}/multi-agent.mdx +64 -4
- package/.docs/raw/docs/{(docs)/guides → guides}/quoting.mdx +10 -17
- package/.docs/raw/docs/guides/slash-commands.mdx +361 -0
- package/.docs/raw/docs/guides/speech.mdx +156 -0
- package/.docs/raw/docs/{(docs)/guides → guides}/suggestions.mdx +21 -83
- package/.docs/raw/docs/{(docs)/guides → guides}/tool-ui.mdx +108 -36
- package/.docs/raw/docs/{(docs)/guides → guides}/tools.mdx +131 -35
- package/.docs/raw/docs/{(docs)/guides → guides}/voice.mdx +39 -0
- package/.docs/raw/docs/ink/index.mdx +1 -3
- package/.docs/raw/docs/ink/migration.mdx +1 -3
- package/.docs/raw/docs/ink/primitives.mdx +37 -1
- package/.docs/raw/docs/integrations/attachments/custom-adapter.mdx +520 -0
- package/.docs/raw/docs/integrations/auth/better-auth.mdx +191 -0
- package/.docs/raw/docs/integrations/auth/clerk.mdx +172 -0
- package/.docs/raw/docs/integrations/auth/next-auth.mdx +196 -0
- package/.docs/raw/docs/integrations/frameworks/ai-sdk.mdx +79 -0
- package/.docs/raw/docs/integrations/frameworks/mastra/full-stack.mdx +188 -0
- package/.docs/raw/docs/integrations/frameworks/mastra/overview.mdx +57 -0
- package/.docs/raw/docs/integrations/frameworks/mastra/separate-server.mdx +201 -0
- package/.docs/raw/docs/integrations/gateways/index.mdx +157 -0
- package/.docs/raw/docs/integrations/index.mdx +173 -0
- package/.docs/raw/docs/integrations/observability/helicone.mdx +130 -0
- package/.docs/raw/docs/integrations/observability/langfuse.mdx +156 -0
- package/.docs/raw/docs/integrations/observability/langsmith.mdx +146 -0
- package/.docs/raw/docs/integrations/persistence/custom-adapter.mdx +712 -0
- package/.docs/raw/docs/integrations/tools/mcp.mdx +267 -0
- package/.docs/raw/docs/primitives/action-bar.mdx +1 -0
- package/.docs/raw/docs/primitives/assistant-modal.mdx +1 -0
- package/.docs/raw/docs/primitives/attachment.mdx +1 -0
- package/.docs/raw/docs/primitives/branch-picker.mdx +1 -0
- package/.docs/raw/docs/primitives/chain-of-thought.mdx +90 -85
- package/.docs/raw/docs/primitives/composer.mdx +96 -63
- package/.docs/raw/docs/primitives/error.mdx +1 -0
- package/.docs/raw/docs/primitives/index.mdx +2 -1
- package/.docs/raw/docs/primitives/message.mdx +68 -5
- package/.docs/raw/docs/primitives/selection-toolbar.mdx +1 -0
- package/.docs/raw/docs/primitives/suggestion.mdx +1 -0
- package/.docs/raw/docs/primitives/thread-list.mdx +39 -0
- package/.docs/raw/docs/primitives/thread.mdx +16 -13
- package/.docs/raw/docs/react-native/index.mdx +1 -3
- package/.docs/raw/docs/react-native/migration.mdx +1 -3
- package/.docs/raw/docs/runtimes/a2a/client-and-hooks.mdx +396 -0
- package/.docs/raw/docs/runtimes/a2a/overview.mdx +60 -0
- package/.docs/raw/docs/runtimes/a2a/quickstart.mdx +216 -0
- package/.docs/raw/docs/runtimes/ag-ui/overview.mdx +70 -0
- package/.docs/raw/docs/runtimes/ag-ui/quickstart.mdx +243 -0
- package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +123 -0
- package/.docs/raw/docs/runtimes/ai-sdk/overview.mdx +52 -0
- package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +71 -131
- package/.docs/raw/docs/runtimes/ai-sdk/v5-legacy.mdx +69 -63
- package/.docs/raw/docs/runtimes/ai-sdk/v6.mdx +365 -101
- package/.docs/raw/docs/runtimes/concepts/adapters.mdx +265 -0
- package/.docs/raw/docs/runtimes/concepts/architecture.mdx +125 -0
- package/.docs/raw/docs/runtimes/concepts/stability.mdx +67 -0
- package/.docs/raw/docs/runtimes/concepts/threads.mdx +428 -0
- package/.docs/raw/docs/runtimes/custom/assistant-transport.mdx +703 -0
- package/.docs/raw/docs/runtimes/custom/data-stream.mdx +323 -0
- package/.docs/raw/docs/runtimes/custom/external-store.mdx +253 -1236
- package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +746 -0
- package/.docs/raw/docs/runtimes/custom/overview.mdx +71 -0
- package/.docs/raw/docs/runtimes/google-adk/api.mdx +256 -0
- package/.docs/raw/docs/runtimes/google-adk/hooks.mdx +717 -0
- package/.docs/raw/docs/runtimes/google-adk/overview.mdx +69 -0
- package/.docs/raw/docs/runtimes/google-adk/quickstart.mdx +229 -0
- package/.docs/raw/docs/runtimes/langchain.mdx +533 -0
- package/.docs/raw/docs/runtimes/langgraph/generative-ui.mdx +305 -0
- package/.docs/raw/docs/runtimes/langgraph/interrupts.mdx +104 -0
- package/.docs/raw/docs/runtimes/langgraph/overview.mdx +84 -0
- package/.docs/raw/docs/runtimes/langgraph/quickstart.mdx +496 -0
- package/.docs/raw/docs/runtimes/langgraph/streaming.mdx +127 -0
- package/.docs/raw/docs/runtimes/langgraph/threads.mdx +113 -0
- package/.docs/raw/docs/runtimes/langgraph/tutorial/introduction.mdx +3 -3
- package/.docs/raw/docs/runtimes/langgraph/tutorial/part-1.mdx +0 -23
- package/.docs/raw/docs/runtimes/langgraph/tutorial/part-2.mdx +1 -1
- package/.docs/raw/docs/runtimes/opencode/hooks.mdx +191 -0
- package/.docs/raw/docs/runtimes/opencode/overview.mdx +48 -0
- package/.docs/raw/docs/runtimes/opencode/quickstart.mdx +119 -0
- package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +74 -198
- package/.docs/raw/docs/ui/accordion.mdx +1 -0
- package/.docs/raw/docs/ui/assistant-modal.mdx +1 -0
- package/.docs/raw/docs/ui/assistant-sidebar.mdx +1 -0
- package/.docs/raw/docs/ui/attachment.mdx +1 -0
- package/.docs/raw/docs/ui/badge.mdx +1 -0
- package/.docs/raw/docs/ui/composer-trigger-popover.mdx +200 -0
- package/.docs/raw/docs/ui/context-display.mdx +1 -0
- package/.docs/raw/docs/ui/diff-viewer.mdx +1 -0
- package/.docs/raw/docs/ui/directive-text.mdx +114 -0
- package/.docs/raw/docs/ui/file.mdx +1 -0
- package/.docs/raw/docs/ui/image.mdx +1 -0
- package/.docs/raw/docs/ui/markdown.mdx +2 -14
- package/.docs/raw/docs/ui/mermaid.mdx +1 -0
- package/.docs/raw/docs/ui/message-timing.mdx +3 -2
- package/.docs/raw/docs/ui/model-selector.mdx +1 -0
- package/.docs/raw/docs/ui/part-grouping.mdx +325 -313
- package/.docs/raw/docs/ui/quote.mdx +1 -0
- package/.docs/raw/docs/ui/reasoning.mdx +69 -32
- package/.docs/raw/docs/ui/scrollbar.mdx +1 -0
- package/.docs/raw/docs/ui/select.mdx +1 -0
- package/.docs/raw/docs/ui/sources.mdx +1 -0
- package/.docs/raw/docs/ui/streamdown.mdx +1 -0
- package/.docs/raw/docs/ui/syntax-highlighting.mdx +1 -0
- package/.docs/raw/docs/ui/tabs.mdx +1 -0
- package/.docs/raw/docs/ui/thread-list.mdx +17 -0
- package/.docs/raw/docs/ui/thread.mdx +56 -1
- package/.docs/raw/docs/ui/tool-fallback.mdx +1 -0
- package/.docs/raw/docs/ui/tool-group.mdx +39 -11
- package/.docs/raw/docs/ui/voice.mdx +1 -0
- package/.docs/raw/docs/utilities/heat-graph.mdx +1 -0
- package/.docs/raw/docs/utilities/react-o11y.mdx +278 -0
- package/.docs/raw/docs/utilities/tw-shimmer.mdx +1 -0
- package/dist/utils/logger.js +1 -1
- package/dist/utils/logger.js.map +1 -1
- package/package.json +4 -4
- package/src/tools/tests/path-traversal.test.ts +1 -1
- package/src/utils/logger.ts +1 -1
- package/.docs/raw/docs/(docs)/guides/branching.mdx +0 -65
- package/.docs/raw/docs/(docs)/guides/chain-of-thought.mdx +0 -164
- package/.docs/raw/docs/(docs)/guides/editing.mdx +0 -66
- package/.docs/raw/docs/(docs)/guides/mentions.mdx +0 -406
- package/.docs/raw/docs/(docs)/guides/slash-commands.mdx +0 -275
- package/.docs/raw/docs/(docs)/guides/speech.mdx +0 -38
- package/.docs/raw/docs/runtimes/a2a/index.mdx +0 -298
- package/.docs/raw/docs/runtimes/assistant-transport.mdx +0 -1033
- package/.docs/raw/docs/runtimes/custom/custom-thread-list.mdx +0 -268
- package/.docs/raw/docs/runtimes/custom/local.mdx +0 -1464
- package/.docs/raw/docs/runtimes/data-stream.mdx +0 -422
- package/.docs/raw/docs/runtimes/google-adk/index.mdx +0 -686
- package/.docs/raw/docs/runtimes/helicone.mdx +0 -61
- package/.docs/raw/docs/runtimes/langgraph/index.mdx +0 -607
- package/.docs/raw/docs/runtimes/langgraph/tutorial/index.mdx +0 -12
- package/.docs/raw/docs/runtimes/langserve.mdx +0 -116
- package/.docs/raw/docs/runtimes/mastra/full-stack-integration.mdx +0 -218
- package/.docs/raw/docs/runtimes/mastra/overview.mdx +0 -18
- package/.docs/raw/docs/runtimes/mastra/separate-server-integration.mdx +0 -217
- package/.docs/raw/docs/ui/mention.mdx +0 -168
|
@@ -1,44 +1,39 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Message Part Grouping
|
|
3
3
|
description: Organize message parts into custom groups with flexible grouping functions.
|
|
4
|
+
platforms: ["react"]
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
import { PartGroupingSample } from "@/components/docs/samples/part-grouping";
|
|
7
8
|
|
|
8
9
|
<PartGroupingSample />
|
|
9
10
|
|
|
10
|
-
<Callout type="warn">
|
|
11
|
-
This feature is experimental and the API may change in future versions.
|
|
12
|
-
</Callout>
|
|
13
|
-
|
|
14
11
|
## Basic Usage
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
```tsx twoslash title="/components/assistant-ui/thread.tsx"
|
|
19
|
-
import { FC, PropsWithChildren } from "react";
|
|
20
|
-
import { MessagePrimitive } from "@assistant-ui/react";
|
|
21
|
-
|
|
22
|
-
const AssistantActionBar: FC = () => null;
|
|
23
|
-
const BranchPicker: FC<{ className?: string }> = () => null;
|
|
13
|
+
For adjacent grouping, use `MessagePrimitive.GroupedParts`. Return a group-key path for each part; adjacent parts that share a group key are coalesced and rendered through the group case.
|
|
24
14
|
|
|
25
|
-
|
|
15
|
+
```tsx title="/components/assistant-ui/thread.tsx"
|
|
26
16
|
const AssistantMessage: FC = () => {
|
|
27
17
|
return (
|
|
28
18
|
<MessagePrimitive.Root className="...">
|
|
29
19
|
<div className="...">
|
|
30
|
-
<MessagePrimitive.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return
|
|
20
|
+
<MessagePrimitive.GroupedParts
|
|
21
|
+
groupBy={(part) => {
|
|
22
|
+
if (part.type === "tool-call") return ["group-tool"];
|
|
23
|
+
return null;
|
|
34
24
|
}}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
25
|
+
>
|
|
26
|
+
{({ part, children }) => {
|
|
27
|
+
switch (part.type) {
|
|
28
|
+
case "group-tool":
|
|
29
|
+
return <div className="group">{children}</div>;
|
|
30
|
+
case "tool-call":
|
|
31
|
+
return part.toolUI ?? null;
|
|
32
|
+
default:
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
40
35
|
}}
|
|
41
|
-
|
|
36
|
+
</MessagePrimitive.GroupedParts>
|
|
42
37
|
</div>
|
|
43
38
|
<AssistantActionBar />
|
|
44
39
|
<BranchPicker className="..." />
|
|
@@ -47,107 +42,103 @@ const AssistantMessage: FC = () => {
|
|
|
47
42
|
};
|
|
48
43
|
```
|
|
49
44
|
|
|
50
|
-
## How Grouping Works
|
|
45
|
+
## How Adjacent Grouping Works
|
|
46
|
+
|
|
47
|
+
`groupBy` receives each part and returns either `null` for an ungrouped leaf or a group-key path such as `["group-chainOfThought", "group-tool"]`. Group keys must start with `"group-"` so your render function can distinguish synthetic groups from real part types.
|
|
51
48
|
|
|
52
|
-
The
|
|
49
|
+
The render function receives both group nodes and leaf parts through `{ part, children }`. Only group cases should render `children`; leaf cases should render their own UI or return `null`.
|
|
53
50
|
|
|
54
|
-
|
|
55
|
-
-
|
|
51
|
+
<Callout type="warn">
|
|
52
|
+
`MessagePrimitive.Unstable_PartsGrouped` remains available for rare non-adjacent clustering where one group needs to collect parts from different positions in the message. For normal consecutive reasoning/tool grouping, use `GroupedParts`.
|
|
53
|
+
</Callout>
|
|
56
54
|
|
|
57
55
|
## Use Cases & Examples
|
|
58
56
|
|
|
59
57
|
### Group by Parent ID
|
|
60
58
|
|
|
61
|
-
Group
|
|
59
|
+
Group adjacent content that shares the same parent relationship:
|
|
62
60
|
|
|
63
61
|
```tsx
|
|
64
|
-
import {
|
|
62
|
+
import { useState, type PropsWithChildren } from "react";
|
|
65
63
|
import { ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
|
64
|
+
import { MessagePrimitive } from "@assistant-ui/react";
|
|
66
65
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const indices = groups.get(groupId) ?? [];
|
|
75
|
-
indices.push(i);
|
|
76
|
-
groups.set(groupId, indices);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return Array.from(groups.entries()).map(([groupId, indices]) => ({
|
|
80
|
-
groupKey: groupId.startsWith("__ungrouped_") ? undefined : groupId,
|
|
81
|
-
indices,
|
|
82
|
-
}));
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
// Usage with collapsible groups
|
|
86
|
-
const CollapsibleGroup: FC<
|
|
87
|
-
PropsWithChildren<{ groupKey: string | undefined; indices: number[] }>
|
|
88
|
-
> = ({ groupKey, indices, children }) => {
|
|
89
|
-
const [isCollapsed, setIsCollapsed] = useState(false);
|
|
90
|
-
|
|
91
|
-
if (!groupKey) return <>{children}</>;
|
|
66
|
+
function ParentGroup({
|
|
67
|
+
id,
|
|
68
|
+
count,
|
|
69
|
+
children,
|
|
70
|
+
}: PropsWithChildren<{ id: string; count: number }>) {
|
|
71
|
+
const [collapsed, setCollapsed] = useState(false);
|
|
92
72
|
|
|
93
73
|
return (
|
|
94
74
|
<div className="my-2 overflow-hidden rounded-lg border">
|
|
95
75
|
<button
|
|
96
|
-
|
|
76
|
+
type="button"
|
|
77
|
+
onClick={() => setCollapsed((value) => !value)}
|
|
97
78
|
className="hover:bg-muted/50 flex w-full items-center justify-between p-3"
|
|
98
79
|
>
|
|
99
80
|
<span>
|
|
100
|
-
Group {
|
|
81
|
+
Group {id} ({count} items)
|
|
101
82
|
</span>
|
|
102
|
-
{
|
|
83
|
+
{collapsed ? <ChevronDownIcon /> : <ChevronUpIcon />}
|
|
103
84
|
</button>
|
|
104
|
-
{!
|
|
85
|
+
{!collapsed && <div className="border-t p-3">{children}</div>}
|
|
105
86
|
</div>
|
|
106
87
|
);
|
|
107
|
-
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
<MessagePrimitive.GroupedParts
|
|
91
|
+
groupBy={(part) => {
|
|
92
|
+
if (!part.parentId) return null;
|
|
93
|
+
return [`group-parent-${part.parentId}`];
|
|
94
|
+
}}
|
|
95
|
+
>
|
|
96
|
+
{({ part, children }) => {
|
|
97
|
+
if (part.type.startsWith("group-parent-")) {
|
|
98
|
+
const id = part.type.replace("group-parent-", "");
|
|
99
|
+
return (
|
|
100
|
+
<ParentGroup id={id} count={part.indices.length}>
|
|
101
|
+
{children}
|
|
102
|
+
</ParentGroup>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (part.type === "text") return <MarkdownText />;
|
|
107
|
+
if (part.type === "tool-call") return part.toolUI ?? <ToolFallback {...part} />;
|
|
108
|
+
return null;
|
|
109
|
+
}}
|
|
110
|
+
</MessagePrimitive.GroupedParts>
|
|
108
111
|
```
|
|
109
112
|
|
|
110
113
|
### Group by Tool Name
|
|
111
114
|
|
|
112
|
-
Organize tool calls by
|
|
115
|
+
Organize adjacent tool calls by tool name:
|
|
113
116
|
|
|
114
117
|
```tsx
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}));
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
// Render tool groups with custom styling
|
|
137
|
-
const ToolGroup: FC<
|
|
138
|
-
PropsWithChildren<{ groupKey: string | undefined; indices: number[] }>
|
|
139
|
-
> = ({ groupKey, indices, children }) => {
|
|
140
|
-
if (!groupKey) return <>{children}</>;
|
|
118
|
+
<MessagePrimitive.GroupedParts
|
|
119
|
+
groupBy={(part) => {
|
|
120
|
+
if (part.type !== "tool-call") return null;
|
|
121
|
+
return [`group-tool-${part.toolName}`];
|
|
122
|
+
}}
|
|
123
|
+
>
|
|
124
|
+
{({ part, children }) => {
|
|
125
|
+
if (part.type.startsWith("group-tool-")) {
|
|
126
|
+
const toolName = part.type.replace("group-tool-", "");
|
|
127
|
+
return (
|
|
128
|
+
<div className="tool-group my-2 rounded-lg border">
|
|
129
|
+
<div className="bg-muted/50 px-4 py-2 text-sm font-medium">
|
|
130
|
+
Tool: {toolName} ({part.indices.length} calls)
|
|
131
|
+
</div>
|
|
132
|
+
<div className="p-4">{children}</div>
|
|
133
|
+
</div>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
141
136
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
<div className="p-4">{children}</div>
|
|
148
|
-
</div>
|
|
149
|
-
);
|
|
150
|
-
};
|
|
137
|
+
if (part.type === "text") return <MarkdownText />;
|
|
138
|
+
if (part.type === "tool-call") return part.toolUI ?? <ToolFallback {...part} />;
|
|
139
|
+
return null;
|
|
140
|
+
}}
|
|
141
|
+
</MessagePrimitive.GroupedParts>
|
|
151
142
|
```
|
|
152
143
|
|
|
153
144
|
### Group Consecutive Text Parts
|
|
@@ -155,58 +146,29 @@ const ToolGroup: FC<
|
|
|
155
146
|
Combine multiple text parts into cohesive blocks:
|
|
156
147
|
|
|
157
148
|
```tsx
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
type
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
groupKey: isTextGroup ? "text-block" : undefined,
|
|
179
|
-
indices: currentGroup,
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
currentGroup = [i];
|
|
183
|
-
isTextGroup = isText;
|
|
149
|
+
<MessagePrimitive.GroupedParts
|
|
150
|
+
groupBy={(part) => {
|
|
151
|
+
if (part.type === "text") return ["group-text-block"];
|
|
152
|
+
return null;
|
|
153
|
+
}}
|
|
154
|
+
>
|
|
155
|
+
{({ part, children }) => {
|
|
156
|
+
switch (part.type) {
|
|
157
|
+
case "group-text-block":
|
|
158
|
+
return (
|
|
159
|
+
<div className="prose prose-sm my-2 rounded-lg bg-gray-50 p-4">
|
|
160
|
+
{children}
|
|
161
|
+
</div>
|
|
162
|
+
);
|
|
163
|
+
case "text":
|
|
164
|
+
return <MarkdownText />;
|
|
165
|
+
case "tool-call":
|
|
166
|
+
return part.toolUI ?? <ToolFallback {...part} />;
|
|
167
|
+
default:
|
|
168
|
+
return null;
|
|
184
169
|
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (currentGroup.length > 0) {
|
|
188
|
-
groups.push({
|
|
189
|
-
groupKey: isTextGroup ? "text-block" : undefined,
|
|
190
|
-
indices: currentGroup,
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return groups;
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
// Render text blocks with special formatting
|
|
198
|
-
const TextBlockGroup: FC<
|
|
199
|
-
PropsWithChildren<{ groupKey: string | undefined; indices: number[] }>
|
|
200
|
-
> = ({ groupKey, indices, children }) => {
|
|
201
|
-
if (groupKey === "text-block") {
|
|
202
|
-
return (
|
|
203
|
-
<div className="prose prose-sm my-2 rounded-lg bg-gray-50 p-4">
|
|
204
|
-
{children}
|
|
205
|
-
</div>
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
return <>{children}</>;
|
|
209
|
-
};
|
|
170
|
+
}}
|
|
171
|
+
</MessagePrimitive.GroupedParts>
|
|
210
172
|
```
|
|
211
173
|
|
|
212
174
|
### Group by Content Type
|
|
@@ -214,50 +176,33 @@ const TextBlockGroup: FC<
|
|
|
214
176
|
Separate different types of content for distinct visual treatment:
|
|
215
177
|
|
|
216
178
|
```tsx
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
"file",
|
|
242
|
-
];
|
|
243
|
-
const orderedGroups: MessagePartGroup[] = [];
|
|
244
|
-
|
|
245
|
-
for (const type of typeOrder) {
|
|
246
|
-
const indices = typeGroups.get(type);
|
|
247
|
-
if (indices && indices.length > 0) {
|
|
248
|
-
orderedGroups.push({ groupKey: type, indices });
|
|
179
|
+
<MessagePrimitive.GroupedParts
|
|
180
|
+
groupBy={(part) => {
|
|
181
|
+
if (part.type === "text") return ["group-content-text"];
|
|
182
|
+
if (part.type === "tool-call") return ["group-content-tools"];
|
|
183
|
+
if (part.type === "reasoning") return ["group-content-reasoning"];
|
|
184
|
+
return null;
|
|
185
|
+
}}
|
|
186
|
+
>
|
|
187
|
+
{({ part, children }) => {
|
|
188
|
+
switch (part.type) {
|
|
189
|
+
case "group-content-text":
|
|
190
|
+
return <div className="space-y-2">{children}</div>;
|
|
191
|
+
case "group-content-tools":
|
|
192
|
+
return <div className="my-2 rounded-lg border p-3">{children}</div>;
|
|
193
|
+
case "group-content-reasoning":
|
|
194
|
+
return <div className="my-2 text-muted-foreground">{children}</div>;
|
|
195
|
+
case "text":
|
|
196
|
+
return <MarkdownText />;
|
|
197
|
+
case "reasoning":
|
|
198
|
+
return <Reasoning {...part} />;
|
|
199
|
+
case "tool-call":
|
|
200
|
+
return part.toolUI ?? <ToolFallback {...part} />;
|
|
201
|
+
default:
|
|
202
|
+
return null;
|
|
249
203
|
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// Add any remaining types
|
|
253
|
-
for (const [type, indices] of typeGroups) {
|
|
254
|
-
if (!typeOrder.includes(type)) {
|
|
255
|
-
orderedGroups.push({ groupKey: type, indices });
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
return orderedGroups;
|
|
260
|
-
};
|
|
204
|
+
}}
|
|
205
|
+
</MessagePrimitive.GroupedParts>
|
|
261
206
|
```
|
|
262
207
|
|
|
263
208
|
### Group by Custom Metadata
|
|
@@ -265,63 +210,39 @@ const groupByContentType = (parts: readonly any[]) => {
|
|
|
265
210
|
Use any custom metadata in your parts for grouping:
|
|
266
211
|
|
|
267
212
|
```tsx
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
indices: number[];
|
|
213
|
+
const priorityStyles = {
|
|
214
|
+
high: "border-red-500 bg-red-50",
|
|
215
|
+
normal: "border-gray-300 bg-white",
|
|
216
|
+
low: "border-gray-200 bg-gray-50",
|
|
273
217
|
};
|
|
274
218
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
orderedGroups.push({ groupKey: priority, indices });
|
|
219
|
+
<MessagePrimitive.GroupedParts
|
|
220
|
+
groupBy={(part) => {
|
|
221
|
+
const priority = part.metadata?.priority;
|
|
222
|
+
if (!priority) return null;
|
|
223
|
+
return [`group-priority-${priority}`];
|
|
224
|
+
}}
|
|
225
|
+
>
|
|
226
|
+
{({ part, children }) => {
|
|
227
|
+
if (part.type.startsWith("group-priority-")) {
|
|
228
|
+
const priority = part.type.replace("group-priority-", "");
|
|
229
|
+
return (
|
|
230
|
+
<div
|
|
231
|
+
className={`my-2 rounded-lg border-2 p-4 ${priorityStyles[priority] || ""}`}
|
|
232
|
+
>
|
|
233
|
+
<div className="mb-2 text-xs font-semibold uppercase text-gray-600">
|
|
234
|
+
{priority} Priority
|
|
235
|
+
</div>
|
|
236
|
+
{children}
|
|
237
|
+
</div>
|
|
238
|
+
);
|
|
296
239
|
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
return orderedGroups;
|
|
300
|
-
};
|
|
301
240
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
const priorityStyles = {
|
|
309
|
-
high: "border-red-500 bg-red-50",
|
|
310
|
-
normal: "border-gray-300 bg-white",
|
|
311
|
-
low: "border-gray-200 bg-gray-50",
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
return (
|
|
315
|
-
<div
|
|
316
|
-
className={`my-2 rounded-lg border-2 p-4 ${priorityStyles[groupKey] || ""}`}
|
|
317
|
-
>
|
|
318
|
-
<div className="mb-2 text-xs font-semibold uppercase text-gray-600">
|
|
319
|
-
{groupKey} Priority
|
|
320
|
-
</div>
|
|
321
|
-
{children}
|
|
322
|
-
</div>
|
|
323
|
-
);
|
|
324
|
-
};
|
|
241
|
+
if (part.type === "text") return <MarkdownText />;
|
|
242
|
+
if (part.type === "tool-call") return part.toolUI ?? <ToolFallback {...part} />;
|
|
243
|
+
return null;
|
|
244
|
+
}}
|
|
245
|
+
</MessagePrimitive.GroupedParts>
|
|
325
246
|
```
|
|
326
247
|
|
|
327
248
|
## Integration with Assistant Streams
|
|
@@ -372,23 +293,181 @@ const stream = createAssistantStream(async (controller) => {
|
|
|
372
293
|
|
|
373
294
|
## API Reference
|
|
374
295
|
|
|
375
|
-
### MessagePrimitive.
|
|
296
|
+
### MessagePrimitive.GroupedParts
|
|
297
|
+
|
|
298
|
+
<ParametersTable
|
|
299
|
+
type="MessagePrimitiveGroupedPartsProps"
|
|
300
|
+
parameters={[
|
|
301
|
+
{
|
|
302
|
+
name: "groupBy",
|
|
303
|
+
type: "(part: PartState, index: number, parts: readonly PartState[]) => GroupKey",
|
|
304
|
+
description:
|
|
305
|
+
"Maps a part to a group-key path. Return null, undefined, or [] to leave the part ungrouped. Group keys must start with group-.",
|
|
306
|
+
required: true,
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
name: "children",
|
|
310
|
+
type: "({ part, children }) => ReactNode",
|
|
311
|
+
description:
|
|
312
|
+
"Render function called for group nodes and leaf parts. Group nodes expose children; leaf parts should render their own UI or return null.",
|
|
313
|
+
required: true,
|
|
314
|
+
},
|
|
315
|
+
]}
|
|
316
|
+
/>
|
|
317
|
+
|
|
318
|
+
### GroupPart Type
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
type GroupPart<TKey extends `group-${string}` = `group-${string}`> = {
|
|
322
|
+
readonly type: TKey;
|
|
323
|
+
readonly status: MessagePartStatus | ToolCallMessagePartStatus;
|
|
324
|
+
readonly indices: readonly number[];
|
|
325
|
+
};
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Best Practices
|
|
329
|
+
|
|
330
|
+
1. **Keep grouping local**: `GroupedParts` groups adjacent runs. If the same key appears again later, it becomes a new group in that position.
|
|
331
|
+
2. **Handle ungrouped parts**: Always include leaf cases for the part types your message can render.
|
|
332
|
+
3. **Only render `children` for groups**: Leaf parts receive a sentinel `children` value that throws if rendered accidentally.
|
|
333
|
+
4. **Use `group-` prefixes**: Synthetic group keys must start with `group-` so they cannot collide with real part types.
|
|
334
|
+
5. **Keep `groupBy` stable**: Prefer a module-level function or `useCallback` for frequently re-rendering message trees.
|
|
335
|
+
|
|
336
|
+
## Common Patterns
|
|
337
|
+
|
|
338
|
+
### Conditional Grouping
|
|
339
|
+
|
|
340
|
+
Only group when certain conditions are met:
|
|
341
|
+
|
|
342
|
+
```tsx
|
|
343
|
+
<MessagePrimitive.GroupedParts
|
|
344
|
+
groupBy={(part, index, parts) => {
|
|
345
|
+
const hasManyParts = parts.length >= 5;
|
|
346
|
+
if (!hasManyParts) return null;
|
|
347
|
+
if (part.type === "tool-call") return ["group-tools"];
|
|
348
|
+
return null;
|
|
349
|
+
}}
|
|
350
|
+
>
|
|
351
|
+
{({ part, children }) => {
|
|
352
|
+
if (part.type === "group-tools") {
|
|
353
|
+
return <div className="rounded-lg border p-3">{children}</div>;
|
|
354
|
+
}
|
|
355
|
+
if (part.type === "tool-call") return part.toolUI ?? <ToolFallback {...part} />;
|
|
356
|
+
if (part.type === "text") return <MarkdownText />;
|
|
357
|
+
return null;
|
|
358
|
+
}}
|
|
359
|
+
</MessagePrimitive.GroupedParts>
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Nested Grouping
|
|
363
|
+
|
|
364
|
+
Create hierarchical adjacent groups by returning multiple keys:
|
|
365
|
+
|
|
366
|
+
```tsx
|
|
367
|
+
<MessagePrimitive.GroupedParts
|
|
368
|
+
groupBy={(part) => {
|
|
369
|
+
if (part.type === "reasoning")
|
|
370
|
+
return ["group-chainOfThought", "group-reasoning"];
|
|
371
|
+
if (part.type === "tool-call")
|
|
372
|
+
return ["group-chainOfThought", `group-tool-${part.toolName}`];
|
|
373
|
+
return null;
|
|
374
|
+
}}
|
|
375
|
+
>
|
|
376
|
+
{({ part, children }) => {
|
|
377
|
+
if (part.type === "group-chainOfThought") return <div>{children}</div>;
|
|
378
|
+
if (part.type === "group-reasoning") return <ReasoningRoot>{children}</ReasoningRoot>;
|
|
379
|
+
if (part.type.startsWith("group-tool-")) return <div>{children}</div>;
|
|
380
|
+
if (part.type === "reasoning") return <Reasoning {...part} />;
|
|
381
|
+
if (part.type === "tool-call") return part.toolUI ?? <ToolFallback {...part} />;
|
|
382
|
+
if (part.type === "text") return <MarkdownText />;
|
|
383
|
+
return null;
|
|
384
|
+
}}
|
|
385
|
+
</MessagePrimitive.GroupedParts>
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### Dynamic Group Rendering
|
|
389
|
+
|
|
390
|
+
Adjust group appearance based on the group node's status or indices:
|
|
391
|
+
|
|
392
|
+
```tsx
|
|
393
|
+
<MessagePrimitive.GroupedParts
|
|
394
|
+
groupBy={(part) => {
|
|
395
|
+
if (part.type === "tool-call") return ["group-tools"];
|
|
396
|
+
return null;
|
|
397
|
+
}}
|
|
398
|
+
>
|
|
399
|
+
{({ part, children }) => {
|
|
400
|
+
if (part.type === "group-tools") {
|
|
401
|
+
const isRunning = part.status.type === "running";
|
|
402
|
+
|
|
403
|
+
return (
|
|
404
|
+
<div
|
|
405
|
+
className={`my-2 rounded-lg border p-4 ${
|
|
406
|
+
isRunning ? "animate-pulse border-blue-500" : ""
|
|
407
|
+
}`}
|
|
408
|
+
>
|
|
409
|
+
<div className="mb-2 text-sm text-muted-foreground">
|
|
410
|
+
{part.indices.length} tool calls
|
|
411
|
+
</div>
|
|
412
|
+
{children}
|
|
413
|
+
</div>
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (part.type === "tool-call") return part.toolUI ?? <ToolFallback {...part} />;
|
|
418
|
+
if (part.type === "text") return <MarkdownText />;
|
|
419
|
+
return null;
|
|
420
|
+
}}
|
|
421
|
+
</MessagePrimitive.GroupedParts>
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
## Non-adjacent grouping (Unstable)
|
|
425
|
+
|
|
426
|
+
Use `MessagePrimitive.Unstable_PartsGrouped` only when you need to collect non-adjacent parts into the same rendered group, such as gathering every part with the same parent ID even when other parts appear between them. This API is unstable and should not be used for normal consecutive reasoning or tool-call grouping.
|
|
427
|
+
|
|
428
|
+
```tsx
|
|
429
|
+
<MessagePrimitive.Unstable_PartsGrouped
|
|
430
|
+
groupingFunction={(parts) => {
|
|
431
|
+
const groups = new Map<string, number[]>();
|
|
432
|
+
|
|
433
|
+
parts.forEach((part, index) => {
|
|
434
|
+
const key = part.parentId ?? `__ungrouped_${index}`;
|
|
435
|
+
const indices = groups.get(key) ?? [];
|
|
436
|
+
indices.push(index);
|
|
437
|
+
groups.set(key, indices);
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
return Array.from(groups.entries()).map(([key, indices]) => ({
|
|
441
|
+
groupKey: key.startsWith("__ungrouped_") ? undefined : key,
|
|
442
|
+
indices,
|
|
443
|
+
}));
|
|
444
|
+
}}
|
|
445
|
+
components={{
|
|
446
|
+
Text: MarkdownText,
|
|
447
|
+
tools: { Fallback: ToolFallback },
|
|
448
|
+
Group: ({ groupKey, children }) => {
|
|
449
|
+
if (!groupKey) return <>{children}</>;
|
|
450
|
+
return <div className="rounded-lg border p-3">{children}</div>;
|
|
451
|
+
},
|
|
452
|
+
}}
|
|
453
|
+
/>
|
|
454
|
+
```
|
|
376
455
|
|
|
377
456
|
<ParametersTable
|
|
378
457
|
type="MessagePrimitiveUnstable_PartsGroupedProps"
|
|
379
458
|
parameters={[
|
|
380
459
|
{
|
|
381
460
|
name: "groupingFunction",
|
|
382
|
-
type: "(parts: readonly
|
|
461
|
+
type: "(parts: readonly PartState[]) => MessagePartGroup[]",
|
|
383
462
|
description:
|
|
384
|
-
"Function that takes
|
|
463
|
+
"Function that takes all message parts and returns non-adjacent group descriptors.",
|
|
385
464
|
required: true,
|
|
386
465
|
},
|
|
387
466
|
{
|
|
388
467
|
name: "components",
|
|
389
468
|
type: "object",
|
|
390
469
|
description:
|
|
391
|
-
"
|
|
470
|
+
"Legacy component map used to render message part types and group wrappers.",
|
|
392
471
|
children: [
|
|
393
472
|
{
|
|
394
473
|
type: "Components",
|
|
@@ -465,70 +544,3 @@ The `Group` component receives:
|
|
|
465
544
|
- `groupKey`: The group identifier (or `undefined` for ungrouped parts)
|
|
466
545
|
- `indices`: Array of indices for the parts in this group
|
|
467
546
|
- `children`: The rendered message part components
|
|
468
|
-
|
|
469
|
-
## Best Practices
|
|
470
|
-
|
|
471
|
-
1. **Keep grouping logic simple**: Complex grouping functions can impact performance
|
|
472
|
-
2. **Handle ungrouped parts**: Always consider parts that don't match any group criteria
|
|
473
|
-
3. **Maintain order**: Consider the visual flow when ordering groups
|
|
474
|
-
4. **Use meaningful keys**: Group keys should be descriptive for debugging
|
|
475
|
-
5. **Test edge cases**: Empty messages, single parts, and large numbers of parts
|
|
476
|
-
|
|
477
|
-
## Common Patterns
|
|
478
|
-
|
|
479
|
-
### Conditional Grouping
|
|
480
|
-
|
|
481
|
-
Only group when certain conditions are met:
|
|
482
|
-
|
|
483
|
-
```tsx
|
|
484
|
-
const conditionalGrouping = (parts: readonly any[]) => {
|
|
485
|
-
// Only group if there are enough parts
|
|
486
|
-
if (parts.length < 5) {
|
|
487
|
-
return [{ groupKey: undefined, indices: parts.map((_, i) => i) }];
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
// Your grouping logic here
|
|
491
|
-
};
|
|
492
|
-
```
|
|
493
|
-
|
|
494
|
-
### Nested Grouping
|
|
495
|
-
|
|
496
|
-
Create hierarchical groups:
|
|
497
|
-
|
|
498
|
-
```tsx
|
|
499
|
-
const nestedGrouping = (parts: readonly any[]) => {
|
|
500
|
-
// First level: group by type
|
|
501
|
-
// Second level: group by subtype or metadata
|
|
502
|
-
// Implementation depends on your specific needs
|
|
503
|
-
};
|
|
504
|
-
```
|
|
505
|
-
|
|
506
|
-
### Dynamic Group Rendering
|
|
507
|
-
|
|
508
|
-
Adjust group appearance based on content:
|
|
509
|
-
|
|
510
|
-
```tsx
|
|
511
|
-
import { FC, PropsWithChildren } from "react";
|
|
512
|
-
import { useAuiState } from "@assistant-ui/react";
|
|
513
|
-
|
|
514
|
-
const DynamicGroup: FC<
|
|
515
|
-
PropsWithChildren<{ groupKey: string | undefined; indices: number[] }>
|
|
516
|
-
> = ({ groupKey, indices, children }) => {
|
|
517
|
-
const parts = useAuiState((s) => s.message.parts);
|
|
518
|
-
const groupParts = indices.map((i) => parts[i]);
|
|
519
|
-
|
|
520
|
-
// Analyze group content
|
|
521
|
-
const hasErrors = groupParts.some((p) => p.error);
|
|
522
|
-
const isLoading = groupParts.some((p) => p.status?.type === "running");
|
|
523
|
-
|
|
524
|
-
if (!groupKey) return <>{children}</>;
|
|
525
|
-
|
|
526
|
-
return (
|
|
527
|
-
<div
|
|
528
|
-
className={`my-2 rounded-lg border p-4 ${hasErrors ? "border-red-500 bg-red-50" : ""} ${isLoading ? "animate-pulse" : ""} `}
|
|
529
|
-
>
|
|
530
|
-
{children}
|
|
531
|
-
</div>
|
|
532
|
-
);
|
|
533
|
-
};
|
|
534
|
-
```
|