"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 ( ); }