Help us improve
Share bugs, ideas, or general feedback.
From nextjs-supabase-ai-sdk-dev
Implements streaming chat interfaces, completions, tool calling UIs, and generative features using Vercel AI SDK hooks like useChat and useCompletion in React/Next.js apps.
npx claudepluginhub constellos/claude-code --plugin nextjs-supabase-ai-sdk-devHow this skill is triggered — by the user, by Claude, or both
Slash command
/nextjs-supabase-ai-sdk-dev:ai-sdk-uiThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Implement AI-powered user interfaces with the Vercel AI SDK. This skill covers streaming UI patterns, conversational interfaces, completion features, tool calling with visual feedback, and generative UI using React Server Components.
Provides Vercel AI SDK v5 React hooks (useChat, useCompletion, useObject) for streaming AI chat UIs in Next.js apps. Fixes parse stream errors, no responses, and streaming issues.
Helps build AI-powered apps with the Vercel AI SDK: core generation APIs, UI hooks for chat/streaming, tool calling, and structured data generation in Next.js and React.
Generative UI implementation patterns for AI SDK RSC including server-side streaming components, dynamic UI generation, and client-server coordination. Use when implementing generative UI, building AI SDK RSC, creating streaming components, or when user mentions generative UI, React Server Components, dynamic UI, AI-generated interfaces, or server-side streaming.
Share bugs, ideas, or general feedback.
Implement AI-powered user interfaces with the Vercel AI SDK. This skill covers streaming UI patterns, conversational interfaces, completion features, tool calling with visual feedback, and generative UI using React Server Components.
When to use:
The AI SDK provides React hooks for client-side AI interactions:
useChat - Conversational interfaces with message history:
'use client';
import { useChat } from 'ai/react';
export function ChatInterface() {
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
api: '/api/chat',
});
return (
<div className="flex flex-col h-full">
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.map((message) => (
<div
key={message.id}
className={message.role === 'user' ? 'text-right' : 'text-left'}
>
<div className={`inline-block p-3 rounded-lg ${
message.role === 'user'
? 'bg-blue-500 text-white'
: 'bg-gray-100 text-gray-900'
}`}>
{message.content}
</div>
</div>
))}
</div>
<form onSubmit={handleSubmit} className="p-4 border-t">
<div className="flex gap-2">
<input
value={input}
onChange={handleInputChange}
placeholder="Type a message..."
className="flex-1 p-2 border rounded"
disabled={isLoading}
/>
<button
type="submit"
disabled={isLoading}
className="px-4 py-2 bg-blue-500 text-white rounded disabled:opacity-50"
>
{isLoading ? 'Sending...' : 'Send'}
</button>
</div>
</form>
</div>
);
}
useCompletion - Single completions without message history:
'use client';
import { useCompletion } from 'ai/react';
export function CompletionInput() {
const { completion, input, handleInputChange, handleSubmit, isLoading } = useCompletion({
api: '/api/completion',
});
return (
<div className="space-y-4">
<form onSubmit={handleSubmit}>
<textarea
value={input}
onChange={handleInputChange}
placeholder="Enter prompt..."
className="w-full p-3 border rounded"
rows={4}
/>
<button
type="submit"
disabled={isLoading}
className="mt-2 px-4 py-2 bg-green-500 text-white rounded"
>
{isLoading ? 'Generating...' : 'Generate'}
</button>
</form>
{completion && (
<div className="p-4 bg-gray-50 rounded">
<h3 className="font-semibold mb-2">Result:</h3>
<p className="whitespace-pre-wrap">{completion}</p>
</div>
)}
</div>
);
}
Create API routes that stream responses:
Chat API Route (app/api/chat/route.ts):
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai('gpt-4o'),
messages,
system: 'You are a helpful assistant.',
});
return result.toDataStreamResponse();
}
Completion API Route (app/api/completion/route.ts):
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';
export async function POST(req: Request) {
const { prompt } = await req.json();
const result = streamText({
model: openai('gpt-4o'),
prompt,
});
return result.toDataStreamResponse();
}
Token-by-token streaming display:
'use client';
import { useChat } from 'ai/react';
export function StreamingChat() {
const { messages, input, handleInputChange, handleSubmit } = useChat();
return (
<div>
{messages.map((message) => (
<div key={message.id}>
<strong>{message.role}:</strong>
{/* Content streams in token-by-token */}
<span className="animate-pulse">{message.content}</span>
</div>
))}
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleInputChange} />
<button type="submit">Send</button>
</form>
</div>
);
}
Loading states and indicators:
'use client';
import { useChat } from 'ai/react';
export function ChatWithLoadingStates() {
const { messages, input, handleInputChange, handleSubmit, isLoading, error } = useChat();
return (
<div>
{messages.map((message) => (
<div key={message.id}>{message.content}</div>
))}
{isLoading && (
<div className="flex items-center gap-2 text-gray-500">
<div className="animate-spin h-4 w-4 border-2 border-gray-300 border-t-blue-500 rounded-full" />
<span>AI is thinking...</span>
</div>
)}
{error && (
<div className="text-red-500 p-2 bg-red-50 rounded">
Error: {error.message}
</div>
)}
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={handleInputChange}
disabled={isLoading}
/>
<button type="submit" disabled={isLoading}>
Send
</button>
</form>
</div>
);
}
Implement tools that Claude can call with visual feedback:
Server-side tool definition:
import { openai } from '@ai-sdk/openai';
import { streamText, tool } from 'ai';
import { z } from 'zod';
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai('gpt-4o'),
messages,
tools: {
getWeather: tool({
description: 'Get current weather for a location',
parameters: z.object({
location: z.string().describe('City name'),
}),
execute: async ({ location }) => {
// Fetch weather data
return { temperature: 72, condition: 'sunny', location };
},
}),
searchProducts: tool({
description: 'Search for products',
parameters: z.object({
query: z.string(),
maxResults: z.number().optional().default(5),
}),
execute: async ({ query, maxResults }) => {
// Search products
return { results: [], query, count: 0 };
},
}),
},
});
return result.toDataStreamResponse();
}
Client-side tool result rendering:
'use client';
import { useChat } from 'ai/react';
function WeatherCard({ data }: { data: { temperature: number; condition: string; location: string } }) {
return (
<div className="p-4 bg-blue-50 rounded-lg">
<h3 className="font-semibold">{data.location}</h3>
<p className="text-2xl">{data.temperature}°F</p>
<p className="text-gray-600">{data.condition}</p>
</div>
);
}
export function ChatWithTools() {
const { messages, input, handleInputChange, handleSubmit } = useChat({
maxSteps: 5, // Allow multi-step tool use
});
return (
<div>
{messages.map((message) => (
<div key={message.id}>
{message.role === 'assistant' && message.toolInvocations?.map((tool) => (
<div key={tool.toolCallId}>
{tool.toolName === 'getWeather' && tool.state === 'result' && (
<WeatherCard data={tool.result} />
)}
{tool.state === 'call' && (
<div className="animate-pulse p-2 bg-gray-100 rounded">
Calling {tool.toolName}...
</div>
)}
</div>
))}
{message.content && <p>{message.content}</p>}
</div>
))}
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleInputChange} />
<button type="submit">Send</button>
</form>
</div>
);
}
Use streamUI for server-side streaming of React components:
Server Action with streamUI:
'use server';
import { openai } from '@ai-sdk/openai';
import { streamUI } from 'ai/rsc';
import { z } from 'zod';
export async function generateUI(prompt: string) {
const result = await streamUI({
model: openai('gpt-4o'),
prompt,
tools: {
showWeather: {
description: 'Show weather widget',
parameters: z.object({
location: z.string(),
temperature: z.number(),
}),
generate: async function* ({ location, temperature }) {
yield <div className="animate-pulse">Loading weather...</div>;
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000));
return (
<div className="p-4 bg-gradient-to-r from-blue-400 to-blue-600 text-white rounded-lg">
<h3 className="text-xl font-bold">{location}</h3>
<p className="text-3xl">{temperature}°F</p>
</div>
);
},
},
showStockChart: {
description: 'Show stock price chart',
parameters: z.object({
symbol: z.string(),
price: z.number(),
}),
generate: async function* ({ symbol, price }) {
yield <div>Loading {symbol} data...</div>;
return (
<div className="p-4 border rounded-lg">
<h3 className="font-bold">{symbol}</h3>
<p className="text-2xl text-green-600">${price}</p>
</div>
);
},
},
},
});
return result.value;
}
Client component consuming streamUI:
'use client';
import { useState } from 'react';
import { generateUI } from './actions';
export function GenerativeUIDemo() {
const [ui, setUI] = useState<React.ReactNode>(null);
const [prompt, setPrompt] = useState('');
const [isLoading, setIsLoading] = useState(false);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setIsLoading(true);
const result = await generateUI(prompt);
setUI(result);
setIsLoading(false);
}
return (
<div className="space-y-4">
<form onSubmit={handleSubmit} className="flex gap-2">
<input
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
placeholder="Ask about weather, stocks..."
className="flex-1 p-2 border rounded"
/>
<button
type="submit"
disabled={isLoading}
className="px-4 py-2 bg-purple-500 text-white rounded"
>
Generate
</button>
</form>
<div className="min-h-[200px] p-4 border rounded">
{ui || <p className="text-gray-400">Generated UI will appear here</p>}
</div>
</div>
);
}
Performance:
maxSteps to limit tool calling depthError Handling:
error state from hooksAccessibility:
Security:
For detailed patterns and advanced techniques:
references/advanced-patterns.md - Multi-modal AI, conversation memory, custom providers