Artifact System
This document describes the artifact system that allows users to extract, view, edit, and persist structured content from AI responses.
Overview
The artifact system extracts special <artifact> tags from AI model responses and presents them in an interactive side panel. Users can view, edit, copy, and download these artifacts. All edits are automatically persisted in localStorage and included in subsequent API calls.
Architecture
Components
ArtifactPanel- Main UI component with preview/edit tabsArtifactToggleButton- Header button to show/hide panelArtifactContext- State management and persistenceartifactExtractor.ts- Utility for parsing artifact tagsArtifact.tsx- Markdown component for rendering artifact buttons
Data Flow
- AI model includes
<artifact>tags in responses artifactExtractorparses tags during streamingArtifactContextstores artifacts in memory + localStorageArtifactPaneldisplays artifacts with edit capabilities- Edited artifacts are included in subsequent API calls
Artifact Tag Format
AI models should output artifacts using this format:
<artifact id="unique-id" title="Artifact Title" type="artifact-type">
Content goes here
</artifact>
Supported Parameters
id(required) - Unique identifier for the artifacttitle(required) - Human-readable title displayed in UItype(required) - Content type (markdown, html, javascript, python, react, jsx, tsx, etc.)
Example
<artifact id="shopping-list" title="Weekly Shopping List" type="markdown">
# Weekly Shopping List
## Groceries
- Milk
- Bread
- Eggs
## Household
- Laundry detergent
- Paper towels
</artifact>
API Integration
Including Artifacts in API Calls
When artifacts exist, they are automatically appended to user messages sent to the API:
const userMessage = originalMessage + artifactContext.getArtifactsForApi();
API Format
Original user message here
Current artifacts:
<artifact id="shopping-list" title="Weekly Shopping List" type="markdown">
# Weekly Shopping List (EDITED)
## Groceries
- Milk (2% organic)
- Bread (whole grain)
- Eggs (dozen)
## Household
- Laundry detergent
- Paper towels
</artifact>
<artifact id="recipe" title="Pasta Recipe" type="markdown">
# Simple Pasta Recipe
## Ingredients
- 1 lb pasta
- 2 cups marinara sauce
- 1/2 cup parmesan cheese
</artifact>
Multiple Artifacts Handling
Storage and Management
- Artifacts stored in
Map<string, ArtifactData>for O(1) lookup - Each artifact has unique ID to prevent conflicts
- Active artifact tracked separately for UI focus
UI Features
- Artifact selector dropdown when multiple artifacts exist
- Navigation between artifacts while maintaining edits
- Bulk operations like copy/download for active artifact
- Auto-selection of new artifacts when added
API Inclusion
All artifacts (original + edited) are included in API calls:
const getArtifactsForApi = (): string => {
if (artifacts.size === 0) return '';
const artifactStrings = Array.from(artifacts.values()).map(artifact =>
`<artifact id="${artifact.id}" title="${artifact.title}" type="${artifact.type}">${artifact.content}</artifact>`
);
return '\n\nCurrent artifacts:\n' + artifactStrings.join('\n');
};
Persistence
localStorage Implementation
- Storage key:
'aitana-artifacts' - Format: JSON object with artifact IDs as keys
- Automatic saving: Triggered on any artifact change
- Automatic loading: Restored on page/app restart
Data Structure
interface ArtifactData {
id: string; // Unique identifier
title: string; // Display title
type: string; // Content type
content: string; // Editable content (may be modified from original)
}
Storage Example
{
"shopping-list": {
"id": "shopping-list",
"title": "Weekly Shopping List",
"type": "markdown",
"content": "# Weekly Shopping List (EDITED)\n\n## Groceries\n- Milk (2% organic)"
},
"recipe": {
"id": "recipe",
"title": "Pasta Recipe",
"type": "markdown",
"content": "# Simple Pasta Recipe\n\n## Ingredients\n- 1 lb pasta"
}
}
User Interface
Panel Layout
┌─────────────────────────────────┐
│ Artifacts (2) [×] │ ← Header with count
├─────────────────────────────────┤
│ [Artifact Selector Dropdown] │ ← Multi-artifact navigation
├─────────────────────────────────┤
│ Title: Weekly Shopping List │ ← Current artifact info
│ Type: markdown │
├─────────────────────────────────┤
│ [Preview] [Edit] │ ← Tab switcher
├─────────────────────────────────┤
│ │
│ Scrollable content area │ ← Preview/edit content
│ │
└─────────────────────────────────┘
Features
- Preview tab: Rendered markdown with syntax highlighting
- Edit tab: Large textarea with monospace font
- Copy functionality: Preserves formatting using hybrid copy
- Download: Saves as appropriate file extension (.md, .py, etc.)
- Auto-scroll: Smooth scrolling within panel boundaries
- Responsive: Adapts to different screen sizes
Integration Points
Streaming Context
// In StreamingContext, artifacts are extracted during streaming
const onArtifactContent = useCallback((artifacts: ArtifactData[]) => {
artifacts.forEach(artifact => {
artifactContext.addArtifact(artifact);
});
}, []);
Chat Interface
// ArtifactPanel is included in ChatInterface
<ChatInterface {...props} />
<ArtifactPanel
onArtifactUpdate={updateArtifact}
hideToggleButton={true}
/>
Header Integration
// ArtifactToggleButton in page headers
<div className="flex items-center gap-2">
<ArtifactToggleButton />
{/* other header buttons */}
</div>
File Extensions Mapping
The system automatically determines file extensions for downloads:
const getFileExtension = (type: string): string => {
switch (type.toLowerCase()) {
case 'markdown': case 'md': return 'md';
case 'html': return 'html';
case 'javascript': case 'js': return 'js';
case 'typescript': case 'ts': return 'ts';
case 'python': case 'py': return 'py';
case 'json': return 'json';
case 'yaml': case 'yml': return 'yml';
case 'css': return 'css';
case 'svg': return 'svg';
default: return 'txt';
}
};
Error Handling
Graceful Degradation
- localStorage failures: Continue in-memory operation
- Malformed artifacts: Skip parsing errors, continue with valid ones
- Missing artifact properties: Use sensible defaults
Console Warnings
console.warn('Failed to load artifacts from localStorage:', error);
console.warn('Failed to save artifacts to localStorage:', error);
Best Practices
For AI Models
- Use descriptive IDs:
user-profile-formnotartifact1 - Meaningful titles: Help users understand content at a glance
- Appropriate types: Use standard file extensions/types
- Self-contained content: Artifacts should be complete and useful standalone
For Developers
- Test persistence: Verify edits survive page refreshes
- Check API inclusion: Ensure edited artifacts reach the model
- Handle edge cases: Empty content, duplicate IDs, etc.
- Monitor localStorage: Large artifacts may hit storage limits
Interactive Preview Support
React and HTML Artifacts
The artifact system includes an interactive preview feature for web-based artifacts:
Supported Preview Types
- HTML (
type="html") - Renders HTML content directly - Browser JavaScript (
type="javascript") - Executes client-side JavaScript code only - React (
type="react"ortype="jsx"ortype="tsx") - Transpiles and renders React components
Not Supported for Preview
- Server-side code (Node.js, Express, APIs) - Requires server environment
- Code with dependencies - npm packages not available in preview
- Database connections - No backend services available
Preview Features
- Sandboxed iframe: Code runs in isolation for security
- Live preview: See your changes immediately
- Refresh button: Reload the preview
- Open in new tab: Test in a full browser window
- Error handling: Clear error messages for debugging
Example React Artifact
<artifact id="counter-app" title="React Counter App" type="react">
function App() {
const [count, setCount] = React.useState(0);
return (
<div style={{padding: '20px', fontFamily: 'Arial' }}>
<h1>Counter: {count}</h1>
<button
onClick={() => setCount(count + 1)}
style={{padding: '10px 20px',
fontSize: '16px',
marginRight: '10px'
}}
>
Increment
</button>
<button
onClick={() => setCount(0)}
style={{padding: '10px 20px',
fontSize: '16px'
}}
>
Reset
</button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
</artifact>
Examples: What Works vs What Doesn’t
✅ Works with Preview (Browser JavaScript):
<artifact id="browser-demo" title="Browser Animation" type="javascript">
// This runs in the browser
let count = 0;
setInterval(() => {
document.getElementById('root').innerHTML =
`<h1>Count: ${count++}</h1>`;
}, 1000);
</artifact>
❌ Won’t Work with Preview (Server Code):
<artifact id="express-server" title="Express.js Server" type="javascript">
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
</artifact>
For server code, the artifact panel will show a helpful message explaining that it needs to be downloaded and run locally.
Security Considerations
- Previews run in sandboxed iframes with limited permissions
- No access to parent window or external resources
- Some browser APIs may be restricted
- For full functionality, download and run locally
Custom Markdown Components
The artifact panel fully supports all custom markdown components:
<preview>- File previews (images, PDFs, etc.)<plot>- Interactive data visualizations<networkgraph>- Network/graph visualizations<alert>- Styled alert messages<tooltip>- Hover tooltips<googlesearch>- Search result displays<toolconfirmation>- Tool execution confirmations<dynamic-ui>- Dynamic UI components
These components work identically in artifacts as they do in the main chat.
Future Enhancements
Potential Features
- Artifact versioning: Track edit history
- Collaborative editing: Multi-user artifact sharing
- Export formats: PDF, Word document generation
- Enhanced preview: Support for Vue, Angular, and other frameworks
- Artifact templates: Pre-defined structures for common types
- Search/filter: Find artifacts across conversations
- Firestore sync: Cloud persistence option
- Live collaboration: Real-time multi-user editing
Performance Considerations
- Lazy loading: Load artifacts on-demand for large collections
- Content limits: Prevent localStorage overflow
- Debounced saving: Reduce save frequency during active editing