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;
}