import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/react/20/solid"; import { Clipboard, ClipboardCheck } from "lucide-react"; import { Suspense, useEffect, useState } from "react"; import { CodeSquareIcon } from "~/assets/icons/CodeSquareIcon"; import { TextSquareIcon } from "~/assets/icons/TextSquareIcon"; import { CodeBlock } from "~/components/code/CodeBlock"; import { StreamdownRenderer } from "~/components/code/StreamdownRenderer"; import { Button, LinkButton } from "~/components/primitives/Buttons"; import { Header3 } from "~/components/primitives/Headers"; import tablerSpritePath from "~/components/primitives/tabler-sprite.svg"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "~/components/primitives/Tooltip"; import { cn } from "~/utils/cn"; import type { DisplayItem, ToolUse } from "./types"; export type PromptLink = { slug: string; version?: string; path: string; }; export function AIChatMessages({ items, promptLink, }: { items: DisplayItem[]; promptLink?: PromptLink; }) { return (
{items.map((item, i) => { switch (item.type) { case "system": return ; case "user": return ; case "tool-use": return ; case "assistant": return ; } })}
); } // --------------------------------------------------------------------------- // Section header (shared across all sections) // --------------------------------------------------------------------------- function SectionHeader({ label, right }: { label: string; right?: React.ReactNode }) { return (
{label} {right &&
{right}
}
); } export function ChatBubble({ children }: { children: React.ReactNode }) { return (
{children}
); } // --------------------------------------------------------------------------- // System // --------------------------------------------------------------------------- function SystemSection({ text, promptLink }: { text: string; promptLink?: PromptLink }) { const [expanded, setExpanded] = useState(false); const isLong = text.length > 150; const preview = isLong ? text.slice(0, 150) + "..." : text; const displayText = expanded || !isLong ? text : preview; return (
System {promptLink && ( {promptLink.slug} {promptLink.version ? ` v${promptLink.version}` : ""} )}
{isLong && (
{displayText}}> {displayText}
); } // --------------------------------------------------------------------------- // User // --------------------------------------------------------------------------- function UserSection({ text }: { text: string }) { return (
{text}}> {text}
); } // --------------------------------------------------------------------------- // Assistant response (with markdown/raw toggle) // --------------------------------------------------------------------------- function isJsonString(value: string): boolean { const trimmed = value.trimStart(); if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return false; try { JSON.parse(value); return true; } catch { return false; } } export function AssistantResponse({ text, headerLabel = "Assistant", }: { text: string; headerLabel?: string; }) { const isJson = isJsonString(text); const [mode, setMode] = useState<"rendered" | "raw">("rendered"); const [copied, setCopied] = useState(false); function handleCopy() { navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 2000); } if (isJson) { return (
); } return (
setMode(mode === "rendered" ? "raw" : "rendered")} className="text-text-dimmed transition-colors duration-100 focus-custom hover:cursor-pointer hover:text-text-bright" > {mode === "rendered" ? ( ) : ( )} {mode === "rendered" ? "Show raw" : "Show rendered"} {copied ? ( ) : ( )} {copied ? "Copied" : "Copy"}
} /> {mode === "rendered" ? (
{text}}> {text}
) : ( )} ); } // --------------------------------------------------------------------------- // Tool use (merged calls + results) // --------------------------------------------------------------------------- function ToolUseSection({ tools }: { tools: ToolUse[] }) { return (
{tools.map((tool) => ( ))}
); } type ToolTab = "input" | "output" | "details" | "agent"; export function ToolUseRow({ tool }: { tool: ToolUse }) { const hasInput = tool.inputJson !== "{}"; const hasResult = !!tool.resultOutput; const hasDetails = !!tool.description || !!tool.parametersJson; const hasSubAgent = !!tool.subAgent; const availableTabs: ToolTab[] = [ ...(hasSubAgent ? (["agent"] as const) : []), ...(hasInput ? (["input"] as const) : []), ...(hasResult ? (["output"] as const) : []), ...(hasDetails ? (["details"] as const) : []), ]; const [activeTab, setActiveTab] = useState( hasSubAgent ? "agent" : hasInput ? "input" : null ); // Auto-select input tab when input arrives after initial render (e.g. streaming tool calls) useEffect(() => { if (!hasSubAgent && hasInput && activeTab === null) { setActiveTab("input"); } }, [hasInput, hasSubAgent]); function handleTabClick(tab: ToolTab) { setActiveTab(activeTab === tab ? null : tab); } return (
{hasSubAgent && ( )} {tool.toolName} {hasSubAgent && tool.subAgent?.isStreaming && ( streaming )} {tool.resultSummary && ( {tool.resultSummary} )}
{availableTabs.length > 0 && ( <>
{availableTabs.map((tab) => ( ))}
{activeTab === "agent" && hasSubAgent && } {activeTab === "input" && hasInput && (
)} {activeTab === "output" && hasResult && (
{isJsonString(tool.resultOutput!) ? ( ) : (
{tool.resultOutput}} > {tool.resultOutput!}
)}
)} {activeTab === "details" && hasDetails && (
{tool.description && (

{tool.description}

)} {tool.parametersJson && (
Parameters schema
)}
)} )}
); } function SubAgentContent({ parts }: { parts: any[] }) { // Extract sub-agent run ID from injected metadata part const runPart = parts.find((p: any) => p.type === "data-subagent-run" && p.data?.runId); const subAgentRunId = runPart?.data?.runId as string | undefined; return (
{subAgentRunId && (
View sub-agent run
)} {parts.map((part: any, j: number) => { const partType = part.type as string; // Skip the injected metadata part — already rendered above if (partType === "data-subagent-run") return null; if (partType === "text" && part.text) { return ; } if (partType === "step-start") { return (
step
); } if (partType.startsWith("tool-")) { const subToolName = partType.slice(5); return ( ); } if (partType === "reasoning" && part.text) { return (
{part.text}
); } return null; })}
); }