Can you search for information about magical forests?
I found some great information! There's a wonderful story called 'The Whispering Woods' about a magical forest where trees can talk and animals sing. The main character is Luna, a young fox with silver fur who has the special ability to communicate with ancient trees.
"use client";
import type { UIMessage } from "@ai-sdk/react";
import type { InferUITools } from "ai";
import { tool } from "ai";
import z from "zod";
import {
ChatMessage,
ChatMessageAuthor,
ChatMessageAvatar,
ChatMessageAvatarFallback,
ChatMessageAvatarImage,
ChatMessageContainer,
ChatMessageContent,
ChatMessageHeader,
ChatMessageMarkdown,
ChatMessageTimestamp,
} from "@/components/ui/chat-message";
import {
ChatMessageArea,
ChatMessageAreaContent,
ChatMessageAreaScrollButton,
} from "@/components/ui/chat-message-area";
import {
ToolInvocation,
ToolInvocationContentCollapsible,
ToolInvocationHeader,
ToolInvocationName,
ToolInvocationRawData,
} from "@/components/ui/tool-invocation";
const searchDatabaseTool = tool({
name: "search-database",
description: "Search the database for information",
inputSchema: z.object({
query: z.string(),
}),
outputSchema: z.string(),
execute: () => {
return "Result of searching the database";
},
});
const toolSet = {
"search-database": searchDatabaseTool,
};
const messages: Array<
UIMessage<
{
member: {
image: string;
name: string;
};
},
never,
InferUITools<typeof toolSet>
>
> = [
{
id: "1",
parts: [
{
type: "text",
text: "Can you search for information about magical forests?",
},
],
role: "user",
metadata: {
member: {
image: "/avatar-1.png",
name: "You",
},
},
},
{
id: "2",
parts: [
{
type: "tool-search-database",
toolCallId: "search-tool-1",
state: "output-available",
input: {
query: "magical forest stories",
},
output: "Found several stories about magical forests, including 'The Whispering Woods' - a tale about a magical forest where trees can talk and animals sing beautiful songs. The story follows Luna, a young fox with silver fur who can speak with ancient trees.",
},
{
type: "text",
text: "I found some great information! There's a wonderful story called 'The Whispering Woods' about a magical forest where trees can talk and animals sing. The main character is Luna, a young fox with silver fur who has the special ability to communicate with ancient trees.",
},
],
role: "assistant",
metadata: {
member: {
image: "/avatar-2.png",
name: "Assistant",
},
},
},
];
export function ChatMessageAreaDemo() {
return (
<ChatMessageArea>
<ChatMessageAreaContent>
{messages.map((message) => (
<ChatMessage key={message.id}>
<ChatMessageAvatar>
<ChatMessageAvatarImage
src={message.metadata?.member.image}
/>
<ChatMessageAvatarFallback>
{message.metadata?.member.name
.charAt(0)
.toUpperCase()}
</ChatMessageAvatarFallback>
</ChatMessageAvatar>
<ChatMessageContainer>
<ChatMessageHeader>
<ChatMessageAuthor>
{message.metadata?.member.name}
</ChatMessageAuthor>
<ChatMessageTimestamp createdAt={new Date()} />
</ChatMessageHeader>
<ChatMessageContent>
{message.parts.map((part) => {
if (part.type === "text") {
return (
<ChatMessageMarkdown
key={`${message.id}-text-${part.text.slice(0, 20)}`}
content={part.text}
/>
);
}
if (part.type === "tool-search-database") {
const hasInput =
part.input != null &&
part.input !== undefined;
const hasOutput =
part.output != null &&
part.output !== undefined;
const toolName = part.type.slice(5);
return (
<ToolInvocation
key={part.toolCallId}
className="w-full"
>
<ToolInvocationHeader>
<ToolInvocationName
name={toolName}
type={part.state}
isError={
part.state ===
"output-error"
}
/>
</ToolInvocationHeader>
{(hasInput ||
hasOutput ||
part.errorText) && (
<ToolInvocationContentCollapsible>
{hasInput && (
<ToolInvocationRawData
data={
part.input
}
title="Arguments"
/>
)}
{part.errorText && (
<ToolInvocationRawData
data={{
error: part.errorText,
}}
title="Error"
/>
)}
{hasOutput && (
<ToolInvocationRawData
data={
part.output
}
title="Result"
/>
)}
</ToolInvocationContentCollapsible>
)}
</ToolInvocation>
);
}
return null;
})}
</ChatMessageContent>
</ChatMessageContainer>
</ChatMessage>
))}
</ChatMessageAreaContent>
<ChatMessageAreaScrollButton />
</ChatMessageArea>
);
}
Installation
pnpm dlx shadcn@latest add @simple-ai/tool-invocation
About
The Tool Invocation component displays tool calls with visual indicators for different states (streaming, available, error). It includes collapsible sections for viewing input arguments and output results.
Examples
Complete Tool Invocation
A tool invocation with completed results showing both input arguments and output.
Can you search for information about magical forests?
I found some great information! There's a wonderful story called 'The Whispering Woods' about a magical forest where trees can talk and animals sing. The main character is Luna, a young fox with silver fur who has the special ability to communicate with ancient trees.
"use client";
import type { UIMessage } from "@ai-sdk/react";
import type { InferUITools } from "ai";
import { tool } from "ai";
import z from "zod";
import {
ChatMessage,
ChatMessageAuthor,
ChatMessageAvatar,
ChatMessageAvatarFallback,
ChatMessageAvatarImage,
ChatMessageContainer,
ChatMessageContent,
ChatMessageHeader,
ChatMessageMarkdown,
ChatMessageTimestamp,
} from "@/components/ui/chat-message";
import {
ChatMessageArea,
ChatMessageAreaContent,
ChatMessageAreaScrollButton,
} from "@/components/ui/chat-message-area";
import {
ToolInvocation,
ToolInvocationContentCollapsible,
ToolInvocationHeader,
ToolInvocationName,
ToolInvocationRawData,
} from "@/components/ui/tool-invocation";
const searchDatabaseTool = tool({
name: "search-database",
description: "Search the database for information",
inputSchema: z.object({
query: z.string(),
}),
outputSchema: z.string(),
execute: () => {
return "Result of searching the database";
},
});
const toolSet = {
"search-database": searchDatabaseTool,
};
const messages: Array<
UIMessage<
{
member: {
image: string;
name: string;
};
},
never,
InferUITools<typeof toolSet>
>
> = [
{
id: "1",
parts: [
{
type: "text",
text: "Can you search for information about magical forests?",
},
],
role: "user",
metadata: {
member: {
image: "/avatar-1.png",
name: "You",
},
},
},
{
id: "2",
parts: [
{
type: "tool-search-database",
toolCallId: "search-tool-1",
state: "output-available",
input: {
query: "magical forest stories",
},
output: "Found several stories about magical forests, including 'The Whispering Woods' - a tale about a magical forest where trees can talk and animals sing beautiful songs. The story follows Luna, a young fox with silver fur who can speak with ancient trees.",
},
{
type: "text",
text: "I found some great information! There's a wonderful story called 'The Whispering Woods' about a magical forest where trees can talk and animals sing. The main character is Luna, a young fox with silver fur who has the special ability to communicate with ancient trees.",
},
],
role: "assistant",
metadata: {
member: {
image: "/avatar-2.png",
name: "Assistant",
},
},
},
];
export function ChatMessageAreaDemo() {
return (
<ChatMessageArea>
<ChatMessageAreaContent>
{messages.map((message) => (
<ChatMessage key={message.id}>
<ChatMessageAvatar>
<ChatMessageAvatarImage
src={message.metadata?.member.image}
/>
<ChatMessageAvatarFallback>
{message.metadata?.member.name
.charAt(0)
.toUpperCase()}
</ChatMessageAvatarFallback>
</ChatMessageAvatar>
<ChatMessageContainer>
<ChatMessageHeader>
<ChatMessageAuthor>
{message.metadata?.member.name}
</ChatMessageAuthor>
<ChatMessageTimestamp createdAt={new Date()} />
</ChatMessageHeader>
<ChatMessageContent>
{message.parts.map((part) => {
if (part.type === "text") {
return (
<ChatMessageMarkdown
key={`${message.id}-text-${part.text.slice(0, 20)}`}
content={part.text}
/>
);
}
if (part.type === "tool-search-database") {
const hasInput =
part.input != null &&
part.input !== undefined;
const hasOutput =
part.output != null &&
part.output !== undefined;
const toolName = part.type.slice(5);
return (
<ToolInvocation
key={part.toolCallId}
className="w-full"
>
<ToolInvocationHeader>
<ToolInvocationName
name={toolName}
type={part.state}
isError={
part.state ===
"output-error"
}
/>
</ToolInvocationHeader>
{(hasInput ||
hasOutput ||
part.errorText) && (
<ToolInvocationContentCollapsible>
{hasInput && (
<ToolInvocationRawData
data={
part.input
}
title="Arguments"
/>
)}
{part.errorText && (
<ToolInvocationRawData
data={{
error: part.errorText,
}}
title="Error"
/>
)}
{hasOutput && (
<ToolInvocationRawData
data={
part.output
}
title="Result"
/>
)}
</ToolInvocationContentCollapsible>
)}
</ToolInvocation>
);
}
return null;
})}
</ChatMessageContent>
</ChatMessageContainer>
</ChatMessage>
))}
</ChatMessageAreaContent>
<ChatMessageAreaScrollButton />
</ChatMessageArea>
);
}
Loading State
A tool invocation showing the loading state while the tool is being executed.
Can you search for information about magical forests?
"use client";
import type { UIMessage } from "@ai-sdk/react";
import type { InferUITools } from "ai";
import { tool } from "ai";
import z from "zod";
import {
ChatMessage,
ChatMessageAuthor,
ChatMessageAvatar,
ChatMessageAvatarFallback,
ChatMessageAvatarImage,
ChatMessageContainer,
ChatMessageContent,
ChatMessageHeader,
ChatMessageMarkdown,
ChatMessageTimestamp,
} from "@/components/ui/chat-message";
import {
ChatMessageArea,
ChatMessageAreaContent,
ChatMessageAreaScrollButton,
} from "@/components/ui/chat-message-area";
import {
ToolInvocation,
ToolInvocationContentCollapsible,
ToolInvocationHeader,
ToolInvocationName,
ToolInvocationRawData,
} from "@/components/ui/tool-invocation";
const searchDatabaseTool = tool({
name: "search-database",
description: "Search the database for information",
inputSchema: z.object({
query: z.string(),
}),
outputSchema: z.string(),
execute: () => {
return "Result of searching the database";
},
});
const toolSet = {
"search-database": searchDatabaseTool,
};
const messages: Array<
UIMessage<
{
member: {
image: string;
name: string;
};
},
never,
InferUITools<typeof toolSet>
>
> = [
{
id: "1",
parts: [
{
type: "text",
text: "Can you search for information about magical forests?",
},
],
role: "user",
metadata: {
member: {
image: "/avatar-1.png",
name: "You",
},
},
},
{
id: "2",
parts: [
{
type: "tool-search-database",
toolCallId: "search-tool-1",
state: "input-available",
input: {
query: "magical forest stories",
},
},
],
role: "assistant",
metadata: {
member: {
image: "/avatar-2.png",
name: "Assistant",
},
},
},
];
export function ToolInvocationDemoLoading() {
return (
<ChatMessageArea>
<ChatMessageAreaContent>
{messages.map((message) => (
<ChatMessage key={message.id}>
<ChatMessageAvatar>
<ChatMessageAvatarImage
src={message.metadata?.member.image}
/>
<ChatMessageAvatarFallback>
{message.metadata?.member.name
.charAt(0)
.toUpperCase()}
</ChatMessageAvatarFallback>
</ChatMessageAvatar>
<ChatMessageContainer>
<ChatMessageHeader>
<ChatMessageAuthor>
{message.metadata?.member.name}
</ChatMessageAuthor>
<ChatMessageTimestamp createdAt={new Date()} />
</ChatMessageHeader>
<ChatMessageContent>
{message.parts.map((part) => {
if (part.type === "text") {
return (
<ChatMessageMarkdown
key={`${message.id}-text-${part.text.slice(0, 20)}`}
content={part.text}
/>
);
}
if (part.type === "tool-search-database") {
const hasInput =
part.input != null &&
part.input !== undefined;
const hasOutput =
part.output != null &&
part.output !== undefined;
const toolName = part.type.slice(5);
return (
<ToolInvocation
key={part.toolCallId}
className="w-full"
>
<ToolInvocationHeader>
<ToolInvocationName
name={toolName}
type={part.state}
isError={
part.state ===
"output-error"
}
/>
</ToolInvocationHeader>
{(hasInput ||
hasOutput ||
part.errorText) && (
<ToolInvocationContentCollapsible>
{hasInput && (
<ToolInvocationRawData
data={
part.input
}
title="Arguments"
/>
)}
{part.errorText && (
<ToolInvocationRawData
data={{
error: part.errorText,
}}
title="Error"
/>
)}
{hasOutput && (
<ToolInvocationRawData
data={
part.output
}
title="Result"
/>
)}
</ToolInvocationContentCollapsible>
)}
</ToolInvocation>
);
}
return null;
})}
</ChatMessageContent>
</ChatMessageContainer>
</ChatMessage>
))}
</ChatMessageAreaContent>
<ChatMessageAreaScrollButton />
</ChatMessageArea>
);
}
Error State
A tool invocation showing an error state when the tool execution fails.
Can you search for information about magical forests?
I encountered an error while searching. The database connection timed out.
"use client";
import type { UIMessage } from "@ai-sdk/react";
import type { InferUITools } from "ai";
import { tool } from "ai";
import z from "zod";
import {
ChatMessage,
ChatMessageAuthor,
ChatMessageAvatar,
ChatMessageAvatarFallback,
ChatMessageAvatarImage,
ChatMessageContainer,
ChatMessageContent,
ChatMessageHeader,
ChatMessageMarkdown,
ChatMessageTimestamp,
} from "@/components/ui/chat-message";
import {
ChatMessageArea,
ChatMessageAreaContent,
ChatMessageAreaScrollButton,
} from "@/components/ui/chat-message-area";
import {
ToolInvocation,
ToolInvocationContentCollapsible,
ToolInvocationHeader,
ToolInvocationName,
ToolInvocationRawData,
} from "@/components/ui/tool-invocation";
const searchDatabaseTool = tool({
name: "search-database",
description: "Search the database for information",
inputSchema: z.object({
query: z.string(),
}),
outputSchema: z.string(),
execute: () => {
return "Result of searching the database";
},
});
const toolSet = {
"search-database": searchDatabaseTool,
};
const messages: Array<
UIMessage<
{
member: {
image: string;
name: string;
};
},
never,
InferUITools<typeof toolSet>
>
> = [
{
id: "1",
parts: [
{
type: "text",
text: "Can you search for information about magical forests?",
},
],
role: "user",
metadata: {
member: {
image: "/avatar-1.png",
name: "You",
},
},
},
{
id: "2",
parts: [
{
type: "tool-search-database",
toolCallId: "search-tool-1",
state: "output-error",
input: {
query: "magical forest stories",
},
output: undefined,
errorText:
"Database connection timeout. Please try again later.",
},
{
type: "text",
text: "I encountered an error while searching. The database connection timed out.",
},
],
role: "assistant",
metadata: {
member: {
image: "/avatar-2.png",
name: "Assistant",
},
},
},
];
export function ToolInvocationDemoError() {
return (
<ChatMessageArea>
<ChatMessageAreaContent>
{messages.map((message) => (
<ChatMessage key={message.id}>
<ChatMessageAvatar>
<ChatMessageAvatarImage
src={message.metadata?.member.image}
/>
<ChatMessageAvatarFallback>
{message.metadata?.member.name
.charAt(0)
.toUpperCase()}
</ChatMessageAvatarFallback>
</ChatMessageAvatar>
<ChatMessageContainer>
<ChatMessageHeader>
<ChatMessageAuthor>
{message.metadata?.member.name}
</ChatMessageAuthor>
<ChatMessageTimestamp createdAt={new Date()} />
</ChatMessageHeader>
<ChatMessageContent>
{message.parts.map((part) => {
if (part.type === "text") {
return (
<ChatMessageMarkdown
key={`${message.id}-text-${part.text.slice(0, 20)}`}
content={part.text}
/>
);
}
if (part.type === "tool-search-database") {
const hasInput =
part.input != null &&
part.input !== undefined;
const hasOutput =
part.output != null &&
part.output !== undefined;
const toolName = part.type.slice(5);
return (
<ToolInvocation
key={part.toolCallId}
className="w-full"
>
<ToolInvocationHeader>
<ToolInvocationName
name={toolName}
type={part.state}
isError={
part.state ===
"output-error"
}
/>
</ToolInvocationHeader>
{(hasInput ||
hasOutput ||
part.errorText) && (
<ToolInvocationContentCollapsible>
{hasInput && (
<ToolInvocationRawData
data={
part.input
}
title="Arguments"
/>
)}
{part.errorText && (
<ToolInvocationRawData
data={{
error: part.errorText,
}}
title="Error"
/>
)}
{hasOutput && (
<ToolInvocationRawData
data={
part.output
}
title="Result"
/>
)}
</ToolInvocationContentCollapsible>
)}
</ToolInvocation>
);
}
return null;
})}
</ChatMessageContent>
</ChatMessageContainer>
</ChatMessage>
))}
</ChatMessageAreaContent>
<ChatMessageAreaScrollButton />
</ChatMessageArea>
);
}