import React, { useEffect, useRef } from "react"; import { useCopilotKit } from "../providers/CopilotKitProvider"; import { defineToolCallRenderer } from "../types/defineToolCallRenderer"; import { z } from "zod"; /** * Tool name used by the dynamic A2UI generation secondary LLM. * This renderer is auto-registered when A2UI is enabled. */ export const RENDER_A2UI_TOOL_NAME = "render_a2ui"; interface A2UIProgressProps { parameters: unknown; } /** * Built-in progress indicator for dynamic A2UI generation. * Shows a skeleton wireframe that progressively reveals as tokens stream in. * * Registered automatically when A2UI is enabled. Users can override by * providing their own `useRenderTool({ name: "render_a2ui", ... })`. */ function A2UIProgressIndicator({ parameters }: A2UIProgressProps) { const lastRef = useRef({ time: 0, tokens: 0 }); const now = Date.now(); let { tokens } = lastRef.current; if (now - lastRef.current.time > 200) { const chars = JSON.stringify(parameters ?? {}).length; tokens = Math.round(chars / 4); lastRef.current = { time: now, tokens }; } const phase = tokens < 50 ? 0 : tokens < 200 ? 1 : tokens < 400 ? 2 : 3; return (
{/* Top bar */}
= 1 ? 1 : 0.4} transition="opacity 0.5s" />
{/* Skeleton lines */}
= 0}> = 0} delay={0.1}> = 1} delay={0.15}> = 1} delay={0.2}> = 2} delay={0.25}> = 2} delay={0.3}> = 3} delay={0.35}>
{/* Shimmer */}
{/* Label */}
Building interface {tokens > 0 && ( ~{tokens.toLocaleString()} tokens )}
); } // --- Primitives --- function Dot() { return (
); } function Spacer() { return
; } function Bar({ w, h, bg, anim, opacity, transition, }: { w: number; h: number; bg: string; anim?: number; opacity?: number; transition?: string; }) { return (
); } function Row({ children, show, delay = 0, }: { children: React.ReactNode; show: boolean; delay?: number; }) { return (
{children}
); } // --- Hook entry point --- /** * Registers the built-in `render_a2ui` tool call renderer via the props-based * `setRenderToolCalls` mechanism (not `useRenderTool`). * * This ensures user-registered `useRenderTool({ name: "render_a2ui", ... })` * hooks automatically override the built-in, since the merge logic in * react-core.ts gives hook-based entries priority over prop-based entries. */ export function A2UIBuiltInToolCallRenderer(): null { const { copilotkit } = useCopilotKit(); useEffect(() => { const renderer = defineToolCallRenderer({ name: RENDER_A2UI_TOOL_NAME, args: z.any(), render: ({ status, args: parameters }) => { if (status === "complete") return <>; const params = parameters as any; // Hide skeleton once the A2UI surface has enough data to render. // For data-bound surfaces: items array is populated. // For dashboard-style surfaces: components array has multiple entries // (meaning the streaming path is emitting activity snapshots). const items = params?.items; if (Array.isArray(items) && items.length > 0) return <>; const components = params?.components; if (Array.isArray(components) && components.length > 2) return <>; return ; }, }); // Register via props-based mechanism so useRenderTool hooks take priority const existing = (copilotkit as any)._renderToolCalls ?? []; copilotkit.setRenderToolCalls([ ...existing.filter((rc: any) => rc.name !== RENDER_A2UI_TOOL_NAME), renderer, ]); }, [copilotkit]); return null; }