Documentation Index
Fetch the complete documentation index at: https://mintlify.com/vercel/ai/llms.txt
Use this file to discover all available pages before exploring further.
Generative UI
Generative UI allows you to create dynamic interfaces that change based on AI model outputs and tool calls. Instead of displaying raw text or JSON, you can render custom React components that provide rich, interactive experiences.
Basic Concept
Generative UI maps AI model outputs to React components:
const result = await streamUI({
model: openai('gpt-4'),
prompt: 'What is the weather in San Francisco?',
tools: {
getWeather: {
description: 'Get current weather',
inputSchema: z.object({
location: z.string(),
}),
generate: async ({ location }) => {
const weather = await fetchWeather(location);
return <WeatherCard {...weather} />;
},
},
},
});
State Management with createAI
createAI provides a context provider for managing AI and UI state across your application.
Setting Up the Provider
'use server';
import { createAI } from '@ai-sdk/rsc';
import { continueConversation } from './actions';
export type AIState = Array<{
role: 'user' | 'assistant';
content: string;
}>;
export type UIState = Array<{
id: string;
component: React.ReactNode;
}>;
export const AI = createAI({
actions: {
continueConversation,
},
initialAIState: [] as AIState,
initialUIState: [] as UIState,
});
Using the Provider
import { AI } from './ai-provider';
export default function RootLayout({ children }) {
return (
<html>
<body>
<AI>{children}</AI>
</body>
</html>
);
}
Accessing State
'use client';
import { useUIState, useActions } from '@ai-sdk/rsc';
export default function Chat() {
const [messages, setMessages] = useUIState();
const { continueConversation } = useActions();
const handleSubmit = async (input: string) => {
const response = await continueConversation(input);
setMessages([...messages, response]);
};
return (
<div>
{messages.map((msg) => (
<div key={msg.id}>{msg.component}</div>
))}
</div>
);
}
Use generators to show progress during tool execution:
tools: {
analyzeData: {
description: 'Analyze a dataset',
inputSchema: z.object({
datasetId: z.string(),
}),
generate: async function* ({ datasetId }) {
// Step 1: Loading
yield <div>Loading dataset {datasetId}...</div>;
const data = await loadDataset(datasetId);
// Step 2: Processing
yield (
<div>
<p>Loaded {data.rows.length} rows</p>
<p>Analyzing...</p>
</div>
);
const analysis = await analyzeData(data);
// Step 3: Complete
return (
<AnalysisResults
summary={analysis.summary}
charts={analysis.charts}
insights={analysis.insights}
/>
);
},
},
}
Conditional UI Generation
Render different components based on tool parameters:
tools: {
displayData: {
description: 'Display data in different formats',
inputSchema: z.object({
data: z.array(z.any()),
format: z.enum(['table', 'chart', 'list']),
}),
generate: async ({ data, format }) => {
switch (format) {
case 'table':
return <DataTable data={data} />;
case 'chart':
return <DataChart data={data} />;
case 'list':
return <DataList data={data} />;
}
},
},
}
Interactive Components
Generate components with client-side interactivity:
tools: {
createPoll: {
description: 'Create an interactive poll',
inputSchema: z.object({
question: z.string(),
options: z.array(z.string()),
}),
generate: async ({ question, options }) => {
return (
<InteractivePoll
question={question}
options={options}
onVote={(option) => {
// Handle vote
}}
/>
);
},
},
}
Managing AI State
Reading AI State
'use server';
import { getAIState } from '@ai-sdk/rsc';
export async function continueConversation(input: string) {
const history = getAIState();
// Use history in your prompt
const result = await streamUI({
model: openai('gpt-4'),
messages: [
...history,
{ role: 'user', content: input },
],
});
return result.value;
}
Updating AI State
'use server';
import { getMutableAIState } from '@ai-sdk/rsc';
export async function continueConversation(input: string) {
const state = getMutableAIState();
// Update state
state.update([
...state.get(),
{ role: 'user', content: input },
]);
const result = await streamUI({
model: openai('gpt-4'),
prompt: input,
});
// Finalize state
state.done([
...state.get(),
{ role: 'assistant', content: result.text },
]);
return result.value;
}
Persisting State
Use callbacks to persist state to a database:
export const AI = createAI({
actions: { continueConversation },
initialAIState: [],
initialUIState: [],
onSetAIState: async ({ state, done }) => {
'use server';
if (done) {
// Save to database when conversation is complete
await saveConversation(state);
}
},
onGetUIState: async () => {
'use server';
// Load conversation from database
const conversation = await loadConversation();
// Convert to UI state
return conversation.map((msg) => ({
id: msg.id,
component: <Message {...msg} />,
}));
},
});
Component Patterns
Skeleton Loading States
generate: async function* ({ query }) {
yield <ResultsSkeleton count={5} />;
const results = await search(query);
return <ResultsList results={results} />;
}
Incremental Rendering
generate: async function* ({ items }) {
for (const item of items) {
const processed = await processItem(item);
yield <ProcessedItem data={processed} />;
}
return <CompletionMessage />;
}
Error Recovery
generate: async function* ({ action }) {
try {
yield <div>Processing {action}...</div>;
const result = await performAction(action);
return <Success data={result} />;
} catch (error) {
return (
<ErrorCard
message={error.message}
retry={() => performAction(action)}
/>
);
}
}
Routing Components
Route to different UIs based on model decisions:
tools: {
showContent: {
description: 'Display content in the appropriate format',
inputSchema: z.object({
type: z.enum(['article', 'video', 'image', 'code']),
content: z.any(),
}),
generate: async ({ type, content }) => {
const components = {
article: <Article content={content} />,
video: <VideoPlayer url={content.url} />,
image: <ImageGallery images={content.images} />,
code: <CodeBlock code={content.code} language={content.language} />,
};
return components[type];
},
},
}
Best Practices
1. Type Safety
import { z } from 'zod';
const weatherSchema = z.object({
location: z.string(),
units: z.enum(['celsius', 'fahrenheit']).default('celsius'),
});
tools: {
getWeather: {
inputSchema: weatherSchema,
generate: async (params) => {
// params is fully typed
const weather = await fetchWeather(params.location, params.units);
return <WeatherCard {...weather} />;
},
},
}
2. Component Reusability
// Shared component
function DataVisualization({ data, type }) {
return type === 'chart' ? <Chart data={data} /> : <Table data={data} />;
}
// Use in multiple tools
tools: {
analyzeData: {
generate: async ({ data }) => {
const analysis = await analyze(data);
return <DataVisualization data={analysis} type="chart" />;
},
},
showData: {
generate: async ({ data }) => {
return <DataVisualization data={data} type="table" />;
},
},
}
3. Progressive Disclosure
generate: async function* ({ topic }) {
// Show summary first
const summary = await generateSummary(topic);
yield <Summary text={summary} />;
// Then show details
const details = await generateDetails(topic);
yield (
<>
<Summary text={summary} />
<Details content={details} />
</>
);
// Finally show related content
const related = await findRelated(topic);
return (
<>
<Summary text={summary} />
<Details content={details} />
<RelatedContent items={related} />
</>
);
}
Next Steps