@assistant-ui/mcp-docs-server 0.1.39 → 0.2.1
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 +17 -18
- package/.docs/organized/code-examples/with-a2a.md +20 -15
- package/.docs/organized/code-examples/with-ag-ui.md +19 -17
- package/.docs/organized/code-examples/with-ai-sdk-v7.md +17 -16
- package/.docs/organized/code-examples/with-artifacts.md +471 -145
- package/.docs/organized/code-examples/with-assistant-transport.md +23 -31
- package/.docs/organized/code-examples/with-browser-extension.md +21 -14
- package/.docs/organized/code-examples/with-chain-of-thought.md +22 -20
- package/.docs/organized/code-examples/with-cloud-standalone.md +13 -14
- package/.docs/organized/code-examples/with-cloud.md +22 -17
- package/.docs/organized/code-examples/with-custom-thread-list.md +17 -16
- package/.docs/organized/code-examples/with-elevenlabs-conversational.md +17 -17
- package/.docs/organized/code-examples/with-elevenlabs-scribe.md +21 -19
- package/.docs/organized/code-examples/with-eve.md +70 -16
- package/.docs/organized/code-examples/with-expo.md +32 -51
- package/.docs/organized/code-examples/with-external-store.md +20 -15
- package/.docs/organized/code-examples/with-ffmpeg.md +24 -18
- package/.docs/organized/code-examples/with-generative-ui.md +20 -21
- package/.docs/organized/code-examples/with-google-adk.md +21 -16
- package/.docs/organized/code-examples/with-heat-graph.md +10 -11
- package/.docs/organized/code-examples/with-image-generation.md +13 -14
- package/.docs/organized/code-examples/with-interactables.md +17 -16
- package/.docs/organized/code-examples/with-langchain.md +13 -14
- package/.docs/organized/code-examples/with-langgraph.md +23 -17
- package/.docs/organized/code-examples/with-livekit.md +17 -17
- package/.docs/organized/code-examples/with-mcp.md +36 -32
- package/.docs/organized/code-examples/with-nuxt.md +2492 -0
- package/.docs/organized/code-examples/with-opencode.md +27 -21
- package/.docs/organized/code-examples/with-pi.md +69 -67
- package/.docs/organized/code-examples/with-react-hook-form.md +18 -17
- package/.docs/organized/code-examples/with-react-ink-web.md +10 -11
- package/.docs/organized/code-examples/with-react-ink.md +7 -7
- package/.docs/organized/code-examples/with-react-router.md +23 -17
- package/.docs/organized/code-examples/with-resumable-stream.md +15 -16
- package/.docs/organized/code-examples/with-store.md +31 -20
- package/.docs/organized/code-examples/with-tanstack.md +22 -16
- package/.docs/organized/code-examples/with-tap-runtime.md +19 -19
- package/.docs/organized/code-examples/with-virtualized-thread.md +12 -13
- package/.docs/organized/code-examples/with-vue.md +408 -0
- package/.docs/raw/docs/(docs)/cli.mdx +3 -1
- package/.docs/raw/docs/(docs)/devtools.mdx +7 -2
- package/.docs/raw/docs/(docs)/index.mdx +9 -76
- package/.docs/raw/docs/(docs)/installation.mdx +4 -18
- package/.docs/raw/docs/(reference)/api-reference/context-providers/assistant-runtime-provider.mdx +24 -4
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/a2ui.mdx +40 -0
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/index.mdx +3 -0
- package/.docs/raw/docs/(reference)/api-reference/hooks/primitives.mdx +19 -420
- package/.docs/raw/docs/(reference)/api-reference/hooks/state.mdx +4 -1
- package/.docs/raw/docs/(reference)/api-reference/integrations/eve.mdx +8 -0
- package/.docs/raw/docs/(reference)/api-reference/integrations/react-ai-sdk.mdx +21 -0
- package/.docs/raw/docs/(reference)/api-reference/model-context/context.mdx +1 -9
- package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-if.mdx +1 -18
- package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +24 -1
- package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +1 -1
- package/.docs/raw/docs/cloud/ai-sdk.mdx +2 -2
- package/.docs/raw/docs/cloud/langgraph.mdx +1 -1
- package/.docs/raw/docs/copilots/model-context.mdx +5 -4
- package/.docs/raw/docs/copilots/motivation.mdx +5 -5
- package/.docs/raw/docs/guides/attachments.mdx +3 -3
- package/.docs/raw/docs/guides/branching.mdx +2 -2
- package/.docs/raw/docs/guides/chain-of-thought.mdx +1 -1
- package/.docs/raw/docs/guides/context-api.mdx +89 -111
- package/.docs/raw/docs/guides/editing.mdx +5 -5
- package/.docs/raw/docs/guides/electron.mdx +369 -0
- package/.docs/raw/docs/guides/index.mdx +10 -0
- package/.docs/raw/docs/guides/quoting.mdx +3 -3
- package/.docs/raw/docs/guides/resumable-stream-deployment.mdx +2 -2
- package/.docs/raw/docs/guides/resumable-streams.mdx +74 -3
- package/.docs/raw/docs/guides/suggestions.mdx +9 -9
- package/.docs/raw/docs/ink/hooks.mdx +12 -7
- package/.docs/raw/docs/ink/primitives.mdx +18 -10
- package/.docs/raw/docs/integrations/attachments/custom-adapter.mdx +16 -0
- package/.docs/raw/docs/integrations/auth/better-auth.mdx +3 -3
- package/.docs/raw/docs/integrations/auth/clerk.mdx +3 -3
- package/.docs/raw/docs/integrations/auth/next-auth.mdx +4 -4
- package/.docs/raw/docs/integrations/frameworks/cloudflare-agents/overview.mdx +1 -1
- package/.docs/raw/docs/integrations/frameworks/mastra/full-stack.mdx +2 -2
- package/.docs/raw/docs/integrations/frameworks/mastra/separate-server.mdx +1 -1
- package/.docs/raw/docs/integrations/gateways/index.mdx +2 -2
- package/.docs/raw/docs/integrations/observability/helicone.mdx +2 -2
- package/.docs/raw/docs/integrations/observability/langfuse.mdx +1 -1
- package/.docs/raw/docs/integrations/observability/langsmith.mdx +3 -3
- package/.docs/raw/docs/integrations/persistence/custom-adapter.mdx +36 -9
- package/.docs/raw/docs/migrations/toolkit-tools.mdx +15 -13
- package/.docs/raw/docs/migrations/v0-15.mdx +236 -0
- package/.docs/raw/docs/primitives/attachment.mdx +2 -2
- package/.docs/raw/docs/primitives/composer.mdx +3 -3
- package/.docs/raw/docs/primitives/message.mdx +33 -1
- package/.docs/raw/docs/primitives/suggestion.mdx +1 -1
- package/.docs/raw/docs/primitives/thread-list.mdx +2 -2
- package/.docs/raw/docs/react-native/hooks.mdx +17 -7
- package/.docs/raw/docs/react-native/index.mdx +3 -3
- package/.docs/raw/docs/react-native/primitives.mdx +41 -7
- package/.docs/raw/docs/runtimes/ai-sdk/overview.mdx +3 -3
- package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +5 -5
- package/.docs/raw/docs/runtimes/ai-sdk/v5-legacy.mdx +5 -5
- package/.docs/raw/docs/runtimes/ai-sdk/v6-legacy.mdx +13 -14
- package/.docs/raw/docs/runtimes/ai-sdk/v7.mdx +11 -12
- package/.docs/raw/docs/runtimes/concepts/threads.mdx +50 -11
- package/.docs/raw/docs/runtimes/custom/assistant-transport.mdx +20 -2
- package/.docs/raw/docs/runtimes/custom/external-store.mdx +31 -2
- package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +39 -12
- package/.docs/raw/docs/runtimes/eve/overview.mdx +51 -0
- package/.docs/raw/docs/runtimes/eve/quickstart.mdx +51 -2
- package/.docs/raw/docs/runtimes/google-adk/hooks.mdx +9 -12
- package/.docs/raw/docs/runtimes/langchain.mdx +2 -2
- package/.docs/raw/docs/runtimes/langgraph/overview.mdx +1 -1
- package/.docs/raw/docs/runtimes/langgraph/streaming.mdx +1 -1
- package/.docs/raw/docs/runtimes/langgraph/tutorial/part-2.mdx +7 -7
- package/.docs/raw/docs/runtimes/langgraph/tutorial/part-3.mdx +4 -4
- package/.docs/raw/docs/runtimes/opencode/hooks.mdx +4 -1
- package/.docs/raw/docs/runtimes/opencode/overview.mdx +10 -0
- package/.docs/raw/docs/tools/a2ui.mdx +107 -0
- package/.docs/raw/docs/tools/backend.mdx +2 -2
- package/.docs/raw/docs/tools/defining-tools.mdx +9 -7
- package/.docs/raw/docs/tools/dynamic-tools.mdx +6 -4
- package/.docs/raw/docs/tools/interactables-legacy.mdx +28 -15
- package/.docs/raw/docs/tools/interactables.mdx +27 -16
- package/.docs/raw/docs/tools/mcp-apps.mdx +96 -8
- package/.docs/raw/docs/tools/mcp.mdx +9 -7
- package/.docs/raw/docs/tools/tool-ui.mdx +26 -22
- package/.docs/raw/docs/tools/user-managed-mcp.mdx +64 -14
- package/.docs/raw/docs/ui/file.mdx +6 -1
- package/.docs/raw/docs/ui/follow-up-suggestions.mdx +2 -0
- package/.docs/raw/docs/ui/mcp-config.mdx +8 -3
- package/.docs/raw/docs/ui/model-selector.mdx +9 -9
- package/.docs/raw/docs/ui/part-grouping.mdx +1 -5
- package/.docs/raw/docs/ui/reasoning.mdx +1 -1
- package/.docs/raw/docs/ui/thread.mdx +24 -5
- package/.docs/raw/docs/utilities/react-o11y.mdx +7 -9
- package/dist/constants.js +2 -2
- package/dist/constants.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/prepare-docs/code-examples.js.map +1 -1
- package/dist/prepare-docs/prepare.d.ts +1 -1
- package/dist/prepare-docs/prepare.js.map +1 -1
- package/dist/stdio.d.ts +1 -1
- package/dist/tools/docs.d.ts +6 -10
- package/dist/tools/docs.d.ts.map +1 -1
- package/dist/tools/docs.js +6 -4
- package/dist/tools/docs.js.map +1 -1
- package/dist/tools/examples.d.ts +4 -8
- package/dist/tools/examples.d.ts.map +1 -1
- package/dist/tools/examples.js +4 -3
- package/dist/tools/examples.js.map +1 -1
- package/dist/tools/resources.d.ts +1 -1
- package/dist/tools/resources.d.ts.map +1 -1
- package/dist/tools/resources.js +3 -2
- package/dist/tools/resources.js.map +1 -1
- package/dist/tools/search.d.ts +4 -10
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/search.js +2 -2
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/tests/mcp-test-client.d.ts +15 -0
- package/dist/tools/tests/mcp-test-client.d.ts.map +1 -0
- package/dist/tools/tests/mcp-test-client.js +68 -0
- package/dist/tools/tests/mcp-test-client.js.map +1 -0
- package/dist/tools/tests/test-setup.d.ts.map +1 -1
- package/dist/tools/tests/test-setup.js +2 -1
- package/dist/tools/tests/test-setup.js.map +1 -1
- package/dist/tools/xulux-templates.d.ts +9 -23
- package/dist/tools/xulux-templates.d.ts.map +1 -1
- package/dist/tools/xulux-templates.js +8 -6
- package/dist/tools/xulux-templates.js.map +1 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/mdx.js +2 -1
- package/dist/utils/mdx.js.map +1 -1
- package/dist/utils/security.js.map +1 -1
- package/dist/xulux/catalog-client.js +1 -1
- package/dist/xulux/catalog-client.js.map +1 -1
- package/package.json +6 -5
- package/src/index.ts +2 -2
- package/src/tools/docs.ts +2 -2
- package/src/tools/examples.ts +2 -2
- package/src/tools/resources.ts +1 -4
- package/src/tools/search.ts +2 -2
- package/src/tools/tests/completions.test.ts +40 -26
- package/src/tools/tests/docs.test.ts +2 -2
- package/src/tools/tests/integration.test.ts +3 -4
- package/src/tools/tests/mcp-protocol.test.ts +160 -175
- package/src/tools/tests/mcp-test-client.ts +111 -0
- package/src/tools/tests/resources.test.ts +97 -66
- package/src/tools/xulux-templates.ts +4 -4
|
@@ -4,45 +4,43 @@
|
|
|
4
4
|
|
|
5
5
|
```typescript
|
|
6
6
|
import { openai } from "@ai-sdk/openai";
|
|
7
|
-
import {
|
|
8
|
-
streamText,
|
|
9
|
-
convertToModelMessages,
|
|
10
|
-
tool,
|
|
11
|
-
stepCountIs,
|
|
12
|
-
zodSchema,
|
|
13
|
-
} from "ai";
|
|
7
|
+
import { convertToModelMessages, stepCountIs, streamText } from "ai";
|
|
14
8
|
import type { UIMessage } from "ai";
|
|
15
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
AISDKToolkit,
|
|
11
|
+
unstable_injectInteractableContext,
|
|
12
|
+
type AISDKToolkitToolsOptions,
|
|
13
|
+
} from "@assistant-ui/react-ai-sdk";
|
|
14
|
+
import toolkit from "../../toolkit";
|
|
16
15
|
|
|
17
16
|
export const maxDuration = 30;
|
|
18
17
|
|
|
18
|
+
const aiToolkit = new AISDKToolkit({ toolkit });
|
|
19
|
+
|
|
20
|
+
type FrontendToolDefs = NonNullable<AISDKToolkitToolsOptions["frontend"]>;
|
|
21
|
+
|
|
19
22
|
export async function POST(req: Request) {
|
|
20
|
-
const {
|
|
23
|
+
const {
|
|
24
|
+
messages,
|
|
25
|
+
tools: clientTools,
|
|
26
|
+
}: {
|
|
27
|
+
messages: UIMessage[];
|
|
28
|
+
tools?: FrontendToolDefs;
|
|
29
|
+
} = await req.json();
|
|
30
|
+
|
|
31
|
+
const tools = await aiToolkit.tools({
|
|
32
|
+
...(clientTools && { frontend: clientTools }),
|
|
33
|
+
});
|
|
21
34
|
|
|
22
35
|
const result = streamText({
|
|
23
36
|
model: openai("gpt-5.4-nano"),
|
|
24
37
|
system:
|
|
25
|
-
"You
|
|
26
|
-
messages: await convertToModelMessages(
|
|
38
|
+
"You create functional HTML artifacts. When the user requests a webpage, interface, visualization, or other visual content, call the artifact tool with a short title and a complete HTML document. When an artifact already exists and the user requests a change, call update_artifact with its id and only the fields that changed. Do not put artifact HTML in a code block.",
|
|
39
|
+
messages: await convertToModelMessages(
|
|
40
|
+
unstable_injectInteractableContext(messages),
|
|
41
|
+
),
|
|
27
42
|
stopWhen: stepCountIs(10),
|
|
28
|
-
tools
|
|
29
|
-
render_html: tool({
|
|
30
|
-
description:
|
|
31
|
-
"Render HTML code in the user's browser. Use this whenever the user asks for HTML, a webpage, a UI component, or any visual content.",
|
|
32
|
-
inputSchema: zodSchema(
|
|
33
|
-
z.object({
|
|
34
|
-
code: z
|
|
35
|
-
.string()
|
|
36
|
-
.describe(
|
|
37
|
-
"The complete HTML code to render, including inline CSS and JavaScript if needed.",
|
|
38
|
-
),
|
|
39
|
-
}),
|
|
40
|
-
),
|
|
41
|
-
execute: async () => {
|
|
42
|
-
return { success: true };
|
|
43
|
-
},
|
|
44
|
-
}),
|
|
45
|
-
},
|
|
43
|
+
tools,
|
|
46
44
|
});
|
|
47
45
|
|
|
48
46
|
return result.toUIMessageStreamResponse();
|
|
@@ -50,6 +48,391 @@ export async function POST(req: Request) {
|
|
|
50
48
|
|
|
51
49
|
```
|
|
52
50
|
|
|
51
|
+
## app/artifact-state.ts
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { z } from "zod";
|
|
55
|
+
|
|
56
|
+
export const artifactSchema = z.object({
|
|
57
|
+
title: z.string().describe("A short title for the artifact."),
|
|
58
|
+
code: z
|
|
59
|
+
.string()
|
|
60
|
+
.describe(
|
|
61
|
+
"A complete HTML document with inline CSS and JavaScript when needed.",
|
|
62
|
+
),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
export type ArtifactState = z.infer<typeof artifactSchema>;
|
|
66
|
+
|
|
67
|
+
export const emptyArtifact: ArtifactState = {
|
|
68
|
+
title: "Untitled artifact",
|
|
69
|
+
code: "",
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const artifactDescription =
|
|
73
|
+
"Create an interactive HTML artifact when the user asks for a webpage, interface, visualization, or other visual content. Provide a short title and a complete HTML document.";
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## app/artifact-surface.tsx
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
"use client";
|
|
81
|
+
|
|
82
|
+
import {
|
|
83
|
+
createContext,
|
|
84
|
+
useContext,
|
|
85
|
+
useEffect,
|
|
86
|
+
useMemo,
|
|
87
|
+
useRef,
|
|
88
|
+
useState,
|
|
89
|
+
type ReactNode,
|
|
90
|
+
} from "react";
|
|
91
|
+
import {
|
|
92
|
+
unstable_useInteractable,
|
|
93
|
+
unstable_useInteractableVersions,
|
|
94
|
+
useAuiState,
|
|
95
|
+
type Unstable_InteractableToolRenderProps,
|
|
96
|
+
} from "@assistant-ui/react";
|
|
97
|
+
import {
|
|
98
|
+
ArrowLeftIcon,
|
|
99
|
+
CheckIcon,
|
|
100
|
+
Code2Icon,
|
|
101
|
+
EyeIcon,
|
|
102
|
+
FileCode2Icon,
|
|
103
|
+
HistoryIcon,
|
|
104
|
+
Loader2Icon,
|
|
105
|
+
PanelRightOpenIcon,
|
|
106
|
+
SparklesIcon,
|
|
107
|
+
XIcon,
|
|
108
|
+
} from "lucide-react";
|
|
109
|
+
import {
|
|
110
|
+
artifactDescription,
|
|
111
|
+
artifactSchema,
|
|
112
|
+
emptyArtifact,
|
|
113
|
+
type ArtifactState,
|
|
114
|
+
} from "./artifact-state";
|
|
115
|
+
|
|
116
|
+
type ArtifactSurfaceTarget = {
|
|
117
|
+
id: string;
|
|
118
|
+
name: "artifact";
|
|
119
|
+
messageId: string;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
type ArtifactSurfaceContextValue = {
|
|
123
|
+
active: ArtifactSurfaceTarget | null;
|
|
124
|
+
open: (target: ArtifactSurfaceTarget) => void;
|
|
125
|
+
close: () => void;
|
|
126
|
+
returnToOrigin: () => void;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const ArtifactSurfaceContext =
|
|
130
|
+
createContext<ArtifactSurfaceContextValue | null>(null);
|
|
131
|
+
|
|
132
|
+
export function ArtifactSurfaceProvider({ children }: { children: ReactNode }) {
|
|
133
|
+
const [active, setActive] = useState<ArtifactSurfaceTarget | null>(null);
|
|
134
|
+
|
|
135
|
+
const value = useMemo<ArtifactSurfaceContextValue>(
|
|
136
|
+
() => ({
|
|
137
|
+
active,
|
|
138
|
+
open: setActive,
|
|
139
|
+
close: () => setActive(null),
|
|
140
|
+
returnToOrigin: () => {
|
|
141
|
+
const messageId = active?.messageId;
|
|
142
|
+
setActive(null);
|
|
143
|
+
if (!messageId) return;
|
|
144
|
+
requestAnimationFrame(() => {
|
|
145
|
+
const message = Array.from(
|
|
146
|
+
document.querySelectorAll<HTMLElement>("[data-message-id]"),
|
|
147
|
+
).find((element) => element.dataset.messageId === messageId);
|
|
148
|
+
message?.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
149
|
+
});
|
|
150
|
+
},
|
|
151
|
+
}),
|
|
152
|
+
[active],
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<ArtifactSurfaceContext.Provider value={value}>
|
|
157
|
+
{children}
|
|
158
|
+
</ArtifactSurfaceContext.Provider>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function useArtifactSurface() {
|
|
163
|
+
const value = useContext(ArtifactSurfaceContext);
|
|
164
|
+
if (!value) {
|
|
165
|
+
throw new Error(
|
|
166
|
+
"useArtifactSurface must be used within ArtifactSurfaceProvider",
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
return value;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function ArtifactTrigger({
|
|
173
|
+
state,
|
|
174
|
+
version,
|
|
175
|
+
id,
|
|
176
|
+
streaming,
|
|
177
|
+
}: Unstable_InteractableToolRenderProps<ArtifactState>) {
|
|
178
|
+
const messageId = useAuiState((s) => s.message.id);
|
|
179
|
+
const { active, open } = useArtifactSurface();
|
|
180
|
+
const displayState = version && !version.isLatest ? version.state : state;
|
|
181
|
+
const title = displayState.title || "Untitled artifact";
|
|
182
|
+
const lineCount = displayState.code
|
|
183
|
+
? displayState.code.split(/\r?\n/).length
|
|
184
|
+
: 0;
|
|
185
|
+
const isOpen = active?.id === id;
|
|
186
|
+
|
|
187
|
+
return (
|
|
188
|
+
<div className="bg-card my-3 overflow-hidden rounded-xl border shadow-[0_10px_30px_-22px_rgba(24,24,27,0.5)]">
|
|
189
|
+
<div className="bg-muted text-muted-foreground flex items-center gap-2 border-b px-3 py-2 font-mono text-[10px] tracking-[0.16em] uppercase">
|
|
190
|
+
<FileCode2Icon className="size-3.5" />
|
|
191
|
+
HTML artifact
|
|
192
|
+
{streaming ? (
|
|
193
|
+
<span className="ml-auto inline-flex items-center gap-1.5 text-amber-700">
|
|
194
|
+
<Loader2Icon className="size-3 animate-spin" />
|
|
195
|
+
Writing
|
|
196
|
+
</span>
|
|
197
|
+
) : (
|
|
198
|
+
<span className="ml-auto inline-flex items-center gap-1.5 text-emerald-700">
|
|
199
|
+
<CheckIcon className="size-3" />
|
|
200
|
+
Ready
|
|
201
|
+
</span>
|
|
202
|
+
)}
|
|
203
|
+
</div>
|
|
204
|
+
<div className="flex items-center gap-4 p-3">
|
|
205
|
+
<div className="min-w-0 flex-1">
|
|
206
|
+
<p className="truncate text-sm font-semibold">{title}</p>
|
|
207
|
+
<p className="text-muted-foreground mt-0.5 text-xs">
|
|
208
|
+
{lineCount > 0 ? `${lineCount} lines` : "Preparing document"}
|
|
209
|
+
{version && !version.isLatest ? " · historical version" : ""}
|
|
210
|
+
</p>
|
|
211
|
+
</div>
|
|
212
|
+
<button
|
|
213
|
+
type="button"
|
|
214
|
+
data-testid="artifact-trigger"
|
|
215
|
+
disabled={streaming}
|
|
216
|
+
onClick={() =>
|
|
217
|
+
open({
|
|
218
|
+
id,
|
|
219
|
+
name: "artifact",
|
|
220
|
+
messageId,
|
|
221
|
+
})
|
|
222
|
+
}
|
|
223
|
+
className="bg-primary text-primary-foreground hover:bg-primary/90 inline-flex h-9 shrink-0 items-center gap-2 rounded-lg px-3 text-xs font-medium transition-colors disabled:cursor-wait disabled:opacity-50"
|
|
224
|
+
>
|
|
225
|
+
<PanelRightOpenIcon className="size-3.5" />
|
|
226
|
+
{isOpen ? "In workbench" : "Open workbench"}
|
|
227
|
+
</button>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function ArtifactSurface() {
|
|
234
|
+
const { active } = useArtifactSurface();
|
|
235
|
+
if (!active) return null;
|
|
236
|
+
return <ArtifactWorkbench key={active.id} target={active} />;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function ArtifactWorkbench({ target }: { target: ArtifactSurfaceTarget }) {
|
|
240
|
+
const [state, { setState }] = unstable_useInteractable("artifact", {
|
|
241
|
+
id: target.id,
|
|
242
|
+
description: artifactDescription,
|
|
243
|
+
stateSchema: artifactSchema,
|
|
244
|
+
initialState: emptyArtifact,
|
|
245
|
+
});
|
|
246
|
+
const versions = unstable_useInteractableVersions<ArtifactState>(
|
|
247
|
+
target.id,
|
|
248
|
+
target.name,
|
|
249
|
+
);
|
|
250
|
+
const { close, returnToOrigin } = useArtifactSurface();
|
|
251
|
+
const [view, setView] = useState<"preview" | "code">("preview");
|
|
252
|
+
const [historyOpen, setHistoryOpen] = useState(false);
|
|
253
|
+
const historyRef = useRef<HTMLDivElement>(null);
|
|
254
|
+
|
|
255
|
+
useEffect(() => {
|
|
256
|
+
if (!historyOpen) return;
|
|
257
|
+
const dismissPointer = (event: PointerEvent) => {
|
|
258
|
+
if (!historyRef.current?.contains(event.target as Node)) {
|
|
259
|
+
setHistoryOpen(false);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
const dismissKey = (event: KeyboardEvent) => {
|
|
263
|
+
if (event.key === "Escape") setHistoryOpen(false);
|
|
264
|
+
};
|
|
265
|
+
const dismissBlur = () => setHistoryOpen(false);
|
|
266
|
+
document.addEventListener("pointerdown", dismissPointer);
|
|
267
|
+
document.addEventListener("keydown", dismissKey);
|
|
268
|
+
window.addEventListener("blur", dismissBlur);
|
|
269
|
+
return () => {
|
|
270
|
+
document.removeEventListener("pointerdown", dismissPointer);
|
|
271
|
+
document.removeEventListener("keydown", dismissKey);
|
|
272
|
+
window.removeEventListener("blur", dismissBlur);
|
|
273
|
+
};
|
|
274
|
+
}, [historyOpen]);
|
|
275
|
+
|
|
276
|
+
return (
|
|
277
|
+
<aside
|
|
278
|
+
data-testid="artifact-surface"
|
|
279
|
+
className="bg-muted fixed inset-0 z-30 flex min-w-0 flex-1 flex-col md:static md:z-0 md:border-l"
|
|
280
|
+
>
|
|
281
|
+
<header className="border-border bg-background/90 flex h-14 shrink-0 items-center gap-3 border-b px-3 backdrop-blur md:px-5">
|
|
282
|
+
<div className="flex min-w-0 items-center gap-2">
|
|
283
|
+
<span className="grid size-7 shrink-0 place-items-center rounded-lg bg-amber-300 text-zinc-950 shadow-[inset_0_0_0_1px_rgba(24,24,27,0.12)]">
|
|
284
|
+
<SparklesIcon className="size-3.5" />
|
|
285
|
+
</span>
|
|
286
|
+
<div className="min-w-0">
|
|
287
|
+
<p className="truncate text-sm font-semibold">
|
|
288
|
+
{state.title || "Untitled artifact"}
|
|
289
|
+
</p>
|
|
290
|
+
<p className="text-muted-foreground font-mono text-[9px] tracking-[0.14em] uppercase">
|
|
291
|
+
Opened from conversation
|
|
292
|
+
</p>
|
|
293
|
+
</div>
|
|
294
|
+
</div>
|
|
295
|
+
|
|
296
|
+
<div className="ml-auto flex items-center gap-1.5">
|
|
297
|
+
<div className="relative" ref={historyRef}>
|
|
298
|
+
<button
|
|
299
|
+
type="button"
|
|
300
|
+
data-testid="artifact-version-select"
|
|
301
|
+
aria-label="Artifact version history"
|
|
302
|
+
aria-expanded={historyOpen}
|
|
303
|
+
onClick={() => setHistoryOpen((open) => !open)}
|
|
304
|
+
className="border-border bg-card text-foreground hover:bg-muted inline-flex h-8 items-center gap-1.5 rounded-md border px-2.5 text-xs font-medium"
|
|
305
|
+
>
|
|
306
|
+
<HistoryIcon className="size-3.5" />
|
|
307
|
+
{versions.length} {versions.length === 1 ? "version" : "versions"}
|
|
308
|
+
</button>
|
|
309
|
+
{historyOpen && (
|
|
310
|
+
<div className="border-border bg-card absolute top-9 right-0 z-10 flex w-44 flex-col overflow-hidden rounded-md border py-1 shadow-lg">
|
|
311
|
+
{versions.map((version, index) => (
|
|
312
|
+
<button
|
|
313
|
+
key={`${version.origin}-${index}`}
|
|
314
|
+
type="button"
|
|
315
|
+
onClick={() => {
|
|
316
|
+
version.restore();
|
|
317
|
+
setHistoryOpen(false);
|
|
318
|
+
}}
|
|
319
|
+
className="text-foreground hover:bg-muted flex items-center justify-between px-3 py-1.5 text-left text-xs"
|
|
320
|
+
>
|
|
321
|
+
<span>
|
|
322
|
+
v{index + 1} ·{" "}
|
|
323
|
+
{version.origin === "user-edit" ? "you" : "assistant"}
|
|
324
|
+
</span>
|
|
325
|
+
{index === versions.length - 1 && (
|
|
326
|
+
<span className="text-muted-foreground font-mono text-[9px] uppercase">
|
|
327
|
+
latest
|
|
328
|
+
</span>
|
|
329
|
+
)}
|
|
330
|
+
</button>
|
|
331
|
+
))}
|
|
332
|
+
</div>
|
|
333
|
+
)}
|
|
334
|
+
</div>
|
|
335
|
+
<button
|
|
336
|
+
type="button"
|
|
337
|
+
data-testid="artifact-return"
|
|
338
|
+
aria-label="Return to conversation"
|
|
339
|
+
onClick={returnToOrigin}
|
|
340
|
+
className="border-border bg-card text-foreground hover:bg-muted inline-flex h-8 items-center gap-1.5 rounded-md border px-2.5 text-xs font-medium"
|
|
341
|
+
>
|
|
342
|
+
<ArrowLeftIcon className="size-3.5" />
|
|
343
|
+
<span className="hidden sm:inline">Conversation</span>
|
|
344
|
+
</button>
|
|
345
|
+
<button
|
|
346
|
+
type="button"
|
|
347
|
+
data-testid="artifact-close"
|
|
348
|
+
onClick={close}
|
|
349
|
+
className="text-muted-foreground hover:bg-muted hover:text-foreground grid size-8 place-items-center rounded-md"
|
|
350
|
+
aria-label="Close artifact"
|
|
351
|
+
>
|
|
352
|
+
<XIcon className="size-4" />
|
|
353
|
+
</button>
|
|
354
|
+
</div>
|
|
355
|
+
</header>
|
|
356
|
+
|
|
357
|
+
<div className="flex min-h-0 flex-1 flex-col p-2 md:p-4">
|
|
358
|
+
<div className="border-border bg-card flex min-h-0 flex-1 flex-col overflow-hidden rounded-xl border shadow-[0_24px_70px_-44px_rgba(24,24,27,0.55)]">
|
|
359
|
+
<div className="bg-muted/80 flex shrink-0 items-center gap-1 border-b p-1.5">
|
|
360
|
+
<button
|
|
361
|
+
type="button"
|
|
362
|
+
aria-pressed={view === "preview"}
|
|
363
|
+
onClick={() => setView("preview")}
|
|
364
|
+
className={`inline-flex h-8 items-center gap-2 rounded-md px-3 text-xs font-medium transition-colors ${
|
|
365
|
+
view === "preview"
|
|
366
|
+
? "bg-card text-foreground ring-border shadow-sm ring-1"
|
|
367
|
+
: "text-muted-foreground hover:text-foreground"
|
|
368
|
+
}`}
|
|
369
|
+
>
|
|
370
|
+
<EyeIcon className="size-3.5" />
|
|
371
|
+
Preview
|
|
372
|
+
</button>
|
|
373
|
+
<button
|
|
374
|
+
type="button"
|
|
375
|
+
aria-pressed={view === "code"}
|
|
376
|
+
onClick={() => setView("code")}
|
|
377
|
+
className={`inline-flex h-8 items-center gap-2 rounded-md px-3 text-xs font-medium transition-colors ${
|
|
378
|
+
view === "code"
|
|
379
|
+
? "bg-card text-foreground ring-border shadow-sm ring-1"
|
|
380
|
+
: "text-muted-foreground hover:text-foreground"
|
|
381
|
+
}`}
|
|
382
|
+
>
|
|
383
|
+
<Code2Icon className="size-3.5" />
|
|
384
|
+
Source
|
|
385
|
+
</button>
|
|
386
|
+
<span className="text-muted-foreground ml-auto pr-2 font-mono text-[9px] tracking-[0.14em] uppercase">
|
|
387
|
+
Live document
|
|
388
|
+
</span>
|
|
389
|
+
</div>
|
|
390
|
+
|
|
391
|
+
{view === "preview" ? (
|
|
392
|
+
<iframe
|
|
393
|
+
data-testid="artifact-preview"
|
|
394
|
+
title={`Preview of ${state.title || "artifact"}`}
|
|
395
|
+
srcDoc={state.code}
|
|
396
|
+
sandbox="allow-scripts allow-forms"
|
|
397
|
+
className="min-h-0 flex-1 bg-white"
|
|
398
|
+
/>
|
|
399
|
+
) : (
|
|
400
|
+
<div className="flex min-h-0 flex-1 flex-col">
|
|
401
|
+
<input
|
|
402
|
+
aria-label="Artifact title"
|
|
403
|
+
value={state.title}
|
|
404
|
+
onChange={(event) =>
|
|
405
|
+
setState((previous) => ({
|
|
406
|
+
...previous,
|
|
407
|
+
title: event.target.value,
|
|
408
|
+
}))
|
|
409
|
+
}
|
|
410
|
+
className="placeholder:text-muted-foreground h-11 shrink-0 border-b px-4 text-sm font-semibold outline-none"
|
|
411
|
+
placeholder="Artifact title"
|
|
412
|
+
/>
|
|
413
|
+
<textarea
|
|
414
|
+
data-testid="artifact-code-editor"
|
|
415
|
+
aria-label="Artifact HTML source"
|
|
416
|
+
value={state.code}
|
|
417
|
+
onChange={(event) =>
|
|
418
|
+
setState((previous) => ({
|
|
419
|
+
...previous,
|
|
420
|
+
code: event.target.value,
|
|
421
|
+
}))
|
|
422
|
+
}
|
|
423
|
+
spellCheck={false}
|
|
424
|
+
className="min-h-0 flex-1 resize-none bg-zinc-950 p-4 font-mono text-[13px] leading-6 text-zinc-100 outline-none selection:bg-amber-300 selection:text-zinc-950"
|
|
425
|
+
/>
|
|
426
|
+
</div>
|
|
427
|
+
)}
|
|
428
|
+
</div>
|
|
429
|
+
</div>
|
|
430
|
+
</aside>
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
```
|
|
435
|
+
|
|
53
436
|
## app/globals.css
|
|
54
437
|
|
|
55
438
|
```css
|
|
@@ -211,90 +594,46 @@ export default function RootLayout({
|
|
|
211
594
|
import { Thread } from "@/components/assistant-ui/thread";
|
|
212
595
|
import {
|
|
213
596
|
AssistantRuntimeProvider,
|
|
214
|
-
Tools,
|
|
215
|
-
useAui,
|
|
216
|
-
useAuiState,
|
|
217
597
|
AuiProvider,
|
|
598
|
+
AuiConfig,
|
|
218
599
|
Suggestions,
|
|
600
|
+
Tools,
|
|
601
|
+
unstable_Interactables,
|
|
602
|
+
useAui,
|
|
219
603
|
} from "@assistant-ui/react";
|
|
220
604
|
import { useChatRuntime } from "@assistant-ui/react-ai-sdk";
|
|
221
|
-
import
|
|
222
|
-
import {
|
|
223
|
-
|
|
605
|
+
import { lastAssistantMessageIsCompleteWithToolCalls } from "ai";
|
|
606
|
+
import {
|
|
607
|
+
ArtifactSurface,
|
|
608
|
+
ArtifactSurfaceProvider,
|
|
609
|
+
useArtifactSurface,
|
|
610
|
+
} from "./artifact-surface";
|
|
224
611
|
import toolkit from "./toolkit";
|
|
225
612
|
|
|
226
|
-
function
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
const lastToolCall = useAuiState((s) => {
|
|
230
|
-
const messages = s.thread.messages;
|
|
231
|
-
return messages
|
|
232
|
-
.flatMap((m) =>
|
|
233
|
-
m.content.filter(
|
|
234
|
-
(c): c is ToolCallMessagePart =>
|
|
235
|
-
c.type === "tool-call" && c.toolName === "render_html",
|
|
236
|
-
),
|
|
237
|
-
)
|
|
238
|
-
.at(-1);
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
const code = lastToolCall?.args.code as string | undefined;
|
|
242
|
-
const isComplete = lastToolCall?.result !== undefined;
|
|
243
|
-
|
|
244
|
-
if (!code) return null;
|
|
613
|
+
function ArtifactExperience() {
|
|
614
|
+
const { active } = useArtifactSurface();
|
|
245
615
|
|
|
246
616
|
return (
|
|
247
|
-
<
|
|
248
|
-
<
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
<CodeIcon className="size-4" />
|
|
260
|
-
Source Code
|
|
261
|
-
</button>
|
|
262
|
-
<button
|
|
263
|
-
type="button"
|
|
264
|
-
onClick={() => isComplete && setTab("preview")}
|
|
265
|
-
disabled={!isComplete}
|
|
266
|
-
className={`inline-flex flex-1 items-center justify-center gap-2 px-4 py-2.5 text-sm font-medium transition-colors ${
|
|
267
|
-
!isComplete
|
|
268
|
-
? "cursor-not-allowed opacity-50"
|
|
269
|
-
: tab === "preview"
|
|
270
|
-
? "bg-background text-foreground"
|
|
271
|
-
: "text-muted-foreground hover:text-foreground"
|
|
272
|
-
}`}
|
|
273
|
-
>
|
|
274
|
-
<EyeIcon className="size-4" />
|
|
275
|
-
Preview
|
|
276
|
-
</button>
|
|
277
|
-
</div>
|
|
278
|
-
{tab === "source" || !isComplete ? (
|
|
279
|
-
<div className="h-full overflow-y-auto px-4 py-2 font-mono text-sm break-words whitespace-pre-line">
|
|
280
|
-
{code}
|
|
281
|
-
</div>
|
|
282
|
-
) : (
|
|
283
|
-
<div className="flex h-full flex-grow px-4 py-2">
|
|
284
|
-
<iframe
|
|
285
|
-
className="h-full w-full"
|
|
286
|
-
title="Artifact Preview"
|
|
287
|
-
srcDoc={code}
|
|
288
|
-
/>
|
|
289
|
-
</div>
|
|
290
|
-
)}
|
|
291
|
-
</div>
|
|
292
|
-
</div>
|
|
617
|
+
<main className="bg-background flex h-full min-h-0 overflow-hidden">
|
|
618
|
+
<section
|
|
619
|
+
className={
|
|
620
|
+
active
|
|
621
|
+
? "bg-background hidden min-w-0 md:block md:w-[min(44vw,42rem)] md:shrink-0"
|
|
622
|
+
: "bg-background min-w-0 flex-1"
|
|
623
|
+
}
|
|
624
|
+
>
|
|
625
|
+
<Thread />
|
|
626
|
+
</section>
|
|
627
|
+
<ArtifactSurface />
|
|
628
|
+
</main>
|
|
293
629
|
);
|
|
294
630
|
}
|
|
295
631
|
|
|
296
|
-
function
|
|
297
|
-
const aui = useAui(
|
|
632
|
+
function ArtifactScopes() {
|
|
633
|
+
const aui = useAui();
|
|
634
|
+
const config = AuiConfig({
|
|
635
|
+
tools: Tools({ toolkit }),
|
|
636
|
+
unstable_interactables: unstable_Interactables(),
|
|
298
637
|
suggestions: Suggestions([
|
|
299
638
|
{
|
|
300
639
|
title: "Build a landing page",
|
|
@@ -310,27 +649,24 @@ function ThreadWithSuggestions() {
|
|
|
310
649
|
},
|
|
311
650
|
]),
|
|
312
651
|
});
|
|
652
|
+
|
|
313
653
|
return (
|
|
314
|
-
<AuiProvider
|
|
315
|
-
<
|
|
654
|
+
<AuiProvider extends={aui} config={config}>
|
|
655
|
+
<ArtifactSurfaceProvider>
|
|
656
|
+
<ArtifactExperience />
|
|
657
|
+
</ArtifactSurfaceProvider>
|
|
316
658
|
</AuiProvider>
|
|
317
659
|
);
|
|
318
660
|
}
|
|
319
661
|
|
|
320
662
|
export default function Home() {
|
|
321
|
-
const runtime = useChatRuntime(
|
|
322
|
-
|
|
323
|
-
tools: Tools({ toolkit }),
|
|
663
|
+
const runtime = useChatRuntime({
|
|
664
|
+
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
|
|
324
665
|
});
|
|
325
666
|
|
|
326
667
|
return (
|
|
327
|
-
<AssistantRuntimeProvider
|
|
328
|
-
<
|
|
329
|
-
<div className="flex-grow basis-full">
|
|
330
|
-
<ThreadWithSuggestions />
|
|
331
|
-
</div>
|
|
332
|
-
<ArtifactsView />
|
|
333
|
-
</main>
|
|
668
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
669
|
+
<ArtifactScopes />
|
|
334
670
|
</AssistantRuntimeProvider>
|
|
335
671
|
);
|
|
336
672
|
}
|
|
@@ -342,25 +678,16 @@ export default function Home() {
|
|
|
342
678
|
```tsx
|
|
343
679
|
"use generative";
|
|
344
680
|
|
|
345
|
-
import { defineToolkit,
|
|
346
|
-
import {
|
|
347
|
-
import {
|
|
681
|
+
import { defineToolkit, unstable_interactableTool } from "@assistant-ui/react";
|
|
682
|
+
import { ArtifactTrigger } from "./artifact-surface";
|
|
683
|
+
import { artifactDescription, artifactSchema } from "./artifact-state";
|
|
348
684
|
|
|
349
685
|
export default defineToolkit({
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
render: () => {
|
|
356
|
-
return (
|
|
357
|
-
<div className="bg-primary text-primary-foreground my-2 inline-flex items-center gap-2 rounded-full border px-4 py-2">
|
|
358
|
-
<TerminalIcon className="size-4" />
|
|
359
|
-
render_html({ code: "..." })
|
|
360
|
-
</div>
|
|
361
|
-
);
|
|
362
|
-
},
|
|
363
|
-
},
|
|
686
|
+
artifact: unstable_interactableTool({
|
|
687
|
+
description: artifactDescription,
|
|
688
|
+
stateSchema: artifactSchema,
|
|
689
|
+
render: (props) => <ArtifactTrigger {...props} />,
|
|
690
|
+
}),
|
|
364
691
|
});
|
|
365
692
|
|
|
366
693
|
```
|
|
@@ -426,33 +753,32 @@ export default withAui(nextConfig);
|
|
|
426
753
|
"start": "next start"
|
|
427
754
|
},
|
|
428
755
|
"dependencies": {
|
|
429
|
-
"@ai-sdk/openai": "^4.0.
|
|
756
|
+
"@ai-sdk/openai": "^4.0.34",
|
|
430
757
|
"@assistant-ui/react": "workspace:*",
|
|
431
758
|
"@assistant-ui/react-ai-sdk": "workspace:*",
|
|
432
759
|
"@assistant-ui/react-markdown": "workspace:*",
|
|
433
760
|
"@assistant-ui/ui": "workspace:*",
|
|
434
|
-
"ai": "^7.0.
|
|
761
|
+
"ai": "^7.0.56",
|
|
435
762
|
"class-variance-authority": "^0.7.1",
|
|
436
763
|
"clsx": "^2.1.1",
|
|
437
|
-
"lucide-react": "^1.
|
|
438
|
-
"next": "^16.
|
|
439
|
-
"react": "^19.2.
|
|
440
|
-
"react-dom": "^19.2.
|
|
764
|
+
"lucide-react": "^1.29.0",
|
|
765
|
+
"next": "^16.3.0",
|
|
766
|
+
"react": "^19.2.8",
|
|
767
|
+
"react-dom": "^19.2.8",
|
|
441
768
|
"tailwind-merge": "^3.6.0",
|
|
442
769
|
"zod": "^4.4.3"
|
|
443
770
|
},
|
|
444
771
|
"devDependencies": {
|
|
445
772
|
"@assistant-ui/next": "workspace:*",
|
|
446
773
|
"@assistant-ui/x-buildutils": "workspace:*",
|
|
447
|
-
"@tailwindcss/postcss": "^4.3.
|
|
448
|
-
"@types/node": "^26.1.
|
|
449
|
-
"@types/react": "^19.2.
|
|
450
|
-
"@types/react-dom": "^19.2.
|
|
451
|
-
"postcss": "^8.5.
|
|
452
|
-
"tailwindcss": "^4.3.
|
|
774
|
+
"@tailwindcss/postcss": "^4.3.3",
|
|
775
|
+
"@types/node": "^26.1.2",
|
|
776
|
+
"@types/react": "^19.2.18",
|
|
777
|
+
"@types/react-dom": "^19.2.4",
|
|
778
|
+
"postcss": "^8.5.26",
|
|
779
|
+
"tailwindcss": "^4.3.3",
|
|
453
780
|
"tw-animate-css": "^1.4.0",
|
|
454
|
-
"typescript": "
|
|
455
|
-
"typescript-7": "npm:typescript@^7.0.2"
|
|
781
|
+
"typescript": "^7.0.2"
|
|
456
782
|
}
|
|
457
783
|
}
|
|
458
784
|
|