Artifacts
Interactive code and document editing with version management
Artifacts are interactive, editable content that appears in the chat canvas. They allow users to view, edit, and interact with generated code and documents directly within the chat interface, similar to OpenAI's ChatGPT canvas feature.
Artifact System Overview
The artifact system consists of:
- ChatCanvas - Side panel for displaying artifacts
- Artifact Viewers - Components for rendering different artifact types
- Version Management - Track changes and revisions
- Edit Capabilities - In-place editing with syntax highlighting
Artifact Types
The system supports two main artifact types:
- Code Artifacts - Interactive code editing with syntax highlighting
- Document Artifacts - Rich text document editing with markdown support
Code Artifacts
Code artifacts provide interactive code editing with full syntax highlighting and language support.
Creating Code Artifacts
// Server-side: Create code artifact part
const codeArtifact = {
type: 'data-artifact',
data: {
type: 'code',
data: {
title: 'Data Visualization Script',
file_name: 'visualize_data.py',
language: 'python',
code: `
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
def create_sales_chart(data_file):
"""Create a sales visualization chart."""
# Load the data
df = pd.read_csv(data_file)
# Prepare the data
monthly_sales = df.groupby('month')['revenue'].sum()
# Create the visualization
plt.figure(figsize=(12, 6))
bars = plt.bar(monthly_sales.index, monthly_sales.values,
color='steelblue', alpha=0.8)
# Customize the chart
plt.title('Monthly Sales Revenue', fontsize=16, fontweight='bold')
plt.xlabel('Month', fontsize=12)
plt.ylabel('Revenue ($)', fontsize=12)
plt.xticks(rotation=45)
# Add value labels on bars
for bar in bars:
height = bar.get_height()
plt.text(bar.get_x() + bar.get_width()/2., height,
f'${height:,.0f}', ha='center', va='bottom')
plt.tight_layout()
plt.grid(True, alpha=0.3)
plt.show()
return monthly_sales
# Example usage
if __name__ == "__main__":
sales_data = create_sales_chart('sales_data.csv')
print(f"Total annual revenue: ${sales_data.sum():,.2f}")
`
}
}
}
Streaming Code Artifacts
// In your API route
export async function POST(request: Request) {
const { messages } = await request.json()
const stream = new ReadableStream({
async start(controller) {
const encoder = new TextEncoder()
// Send initial response
controller.enqueue(
encoder.encode(
'0:"I\'ll create a Python script for data visualization:\\n"\\n'
)
)
// Generate code (could be from LLM)
const generatedCode = await generateCodeWithLLM(messages)
// Send code artifact
const artifact = {
type: 'data-artifact',
data: {
type: 'code',
data: {
title: 'Data Visualization Script',
file_name: 'chart_generator.py',
language: 'python',
code: generatedCode,
},
},
}
// send the artifact with the data: prefix for SSE format
controller.enqueue(encoder.encode(`data: ${JSON.stringify(artifact)}\\n`))
// Send follow-up text
controller.enqueue(
encoder.encode(
'0:"You can edit this code directly in the canvas. Would you like me to explain any part of it?"\\n'
)
)
controller.close()
},
})
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Connection': 'keep-alive',
},
})
}
Supported Languages
The code artifact system supports syntax highlighting for:
- JavaScript/TypeScript -
.js
,.ts
,.jsx
,.tsx
- Python -
.py
,.pyx
- CSS/SCSS -
.css
,.scss
,.sass
- HTML -
.html
,.htm
- JSON -
.json
- Markdown -
.md
,.mdx
- SQL -
.sql
- Shell/Bash -
.sh
,.bash
Code Artifact Features
// The canvas automatically provides these features:
// - Syntax highlighting
// - Line numbers
// - Code folding
// - Search and replace
// - Auto-indentation
// - Bracket matching
// - Error highlighting (for supported languages)
Document Artifacts
Document artifacts provide rich text editing with markdown support and real-time preview.
Creating Document Artifacts
const documentArtifact = {
type: 'data-artifact',
data: {
type: 'document',
data: {
title: 'API Integration Guide',
content: `
# API Integration Guide
## Overview
This guide walks you through integrating our REST API into your application.
## Authentication
All API requests require authentication using API keys:
\`\`\`bash
curl -H "Authorization: Bearer YOUR_API_KEY" \\
https://api.example.com/v1/users
\`\`\`
## Endpoints
### User Management
#### Get User Profile
\`\`\`
GET /v1/users/{userId}
\`\`\`
**Response:**
\`\`\`json
{
"id": "user_123",
"name": "John Doe",
"email": "john@example.com",
"created_at": "2024-01-15T10:30:00Z"
}
\`\`\`
#### Update User Profile
\`\`\`
PUT /v1/users/{userId}
Content-Type: application/json
{
"name": "John Smith",
"email": "john.smith@example.com"
}
\`\`\`
### Data Operations
#### List Records
\`\`\`
GET /v1/records?limit=20&offset=0
\`\`\`
**Query Parameters:**
- \`limit\`: Number of records to return (default: 20, max: 100)
- \`offset\`: Number of records to skip (default: 0)
- \`sort\`: Sort field (default: created_at)
- \`order\`: Sort order (asc/desc, default: desc)
## Error Handling
The API uses standard HTTP status codes:
| Code | Description |
|------|-------------|
| 200 | Success |
| 400 | Bad Request |
| 401 | Unauthorized |
| 404 | Not Found |
| 500 | Internal Server Error |
**Error Response Format:**
\`\`\`json
{
"error": {
"code": "INVALID_REQUEST",
"message": "The request is missing required parameters",
"details": {
"missing_fields": ["name", "email"]
}
}
}
\`\`\`
## Rate Limiting
- **Free Tier**: 100 requests per hour
- **Pro Tier**: 1,000 requests per hour
- **Enterprise**: Custom limits
Rate limit headers are included in all responses:
\`\`\`
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1640995200
\`\`\`
## SDKs and Libraries
We provide official SDKs for popular languages:
### JavaScript/Node.js
\`\`\`bash
npm install @example/api-client
\`\`\`
\`\`\`javascript
import { ApiClient } from '@example/api-client'
const client = new ApiClient({
apiKey: 'your-api-key'
})
const user = await client.users.get('user_123')
console.log(user)
\`\`\`
### Python
\`\`\`bash
pip install example-api-client
\`\`\`
\`\`\`python
from example_api import ApiClient
client = ApiClient(api_key='your-api-key')
user = client.users.get('user_123')
print(user)
\`\`\`
## Best Practices
1. **Always use HTTPS** for API requests
2. **Store API keys securely** - never commit them to version control
3. **Implement retry logic** with exponential backoff
4. **Cache responses** when appropriate to reduce API calls
5. **Monitor rate limits** and implement proper handling
## Webhooks
Configure webhooks to receive real-time notifications:
\`\`\`json
{
"url": "https://yourapp.com/webhooks/api",
"events": ["user.created", "user.updated", "user.deleted"],
"secret": "webhook-secret-key"
}
\`\`\`
## Support
- **Documentation**: https://docs.example.com
- **Support Email**: support@example.com
- **Status Page**: https://status.example.com
Happy coding! 🚀
`,
},
},
}
Document Features
Document artifacts provide:
- Rich Text Editing - WYSIWYG editor with markdown output
- Live Preview - Real-time rendering of markdown content
- Formatting Tools - Bold, italic, headers, lists, links
- Code Blocks - Syntax-highlighted code snippets
- Tables - Table creation and editing
- Math Support - LaTeX math rendering
- Image Embedding - Image insertion and management
Canvas Integration
Basic Canvas Setup
import { ChatSection, ChatCanvas } from '@llamaindex/chat-ui'
function ChatWithCanvas() {
const handler = useChat({
transport: new DefaultChatTransport({
api: '/api/chat',
}),
})
return (
<ChatSection handler={handler} className="flex h-full">
<div className="flex-1">
<ChatMessages />
<ChatInput />
</div>
<ChatCanvas className="w-1/2 border-l" />
</ChatSection>
)
}
Adding Custom Artifact Viewers
You can extend the canvas with custom artifact viewers to handle different types of content. Here's an example of creating a custom image artifact viewer:
import {
Artifact,
ChatCanvas,
useChatCanvas
} from '@llamaindex/chat-ui'
import { Image } from 'lucide-react'
// Define your custom artifact type
type ImageArtifact = Artifact<
{
imageUrl: string
caption: string
},
'image'
>
function ImageArtifactViewer() {
const { displayedArtifact } = useChatCanvas()
// Only render for image artifacts
if (displayedArtifact?.type !== 'image') return null
const {
data: { imageUrl, caption },
} = displayedArtifact as ImageArtifact
return (
<div className="flex min-h-0 flex-1 flex-col">
{/* Custom header with icon and title */}
<div className="flex items-center justify-between border-b p-4">
<h3 className="flex items-center gap-3 text-gray-600">
<Image className="size-8 text-blue-500" />
<div className="font-semibold">{caption}</div>
</h3>
{/* Use built-in canvas actions */}
<ChatCanvas.Actions>
<ChatCanvas.Actions.History />
<ChatCanvas.Actions.Close />
</ChatCanvas.Actions>
</div>
{/* Custom content area */}
<div className="flex flex-1 items-center justify-center p-4">
<img
src={imageUrl}
alt={caption}
className="h-[70%] rounded-2xl shadow-2xl"
/>
</div>
</div>
)
}
// Use the custom viewer in your chat
function CustomChat() {
const handler = useChat({ initialMessages: [] })
return (
<ChatSection handler={handler}>
<div className="flex h-full flex-col">
<ChatMessages />
<ChatInput />
</div>
<ChatCanvas>
{/* Built-in artifact viewers */}
<ChatCanvas.CodeArtifact />
<ChatCanvas.DocumentArtifact />
{/* Your custom artifact viewer */}
<ImageArtifactViewer />
</ChatCanvas>
</ChatSection>
)
}
You can also customize ArtifactCard for your artifact type:
import { Image } from 'lucide-react'
// custom artifact card for image artifacts with title is the caption and icon is the image icon
function CustomArtifactCard({ data }: { data: Artifact }) {
return (
<ChatCanvas.Artifact
data={data}
getTitle={artifact => (artifact as ImageArtifact).data.caption}
iconMap={{ image: Image }}
/>
)
}
To trigger your custom artifact viewer, the AI response should include a part with the matching artifact type:
message = {
parts: [
{
type: 'data-artifact',
data: {
type: 'code',
data: {
title: 'Data Visualization Script',
file_name: 'visualize_data.py',
language: 'python',
code: 'import matplotlib.pyplot as plt\n# Code...',
},
}
}
]
}
You can create multiple custom artifact viewers for different content types:
<ChatCanvas>
{/* Built-in viewers */}
<ChatCanvas.CodeArtifact />
<ChatCanvas.DocumentArtifact />
{/* Custom viewers */}
<ImageArtifactViewer /> {/* for image artifacts */}
<PDFArtifactViewer /> {/* for pdf artifacts */}
<ChartArtifactViewer /> {/* for chart artifacts */}
</ChatCanvas>
For a complete working example of custom artifact viewers, check out the demo implementation in apps/web/app/demo/canvas/custom/page.tsx
. This demo shows:
- Custom
ImageArtifactViewer
implementation - Integration with existing chat components
- Sample messages with artifact parts
- Copy-to-clipboard functionality for the code
Canvas Auto-Show
The canvas automatically appears when artifacts are present:
// Canvas appears automatically when message contains artifacts
<ChatMessage message={messageWithArtifact}>
<ChatMessage.Content>
<ChatMessage.Part.Artifact />
</ChatMessage.Content>
</ChatMessage>
Version Management
Artifacts support version tracking and restoration:
Version History
import { useChatCanvas } from '@llamaindex/chat-ui'
function ArtifactVersions() {
const { currentArtifact, updateArtifact } = useChatCanvas()
if (!currentArtifact) return null
const versions = currentArtifact.versions || []
const restoreVersion = (version: any) => {
updateArtifact({
...currentArtifact,
content: version.content,
versions: [
...versions,
{
id: generateId(),
content: currentArtifact.content,
timestamp: new Date().toISOString(),
description: 'Current version backup',
},
],
})
}
return (
<div className="space-y-2">
<h3 className="font-semibold">Version History</h3>
{versions.map((version, index) => (
<div key={version.id} className="flex items-center justify-between">
<div>
<p className="text-sm font-medium">
Version {versions.length - index}
</p>
<p className="text-xs text-gray-500">
{new Date(version.timestamp).toLocaleString()}
</p>
</div>
<button
onClick={() => restoreVersion(version)}
className="text-sm text-blue-600 hover:underline"
>
Restore
</button>
</div>
))}
</div>
)
}
Save Changes
function SaveArtifactChanges() {
const { currentArtifact, updateArtifact } = useChatCanvas()
const [hasChanges, setHasChanges] = useState(false)
const saveChanges = () => {
if (!currentArtifact || !hasChanges) return
// Create version backup
const newVersion = {
id: generateId(),
content: currentArtifact.originalContent,
timestamp: new Date().toISOString(),
description: 'Auto-saved version',
}
updateArtifact({
...currentArtifact,
versions: [...(currentArtifact.versions || []), newVersion],
originalContent: currentArtifact.content,
})
setHasChanges(false)
}
return (
<button
onClick={saveChanges}
disabled={!hasChanges}
className="rounded bg-blue-600 px-3 py-1 text-white disabled:opacity-50"
>
{hasChanges ? 'Save Changes' : 'Saved'}
</button>
)
}
Artifact Actions
Export Functionality
function ArtifactExport() {
const { currentArtifact } = useChatCanvas()
const exportAsFile = () => {
if (!currentArtifact) return
const content =
currentArtifact.type === 'code'
? currentArtifact.code
: currentArtifact.content
const filename =
currentArtifact.type === 'code'
? currentArtifact.file_name
: `${currentArtifact.title}.md`
const blob = new Blob([content], {
type: 'text/plain;charset=utf-8',
})
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = filename
link.click()
URL.revokeObjectURL(url)
}
return (
<button
onClick={exportAsFile}
className="flex items-center gap-2 rounded border px-3 py-1 text-sm"
>
<DownloadIcon className="h-4 w-4" />
Export
</button>
)
}
Copy to Clipboard
import { useCopyToClipboard } from '@llamaindex/chat-ui'
function CopyArtifact() {
const { currentArtifact } = useChatCanvas()
const { copyToClipboard, isCopied } = useCopyToClipboard()
const handleCopy = () => {
if (!currentArtifact) return
const content =
currentArtifact.type === 'code'
? currentArtifact.code
: currentArtifact.content
copyToClipboard(content)
}
return (
<button
onClick={handleCopy}
className="flex items-center gap-2 rounded border px-3 py-1 text-sm"
>
<CopyIcon className="h-4 w-4" />
{isCopied ? 'Copied!' : 'Copy'}
</button>
)
}
Next Steps
- Examples - See complete artifact implementations
- Customization - Style and customize artifact appearance
- Widgets - Explore related widget functionality
- Parts - Understand the message parts system