import type { UIMessage as Message } from "ai"; import { getToolName, isToolUIPart } from "ai"; import "./styles.css"; import { useAgentChat } from "@cloudflare/ai-chat/react"; import { useAgent } from "agents/react"; import { useCallback, useEffect, useRef, useState } from "react"; import { executeGetLocalTime } from "./utils"; export default function Chat() { const [theme, setTheme] = useState<"dark" | "light">("dark"); const [showMetadata, setShowMetadata] = useState(true); const [lastResponseTime, setLastResponseTime] = useState(null); const messagesEndRef = useRef(null); const scrollToBottom = useCallback(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, []); useEffect(() => { document.documentElement.setAttribute("data-theme", theme); }, [theme]); useEffect(() => { scrollToBottom(); }, [scrollToBottom]); const toggleTheme = () => { const newTheme = theme === "dark" ? "light" : "dark"; setTheme(newTheme); document.documentElement.setAttribute("data-theme", newTheme); }; const agent = useAgent({ agent: "human-in-the-loop" }); const { messages, sendMessage, addToolApprovalResponse, clearHistory } = useAgentChat({ agent, // Handle tools that need client-side execution (no server execute function). // The LLM calls the tool, the server streams tool-input-available, and // this callback fires with the tool call details. onToolCall: async ({ toolCall, addToolOutput: provideOutput }) => { if (toolCall.toolName === "getLocalTime") { const result = await executeGetLocalTime( toolCall.input as { location: string } ); provideOutput({ toolCallId: toolCall.toolCallId, output: result }); } } }); const [input, setInput] = useState(""); const handleSubmit = useCallback( (e: React.FormEvent) => { e.preventDefault(); if (input.trim()) { const startTime = Date.now(); sendMessage({ role: "user", parts: [{ type: "text", text: input }] }); setInput(""); setTimeout(() => { setLastResponseTime(Date.now() - startTime); }, 1000); } }, [input, sendMessage] ); const handleInputChange = (e: React.ChangeEvent) => { setInput(e.target.value); }; useEffect(() => { messages.length > 0 && scrollToBottom(); }, [messages, scrollToBottom]); // Check if there are pending tool approvals const pendingApproval = messages.some((m: Message) => m.parts?.some( (part) => isToolUIPart(part) && "approval" in part && (part.approval as { id?: string })?.id && part.state === "approval-requested" ) ); return ( <>
{showMetadata && (

Response Metadata

Model: gpt-4o
Messages: {messages.length}
Human-in-Loop: Enabled (needsApproval)
Session ID: {agent.id || "Active"}
{lastResponseTime && (
Last Response: {lastResponseTime}ms
)}
)}
{messages?.map((m: Message) => (
{`${m.role}: `} {m.parts?.map((part, i) => { switch (part.type) { case "text": return (
{part.text}
); default: if (isToolUIPart(part)) { const toolCallId = part.toolCallId; const toolName = getToolName(part); // Tool completed — show result if (part.state === "output-available") { return (
{toolName}{" "} returned:{" "} {JSON.stringify(part.output, null, 2)}
); } // Tool needs approval (needsApproval tools) if ( "approval" in part && part.state === "approval-requested" ) { const approvalId = (part.approval as { id?: string }) ?.id; return (
Run {toolName}{" "} with args:{" "} {JSON.stringify(part.input)}
); } // Tool waiting for client execution (onToolCall handles it) if (part.state === "input-available") { return (
{toolName}{" "} executing...
); } // Tool streaming input if (part.state === "input-streaming") { return (
{toolName}{" "} preparing...
); } } return null; } })}
))}
); }