Documentation Index
Fetch the complete documentation index at: https://chatjs.dev/docs/llms.txt
Use this file to discover all available pages before exploring further.
Give your AI tools custom UI components that show loading states and rich results.
Overview
Tools let the LLM perform actions (API calls, calculations, etc). This pattern connects a backend tool to a frontend component that renders its input/output with proper loading states.
File convention: lib/ai/tools/{name}.ts → components/part/{name}.tsx
How it works
- Define tool with Vercel AI SDK’s
tool() - schema + execute function
- Register in
lib/ai/tools/tools.ts
- Route tool type to component in
components/message-parts.tsx
- Render with loading/complete states based on
tool.state
The AI SDK streams tool calls as parts with two states:
| State | tool.state | Available |
|---|
| Loading | "input-available" | tool.input |
| Complete | "output-available" | tool.input + tool.output |
Code
lib/ai/tools/get-weather.ts
import { tool } from "ai";
import { z } from "zod";
export const getWeather = tool({
description: "Get the current weather at a location",
inputSchema: z.object({
latitude: z.number(),
longitude: z.number(),
}),
execute: async ({
latitude,
longitude,
}: {
latitude: number;
longitude: number;
}) => {
const response = await fetch(
`https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t=temperature_2m&hourly=temperature_2m&daily=sunrise,sunset&timezone=auto`
);
const weatherData = await response.json();
return weatherData as WeatherAtLocation;
},
});
export type WeatherAtLocation = {
latitude: number;
longitude: number;
generationtime_ms: number;
utc_offset_seconds: number;
timezone: string;
timezone_abbreviation: string;
elevation: number;
current_units: {
time: string;
interval: string;
temperature_2m: string;
};
current: {
time: string;
interval: number;
temperature_2m: number;
};
hourly_units: {
time: string;
temperature_2m: string;
};
hourly: {
time: string[];
temperature_2m: number[];
};
daily_units: {
time: string;
sunrise: string;
sunset: string;
};
daily: {
time: string[];
sunrise: string[];
sunset: string[];
};
};
import { getWeather } from "./get-weather";
export function getTools(
{
/* ...params */
}
) {
return {
getWeather,
// ... other tools
};
}
3. Route to Component
components/message-parts.tsx
import { Weather } from "./part/weather";
function ToolPart({
part,
messageId,
isReadonly,
}: {
part: ToolUIPart<ChatTools>;
messageId: string;
isReadonly: boolean;
}) {
if (part.type === "tool-getWeather") {
return <Weather tool={part} />;
}
// ... other tools
return null;
}
4. UI Component
components/part/weather.tsx
"use client";
import type { WeatherAtLocation } from "@/lib/ai/tools/get-weather";
import type { ChatMessage } from "@/lib/ai/types";
export type WeatherTool = Extract<
ChatMessage["parts"][number],
{ type: "tool-getWeather" }
>;
export function Weather({ tool }: { tool: WeatherTool }) {
const isLoading = tool.state === "input-available";
if (isLoading) {
return <div className="skeleton rounded-2xl bg-blue-400 p-4 h-24 w-48" />;
}
const data = tool.output;
return (
<div className="rounded-2xl bg-blue-400 p-4 text-white">
<div className="text-4xl">
{Math.round(data.current.temperature_2m)}
{data.current_units.temperature_2m}
</div>
</div>
);
}