import { Button } from "@openstatus/ui/components/ui/button"; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@openstatus/ui/components/ui/collapsible"; import { Tabs, TabsList, TabsTrigger } from "@openstatus/ui/components/ui/tabs"; import { cn } from "@openstatus/ui/lib/utils"; import { type DynamicToolUIPart, type ToolUIPart, type UITools, getToolName, } from "ai"; import { BracesIcon, ChevronDownIcon, TableIcon } from "lucide-react"; import { type KeyboardEvent, useEffect, useRef, useState } from "react"; import { ChangesTable } from "@/components/common/changes-table"; import { useChatTool } from "./chat-tool-context"; import { renderToolDraft, renderToolResult, summarizeToolOutput, } from "./tool-renderers"; // SDK tool-part state machine: input-streaming/-available → in-flight; // approval-requested → HITL gate; approval-responded → about to execute; // output-available/-error/-denied → terminal. type ToolState = NonNullable; type Props = { part: ToolUIPart | DynamicToolUIPart; }; export function ChatToolPart({ part }: Props) { const toolName = getToolName(part); const { confirmTool, cancelTool } = useChatTool(); if (part.state === "approval-requested" && part.approval?.id) { return ( ); } return ; } function ApprovalCard({ approvalId, toolName, input, confirmTool, cancelTool, }: { approvalId: string; toolName: string; input: unknown; confirmTool: (id: string) => void; cancelTool: (id: string, reason?: string) => void; }) { // Scope key handling to the card so batched approvals / popovers don't share a window listener. const cardRef = useRef(null); useEffect(() => { cardRef.current?.focus(); }, []); const onKeyDown = (e: KeyboardEvent) => { if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); confirmTool(approvalId); } else if (e.key === "Escape") { e.preventDefault(); cancelTool(approvalId); } }; const draft = renderToolDraft(toolName, input); return (
{toolName}
{draft ? ( ) : (
            {JSON.stringify(input, null, 2)}
          
)}
Esc{" "} to cancel ·{" "} ⌘ ↵{" "} to apply
); } function ToolDisclosure({ part, toolName, }: { part: ToolUIPart | DynamicToolUIPart; toolName: string; }) { const state: ToolState = part.state ?? "output-available"; const rich = renderToolResult(toolName, part.input, part.output); const summary = summarizeToolOutput(toolName, part.output) ?? (state === "output-denied" ? "Cancelled" : undefined); const [open, setOpen] = useState(false); // Gate the height animation behind a post-mount flag so a tool that mounts // already-open doesn't play the open animation on first paint. const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); }, []); const [view, setView] = useState<"rich" | "raw">("rich"); const showRich = rich !== undefined && view === "rich"; return (
{ if (e.target !== e.currentTarget) return; if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setOpen((o) => !o); } }} className="flex w-full cursor-pointer items-center gap-2 rounded-xl p-3 text-left text-sm" > {toolName} {summary ? `· ${summary}` : null} {rich !== undefined ? (
e.stopPropagation()} onKeyDown={(e) => e.stopPropagation()} className={cn(!open && "invisible")} aria-hidden={!open} > setView(v as "rich" | "raw")} >
) : null}
{showRich ? ( rich ) : ( <> {part.input !== undefined ? ( ) : null} {part.output !== undefined ? ( ) : null} )} {part.state === "output-error" && part.errorText ? (
{part.errorText}
) : null} {state === "output-denied" && part.approval?.reason ? (
{part.approval.reason}
) : null}
); } function ToolPanel({ label, body }: { label: string; body: unknown }) { return (

{label}

        {typeof body === "string" ? body : JSON.stringify(body, null, 2)}
      
); } function ToolStateDot({ state }: { state: ToolState }) { const isLoading = state === "input-streaming" || state === "input-available" || state === "approval-responded"; const color = state === "output-error" ? "bg-destructive" : state === "output-denied" ? "bg-muted-foreground" : state === "output-available" ? "bg-success" : state === "approval-requested" ? "bg-warning" : "bg-muted-foreground"; return ( {isLoading ? ( ) : null} ); }