2025-02-13 08:25:57 -08:00
# Artifacts
2025-02-05 14:58:38 +03:00
2025-02-13 08:25:57 -08:00
Artifacts is a special user interface mode that allows you to have a workspace like interface along with the chat interface. This is similar to [ChatGPT's Canvas ](https://openai.com/index/introducing-canvas ) and [Claude's Artifacts ](https://www.anthropic.com/news/artifacts ).
2025-02-05 14:58:38 +03:00
2025-02-13 08:25:57 -08:00
The template already ships with the following artifacts:
2025-02-05 14:58:38 +03:00
2025-02-13 08:25:57 -08:00
- **Text Artifact**: A artifact that allows you to work with text content like drafting essays and emails.
- **Code Artifact**: A artifact that allows you to write and execute code (Python).
- **Image Artifact**: A artifact that allows you to work with images like editing, annotating, and processing images.
- **Sheet Artifact**: A artifact that allows you to work with tabular data like creating, editing, and analyzing data.
2025-02-05 14:58:38 +03:00
2025-02-13 08:25:57 -08:00
## Adding a Custom Artifact
2025-02-05 14:58:38 +03:00
2025-02-13 08:25:57 -08:00
To add a custom artifact, you will need to create a folder in the `artifacts` directory with the artifact name. The folder should contain the following files:
2025-02-05 14:58:38 +03:00
2025-02-13 08:25:57 -08:00
- `client.tsx` : The client-side code for the artifact.
- `server.ts` : The server-side code for the artifact.
2025-02-05 14:58:38 +03:00
2025-02-13 08:25:57 -08:00
Here is an example of a custom artifact called `CustomArtifact` :
2025-02-05 14:58:38 +03:00
```bash
2025-02-13 08:25:57 -08:00
artifacts/
2025-02-05 14:58:38 +03:00
custom/
client.tsx
server.ts
```
### Client-Side Example (client.tsx)
2025-02-13 08:25:57 -08:00
This file is responsible for rendering your custom artifact. You might replace the inner UI with your own components, but the overall pattern (initialization, handling streamed data, and rendering content) remains the same. For instance:
2025-02-05 14:58:38 +03:00
```tsx
2025-02-13 08:25:57 -08:00
import { Artifact } from "@/components/create -artifact";
2025-02-05 14:58:38 +03:00
import { ExampleComponent } from "@/components/example -component";
import { toast } from "sonner";
2025-02-13 08:25:57 -08:00
interface CustomArtifactMetadata {
// Define metadata your custom artifact might need—the example below is minimal.
2025-02-05 14:58:38 +03:00
info: string;
}
2025-02-13 08:25:57 -08:00
export const customArtifact = new Artifact< "custom", CustomArtifactMetadata>({
2025-02-05 14:58:38 +03:00
kind: "custom",
2025-02-13 08:25:57 -08:00
description: "A custom artifact for demonstrating custom functionality.",
2025-02-05 14:58:38 +03:00
// Initialization can fetch any extra data or perform side effects
initialize: async ({ documentId, setMetadata }) => {
2025-02-13 08:25:57 -08:00
// For example, initialize the artifact with default metadata.
2025-02-05 14:58:38 +03:00
setMetadata({
info: `Document ${documentId} initialized.` ,
});
},
2025-02-13 08:25:57 -08:00
// Handle streamed parts from the server (if your artifact supports streaming updates)
onStreamPart: ({ streamPart, setMetadata, setArtifact }) => {
2025-02-05 14:58:38 +03:00
if (streamPart.type === "info-update") {
setMetadata((metadata) => ({
...metadata,
info: streamPart.content as string,
}));
}
if (streamPart.type === "content-update") {
2025-02-13 08:25:57 -08:00
setArtifact((draftArtifact) => ({
...draftArtifact,
content: draftArtifact.content + (streamPart.content as string),
2025-02-05 14:58:38 +03:00
status: "streaming",
}));
}
},
2025-02-13 08:25:57 -08:00
// Defines how the artifact content is rendered
2025-02-05 14:58:38 +03:00
content: ({
mode,
status,
content,
isCurrentVersion,
currentVersionIndex,
onSaveContent,
getDocumentContentById,
isLoading,
metadata,
}) => {
if (isLoading) {
2025-02-13 08:25:57 -08:00
return < div > Loading custom artifact...< / div > ;
2025-02-05 14:58:38 +03:00
}
if (mode === "diff") {
const oldContent = getDocumentContentById(currentVersionIndex - 1);
const newContent = getDocumentContentById(currentVersionIndex);
return (
< div >
< h3 > Diff View< / h3 >
< pre > {oldContent}< / pre >
< pre > {newContent}< / pre >
< / div >
);
}
return (
2025-02-13 08:25:57 -08:00
< div className = "custom-artifact" >
2025-02-05 14:58:38 +03:00
< ExampleComponent
content={content}
metadata={metadata}
onSaveContent={onSaveContent}
isCurrentVersion={isCurrentVersion}
/>
< button
onClick={() => {
navigator.clipboard.writeText(content);
toast.success("Content copied to clipboard!");
}}
>
Copy
< / button >
< / div >
);
},
2025-02-13 08:25:57 -08:00
// An optional set of actions exposed in the artifact toolbar.
2025-02-05 14:58:38 +03:00
actions: [
{
icon: < span > ⟳< / span > ,
2025-02-13 08:25:57 -08:00
description: "Refresh artifact info",
2025-02-05 14:58:38 +03:00
onClick: ({ appendMessage }) => {
appendMessage({
role: "user",
2025-02-13 08:25:57 -08:00
content: "Please refresh the info for my custom artifact.",
2025-02-05 14:58:38 +03:00
});
},
},
],
// Additional toolbar actions for more control
toolbar: [
{
icon: < span > ✎< / span > ,
2025-02-13 08:25:57 -08:00
description: "Edit custom artifact",
2025-02-05 14:58:38 +03:00
onClick: ({ appendMessage }) => {
appendMessage({
role: "user",
2025-02-13 08:25:57 -08:00
content: "Edit the custom artifact content.",
2025-02-05 14:58:38 +03:00
});
},
},
],
});
```
Server-Side Example (server.ts)
2025-02-13 08:25:57 -08:00
The server file processes the document for the artifact. It streams updates (if applicable) and returns the final content. For example:
2025-02-05 14:58:38 +03:00
```ts
import { smoothStream, streamText } from "ai";
2025-03-04 17:25:46 -08:00
import { myProvider } from "@/lib/ai/providers ";
2025-02-13 08:25:57 -08:00
import { createDocumentHandler } from "@/lib/artifacts/server ";
2025-02-05 14:58:38 +03:00
import { updateDocumentPrompt } from "@/lib/ai/prompts ";
export const customDocumentHandler = createDocumentHandler< "custom">({
kind: "custom",
// Called when the document is first created.
onCreateDocument: async ({ title, dataStream }) => {
let draftContent = "";
// For demonstration, use streamText to generate content.
const { fullStream } = streamText({
2025-02-13 08:25:57 -08:00
model: myProvider.languageModel("artifact-model"),
2025-02-05 14:58:38 +03:00
system:
"Generate a creative piece based on the title. Markdown is supported.",
experimental_transform: smoothStream({ chunking: "word" }),
prompt: title,
});
// Stream the content back to the client.
for await (const delta of fullStream) {
if (delta.type === "text-delta") {
draftContent += delta.textDelta;
dataStream.writeData({
type: "content-update",
content: delta.textDelta,
});
}
}
return draftContent;
},
// Called when updating the document based on user modifications.
onUpdateDocument: async ({ document, description, dataStream }) => {
let draftContent = "";
const { fullStream } = streamText({
2025-02-13 08:25:57 -08:00
model: myProvider.languageModel("artifact-model"),
2025-02-05 14:58:38 +03:00
system: updateDocumentPrompt(document.content, "custom"),
experimental_transform: smoothStream({ chunking: "word" }),
prompt: description,
experimental_providerMetadata: {
openai: {
prediction: {
type: "content",
content: document.content,
},
},
},
});
for await (const delta of fullStream) {
if (delta.type === "text-delta") {
draftContent += delta.textDelta;
dataStream.writeData({
type: "content-update",
content: delta.textDelta,
});
}
}
return draftContent;
},
});
```
2025-02-13 08:25:57 -08:00
Once you have created the client and server files, you can import the artifact in the `lib/artifacts/server.ts` file and add it to the `documentHandlersByArtifactKind` array.
2025-02-05 14:58:38 +03:00
```ts
2025-02-13 08:25:57 -08:00
export const documentHandlersByArtifactKind: Array< DocumentHandler > = [
2025-02-05 14:58:38 +03:00
...,
customDocumentHandler,
];
2025-02-13 08:25:57 -08:00
export const artifactKinds = [..., "custom"] as const;
2025-02-05 14:58:38 +03:00
```
2025-02-06 17:18:26 +03:00
Specify it in document schema at `lib/db/schema.ts` .
```ts
export const document = pgTable(
"Document",
{
id: uuid("id").notNull().defaultRandom(),
createdAt: timestamp("createdAt").notNull(),
title: text("title").notNull(),
content: text("content"),
2025-02-13 08:25:57 -08:00
kind: varchar("text", { enum: [..., "custom"] }) // Add the custom artifact kind here
2025-02-06 17:18:26 +03:00
.notNull()
.default("text"),
userId: uuid("userId")
.notNull()
.references(() => user.id),
},
(table) => {
return {
pk: primaryKey({ columns: [table.id, table.createdAt] }),
};
},
);
```
2025-02-13 08:25:57 -08:00
And also add the client-side artifact to the `artifactDefinitions` array in the `components/artifact.tsx` file.
2025-02-05 14:58:38 +03:00
```ts
2025-02-13 08:25:57 -08:00
import { customArtifact } from "@/artifacts/custom/client ";
2025-02-05 14:58:38 +03:00
2025-02-13 08:25:57 -08:00
export const artifactDefinitions = [..., customArtifact];
2025-02-05 14:58:38 +03:00
```
2025-02-13 08:25:57 -08:00
You should now be able to see the custom artifact in the workspace!