UNPKG

@ai-sdk-tools/artifacts

Version:

Advanced streaming interfaces for AI applications

346 lines (273 loc) 9.76 kB
# @ai-sdk-tools/artifacts Advanced streaming interfaces for AI applications. Create structured, type-safe artifacts that stream real-time updates from AI tools to React components. ## ✨ Features - 🎯 **Type-Safe Streaming** - Full TypeScript support with Zod schema validation - 🔄 **Real-time Updates** - Stream partial updates with progress tracking - 🎨 **Clean API** - Minimal boilerplate, maximum flexibility - 🏪 **State Management** - Built on @ai-sdk-tools/store for efficient message handling -**Performance Optimized** - Efficient state management and updates ## 📦 Installation ```bash npm install @ai-sdk-tools/artifacts @ai-sdk-tools/store ``` **Why do you need both packages?** - `@ai-sdk-tools/artifacts` - Provides the artifact streaming and management APIs - `@ai-sdk-tools/store` - Required for message state management and React hooks The artifacts package uses the store package's `useChatMessages` hook to efficiently extract and track artifact data from AI SDK message streams, ensuring optimal performance and avoiding unnecessary re-renders. ## 🔧 Setup ### 1. Initialize Chat with Store ```tsx import { useChat } from '@ai-sdk-tools/store'; // Drop-in replacement for @ai-sdk/react import { DefaultChatTransport } from 'ai'; function ChatComponent() { // Initialize chat (same API as @ai-sdk/react) const { messages, input, handleInputChange, handleSubmit } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat' }) }); return ( <div> {/* Your chat UI */} <ArtifactDisplay /> {/* Artifacts work from any component */} </div> ); } ``` ### 2. Use Artifacts from Any Component The `useArtifact` hook automatically connects to the global chat store to extract artifact data from message streams - no prop drilling needed! ## 🚀 Quick Start ### 1. Define an Artifact ```typescript import { artifact } from '@ai-sdk-tools/artifacts'; import { z } from 'zod'; const burnRateArtifact = artifact('burn-rate', z.object({ title: z.string(), stage: z.enum(['loading', 'processing', 'complete']).default('loading'), monthlyBurn: z.number(), runway: z.number(), data: z.array(z.object({ month: z.string(), burnRate: z.number() })).default([]) })); ``` ### 2. Create a Tool with Context ```typescript // Use direct AI SDK tool format const analyzeBurnRate = { description: 'Analyze company burn rate', inputSchema: z.object({ company: z.string() }), execute: async ({ company }: { company: string }) => { // Access typed context in tools const context = getContext(); // Fully typed as MyContext console.log('Processing for user:', context.userId); console.log('Theme:', context.config.theme); const analysis = burnRateArtifact.stream({ title: `${company} Analysis for ${context.userId}`, stage: 'loading', monthlyBurn: 50000, runway: 12 }); // Stream updates analysis.progress = 0.5; await analysis.update({ stage: 'processing' }); // Complete await analysis.complete({ title: `${company} Analysis`, stage: 'complete', monthlyBurn: 45000, runway: 14, data: [{ month: '2024-01', burnRate: 50000 }] }); return 'Analysis complete'; } }; ``` ### 3. Set Up Route with Context ```typescript import { createTypedContext, BaseContext } from '@ai-sdk-tools/artifacts'; import { createUIMessageStream, createUIMessageStreamResponse, streamText } from 'ai'; import { openai } from '@ai-sdk/openai'; // Define your context type interface MyContext extends BaseContext { userId: string; permissions: string[]; config: { theme: 'light' | 'dark' }; } // Create typed context helpers const { setContext, getContext } = createTypedContext<MyContext>(); export const POST = async (req: Request) => { const { messages } = await req.json(); const stream = createUIMessageStream({ execute: ({ writer }) => { // Set typed context setContext({ writer, userId: req.headers.get('user-id') || 'anonymous', permissions: ['read', 'write'], config: { theme: 'dark' } }); const result = streamText({ model: openai('gpt-4'), messages, tools: { analyzeBurnRate } }); writer.merge(result.toUIMessageStream()); } }); return createUIMessageStreamResponse({ stream }); }; ``` ### 4. Consume in React ```tsx import { useArtifact } from '@ai-sdk-tools/artifacts/client'; function Analysis() { const { data, status, progress, error } = useArtifact(burnRateArtifact, { onComplete: (data) => console.log('Done!', data), onError: (error) => console.error('Failed:', error) }); if (!data) return null; return ( <div> <h2>{data.title}</h2> <p>Stage: {data.stage}</p> <p>Monthly Burn: ${data.monthlyBurn.toLocaleString()}</p> <p>Runway: {data.runway} months</p> {progress && <div>Progress: {progress * 100}%</div>} {data.data.map(item => ( <div key={item.month}> {item.month}: ${item.burnRate.toLocaleString()} </div> ))} </div> ); } ``` ## 📚 API Reference ### `artifact(id, schema)` Creates an artifact definition with Zod schema validation. ### `useArtifact(artifact, callbacks?)` React hook for consuming a specific streaming artifact. **Returns:** - `data` - Current artifact payload - `status` - Current status ('idle' | 'loading' | 'streaming' | 'complete' | 'error') - `progress` - Progress value (0-1) - `error` - Error message if failed - `isActive` - Whether artifact is currently processing - `hasData` - Whether artifact has any data **Callbacks:** - `onUpdate(data, prevData)` - Called when data updates - `onComplete(data)` - Called when artifact completes - `onError(error, data)` - Called on error - `onProgress(progress, data)` - Called on progress updates - `onStatusChange(status, prevStatus)` - Called when status changes ### `useArtifacts(options?)` React hook for listening to all artifacts across all types. Perfect for implementing switch cases to render different artifact types. **Options:** - `onData(artifactType, data)` - Callback fired when any artifact updates - `storeId` - Optional store ID (defaults to global store) **Returns:** - `byType` - All artifacts grouped by type: `Record<string, ArtifactData[]>` - `latest` - Latest version of each artifact type: `Record<string, ArtifactData>` - `artifacts` - All artifacts in chronological order: `ArtifactData[]` - `current` - Most recent artifact across all types: `ArtifactData | null` **Example:** ```tsx import { useArtifacts } from '@ai-sdk-tools/artifacts/client'; function ArtifactRenderer() { const { latest } = useArtifacts({ onData: (artifactType, data) => { console.log(`New ${artifactType} artifact:`, data); } }); return ( <div> {Object.entries(latest).map(([type, artifact]) => { switch (type) { case 'burn-rate': return <BurnRateComponent key={type} data={artifact} />; case 'financial-report': return <ReportComponent key={type} data={artifact} />; default: return <GenericComponent key={type} type={type} data={artifact} />; } })} </div> ); } // Perfect for Canvas-style switching on current artifact function Canvas() { const { current } = useArtifacts(); switch (current?.type) { case "burn-rate-canvas": return <BurnRateCanvas />; case "revenue-canvas": return <RevenueCanvas />; default: return <DefaultCanvas />; } } ``` ## 🔧 Advanced Usage ### Combining Both Hooks You can use both hooks together for different purposes: ```tsx import { useArtifact, useArtifacts } from '@ai-sdk-tools/artifacts/client'; function DashboardWithAnalysis() { // Listen to all artifacts for notifications/logging useArtifacts({ onData: (artifactType, data) => { // Send to analytics analytics.track('artifact_updated', { type: artifactType, status: data.status }); // Show notifications if (data.status === 'complete') { toast.success(`${artifactType} analysis complete!`); } } }); // Use specific artifact for detailed display const { data: burnRateData, status } = useArtifact(burnRateArtifact); // Get latest of all types for overview const { latest } = useArtifacts(); return ( <div> {/* Overview of all artifacts */} <div className="overview"> {Object.entries(latest).map(([type, artifact]) => ( <div key={type} className="artifact-card"> <h3>{type}</h3> <span className={`status ${artifact.status}`}> {artifact.status} </span> </div> ))} </div> {/* Detailed burn rate display */} {burnRateData && ( <BurnRateChart data={burnRateData} status={status} /> )} </div> ); } ``` ### Hook Selection Guide **Use `useArtifact`** when: - You need to display/work with a specific artifact type - You want detailed status, progress, and error handling - You need type-safe access to the artifact's payload **Use `useArtifacts`** when: - You want to render different artifact types with switch cases - You need to listen to all artifacts for logging/analytics - You want to show an overview of all available artifacts - You're building a generic artifact renderer ## 🔧 Examples See the `src/examples/` directory for complete examples including: - Burn rate analysis with progress tracking - React component integration - Route setup and tool implementation - Using `useArtifacts` for multi-type artifact rendering ## 📄 License MIT