UNPKG

@copilotkit/react-core

Version:

<div align="center"> <a href="https://copilotkit.ai" target="_blank"> <img src="https://github.com/copilotkit/copilotkit/raw/main/assets/banner.png" alt="CopilotKit Logo"> </a>

150 lines (134 loc) 5.34 kB
/** * The useCoAgentStateRender hook allows you to render UI or text based components on a Agentic Copilot's state in the chat. * This is particularly useful for showing intermediate state or progress during Agentic Copilot operations. * * ## Usage * * ### Simple Usage * * ```tsx * import { useCoAgentStateRender } from "@copilotkit/react-core"; * * type YourAgentState = { * agent_state_property: string; * } * * useCoAgentStateRender<YourAgentState>({ * name: "basic_agent", * nodeName: "optionally_specify_a_specific_node", * render: ({ status, state, nodeName }) => { * return ( * <YourComponent * agentStateProperty={state.agent_state_property} * status={status} * nodeName={nodeName} * /> * ); * }, * }); * ``` * * This allows for you to render UI components or text based on what is happening within the agent. * * ### Example * A great example of this is in our Perplexity Clone where we render the progress of an agent's internet search as it is happening. * You can play around with it below or learn how to build it with its [demo](/coagents/videos/perplexity-clone). * * <Callout type="info"> * This example is hosted on Vercel and may take a few seconds to load. * </Callout> * * <iframe src="https://examples-coagents-ai-researcher-ui.vercel.app/" className="w-full rounded-lg border h-[700px] my-4" /> */ import { useRef, useContext, useEffect } from "react"; import { CopilotContext } from "../context/copilot-context"; import { randomId } from "@copilotkit/shared"; import { CoAgentStateRender } from "../types/coagent-action"; import { useToast } from "../components/toast/toast-provider"; /** * This hook is used to render agent state with custom UI components or text. This is particularly * useful for showing intermediate state or progress during Agentic Copilot operations. * To get started using rendering intermediate state through this hook, checkout the documentation. * * https://docs.copilotkit.ai/coagents/shared-state/predictive-state-updates */ // We implement useCoAgentStateRender dependency handling so that // the developer has the option to not provide any dependencies. // see useCopilotAction for more details about this approach. export function useCoAgentStateRender<T = any>( action: CoAgentStateRender<T>, dependencies?: any[], ): void { const { setCoAgentStateRender, removeCoAgentStateRender, coAgentStateRenders, chatComponentsCache, availableAgents, } = useContext(CopilotContext); const idRef = useRef<string>(randomId()); const { addToast } = useToast(); useEffect(() => { if (availableAgents?.length && !availableAgents.some((a) => a.name === action.name)) { const message = `(useCoAgentStateRender): Agent "${action.name}" not found. Make sure the agent exists and is properly configured.`; addToast({ type: "warning", message }); } }, [availableAgents]); const key = `${action.name}-${action.nodeName || "global"}`; if (dependencies === undefined) { if (coAgentStateRenders[idRef.current]) { coAgentStateRenders[idRef.current].handler = action.handler as any; if (typeof action.render === "function") { if (chatComponentsCache.current !== null) { chatComponentsCache.current.coAgentStateRenders[key] = action.render; } } } } useEffect(() => { // Check for duplicates by comparing against all other actions const currentId = idRef.current; const hasDuplicate = Object.entries(coAgentStateRenders).some(([id, otherAction]) => { // Skip comparing with self if (id === currentId) return false; // Different agent names are never duplicates if (otherAction.name !== action.name) return false; // Same agent names: const hasNodeName = !!action.nodeName; const hasOtherNodeName = !!otherAction.nodeName; // If neither has nodeName, they're duplicates if (!hasNodeName && !hasOtherNodeName) return true; // If one has nodeName and other doesn't, they're not duplicates if (hasNodeName !== hasOtherNodeName) return false; // If both have nodeName, they're duplicates only if the names match return action.nodeName === otherAction.nodeName; }); if (hasDuplicate) { const message = action.nodeName ? `Found multiple state renders for agent ${action.name} and node ${action.nodeName}. State renders might get overridden` : `Found multiple state renders for agent ${action.name}. State renders might get overridden`; addToast({ type: "warning", message, id: `dup-action-${action.name}`, }); } }, [coAgentStateRenders]); useEffect(() => { setCoAgentStateRender(idRef.current, action as any); if (chatComponentsCache.current !== null && action.render !== undefined) { chatComponentsCache.current.coAgentStateRenders[key] = action.render; } return () => { removeCoAgentStateRender(idRef.current); }; }, [ setCoAgentStateRender, removeCoAgentStateRender, action.name, // include render only if it's a string typeof action.render === "string" ? action.render : undefined, // dependencies set by the developer ...(dependencies || []), ]); }