"use client";
// Tool Rendering — PRIMARY (per-tool + catch-all) variant.
//
// The most sophisticated point in the three-way progression: every
// "interesting" backend tool gets its own dedicated, branded UI, and a
// catch-all paints anything that slips through.
//
// get_weather → (per-tool renderer)
// search_flights → (per-tool renderer)
// get_stock_price → (per-tool renderer)
// roll_d20 → (per-tool renderer)
// * → (wildcard fallback)
// @region[render-flight-tool]
// @region[render-weather-tool]
import React from "react";
import {
CopilotKit,
CopilotChat,
useRenderTool,
useDefaultRenderTool,
} from "@copilotkit/react-core/v2";
import { z } from "zod";
import { WeatherCard } from "./weather-card";
import { FlightListCard } from "./flight-list-card";
import type { Flight } from "./flight-list-card";
import { StockCard } from "./stock-card";
import { D20Card } from "./d20-card";
import { CustomCatchallRenderer } from "./custom-catchall-renderer";
import type { CatchallToolStatus } from "./custom-catchall-renderer";
import { parseJsonResult } from "../_shared/parse-json-result";
import { useSuggestions } from "./suggestions";
interface WeatherResult {
city?: string;
temperature?: number;
humidity?: number;
wind_speed?: number;
conditions?: string;
}
interface FlightSearchResult {
origin?: string;
destination?: string;
flights?: Flight[];
}
interface StockResult {
ticker?: string;
price_usd?: number;
change_pct?: number;
}
interface D20Result {
value?: number;
result?: number;
sides?: number;
}
export default function ToolRenderingDemo() {
return (
);
}
function Chat() {
// Per-tool renderer #1: get_weather → branded WeatherCard.
useRenderTool(
{
name: "get_weather",
parameters: z.object({
location: z.string(),
}),
render: ({ parameters, result, status }) => {
const loading = status !== "complete";
const parsed = parseJsonResult(result);
return (
);
},
},
[],
);
// @endregion[render-weather-tool]
// Per-tool renderer #2: search_flights → branded FlightListCard.
useRenderTool(
{
name: "search_flights",
parameters: z.object({
origin: z.string(),
destination: z.string(),
}),
render: ({ parameters, result, status }) => {
const loading = status !== "complete";
const parsed = parseJsonResult(result);
return (
);
},
},
[],
);
// @endregion[render-flight-tool]
// Per-tool renderer #3: get_stock_price → branded StockCard.
useRenderTool(
{
name: "get_stock_price",
parameters: z.object({
ticker: z.string(),
}),
render: ({ parameters, result, status }) => {
const loading = status !== "complete";
const parsed = parseJsonResult(result);
return (
);
},
},
[],
);
// Per-tool renderer #4: roll_d20 → branded D20Card. Each tool call
// mounts its own card so e2e tests can count them.
useRenderTool(
{
name: "roll_d20",
parameters: z.object({
value: z.number().optional(),
}),
render: ({ result, status }) => {
const loading = status !== "complete";
const parsed = parseJsonResult(result);
const value =
typeof parsed.value === "number"
? parsed.value
: typeof parsed.result === "number"
? parsed.result
: undefined;
return ;
},
},
[],
);
// @region[catchall-renderer]
// Wildcard catch-all for anything that doesn't match a per-tool
// renderer above.
useDefaultRenderTool(
{
render: ({ name, parameters, status, result }) => (
),
},
[],
);
// @endregion[catchall-renderer]
useSuggestions();
return (
);
}