Tool Result Rendering
Frontend tools can include a render
method that displays custom React components for their results. This creates rich, interactive content directly in the chat interface, going beyond simple text responses.
AI with Custom Tool Rendering
• Tools that render React componentsCustom Rendering with React Components
The render
method receives the tool’s return value and renders a React component:
useAIFrontendTool({
name: "create_chart",
description: "Create a data visualization chart",
parameters: z.object({
title: z.string().describe("Title for the chart"),
type: z.enum(["bar", "pie", "line"]).describe("Type of chart"),
data: z.array(z.object({
label: z.string().describe("Label for data point"),
value: z.number().describe("Value for data point"),
})).describe("Data points for the chart"),
}),
execute: async (params) => {
return {
chartId: `chart-${Date.now()}`,
title: params.title,
type: params.type,
data: params.data,
createdAt: new Date().toISOString(),
};
},
render: (result) => (
<ChartComponent
title={result.title}
type={result.type}
data={result.data}
/>
),
});
Complete Implementation Example
Here’s a full example showing tools with custom rendering:
"use client";
import React, { useState } from "react";
import { ChatContainer, useAIChat, useAIFrontendTool } from "ai-chat-bootstrap";
import { z } from "zod";
// Custom Chart Component
function ChartComponent({ data, type, title }: {
data: Array<{ label: string; value: number }>;
type: "bar" | "pie" | "line";
title: string;
}) {
const maxValue = Math.max(...data.map(d => d.value));
return (
<div className="p-4 bg-card rounded-lg border">
<h3 className="text-lg font-semibold mb-4">{title}</h3>
<div className="space-y-3">
{data.map((item, index) => (
<div key={index} className="flex items-center gap-3">
<div className="w-16 text-sm text-muted-foreground">
{item.label}
</div>
<div className="flex-1 bg-muted rounded-full h-6 relative">
<div
className="h-full bg-primary rounded-full transition-all duration-500"
style={{ width: `${(item.value / maxValue) * 100}%` }}
/>
<div className="absolute inset-0 flex items-center justify-center text-xs font-medium">
{item.value}
</div>
</div>
</div>
))}
</div>
</div>
);
}
export function ToolRenderingDemo() {
// Chart creation tool with custom rendering
useAIFrontendTool({
name: "create_chart",
description: "Create a data visualization chart",
parameters: z.object({
title: z.string().describe("Title for the chart"),
type: z.enum(["bar", "pie", "line"]).describe("Type of chart"),
data: z.array(z.object({
label: z.string().describe("Label for data point"),
value: z.number().describe("Value for data point"),
})).describe("Data points for the chart"),
}),
execute: async (params) => {
return {
chartId: `chart-${Date.now()}`,
title: params.title,
type: params.type,
data: params.data,
createdAt: new Date().toISOString(),
};
},
render: (result) => (
<ChartComponent
title={result.title}
type={result.type}
data={result.data}
/>
),
});
const chat = useAIChat({
api: "/api/chat",
systemPrompt: "You can create charts using the create_chart tool when users request data visualizations."
});
return (
<ChatContainer
chat={chat}
header={{ title: "AI with Custom Rendering" }}
ui={{ placeholder: "Ask me to create a chart!" }}
/>
);
}
Backend Integration
The backend API route works the same way - tools with render methods are handled automatically:
import { createAzure } from "@ai-sdk/azure";
import { convertToModelMessages, streamText } from "ai";
const azure = createAzure({
resourceName: process.env.AZURE_RESOURCE_NAME!,
apiKey: process.env.AZURE_API_KEY!,
apiVersion: process.env.AZURE_API_VERSION ?? "preview",
});
const model = azure(process.env.AZURE_DEPLOYMENT_ID!);
export async function POST(req: Request) {
const { messages, enrichedSystemPrompt, tools } = await req.json();
const result = await streamText({
model,
messages: [
{ role: "system", content: enrichedSystemPrompt },
...convertToModelMessages(messages),
],
tools,
});
return result.toUIMessageStreamResponse();
}
Advanced Features
Conditional Rendering
Render different components based on the result:
render: (result) => {
if (result.type === 'success') {
return <SuccessCard message={result.message} />;
}
if (result.type === 'error') {
return <ErrorCard error={result.error} retry={result.retry} />;
}
return <DefaultCard data={result} />;
}
State Management
Tools can interact with external state:
render: (result) => (
<div onClick={() => setSelectedItem(result.id)}>
<ItemCard
item={result}
isSelected={selectedItem === result.id}
/>
</div>
)
Event Handling
Components can trigger additional actions:
render: (result) => (
<div className="p-4 bg-card rounded-lg border">
<h3>{result.title}</h3>
<button
onClick={() => {
// Trigger another tool or action
executeAnotherTool(result.id);
}}
className="mt-2 bg-primary text-primary-foreground px-4 py-2 rounded"
>
Process Result
</button>
</div>
)
How It Works
- Tool Execution: Tool runs and returns structured data
- Render Invocation: The
render
method receives the result - Component Creation: A React component is created and rendered
- Chat Integration: The component appears in the chat message
- Interactivity: Users can interact with the rendered component
- State Updates: Components can trigger further tool executions
API Reference
- Hook: useAIFrontendTool
- Hook: useAIChat
Next
Continue to Sharing Context →
Last updated on