diff --git a/src/components/ai-elements/tool.tsx b/src/components/ai-elements/tool.tsx index de70e83..0b2b7de 100644 --- a/src/components/ai-elements/tool.tsx +++ b/src/components/ai-elements/tool.tsx @@ -18,18 +18,35 @@ import { WrenchIcon, XCircleIcon, } from "lucide-react"; -import { isValidElement } from "react"; +import { createContext, isValidElement, useContext, useState } from "react"; import { CodeBlock } from "./code-block"; +// Context to track if tool content has been opened (for lazy rendering) +const ToolOpenContext = createContext(false); + export type ToolProps = ComponentProps; -export const Tool = ({ className, ...props }: ToolProps) => ( - -); +export const Tool = ({ className, defaultOpen = false, ...props }: ToolProps) => { + // Track if the tool has ever been opened (for lazy content rendering) + const [hasBeenOpened, setHasBeenOpened] = useState(defaultOpen); + + return ( + + { + if (open && !hasBeenOpened) { + setHasBeenOpened(true); + } + props.onOpenChange?.(open); + }} + {...props} + /> + + ); +}; export type ToolPart = ToolUIPart | DynamicToolUIPart; @@ -103,15 +120,22 @@ export const ToolHeader = ({ export type ToolContentProps = ComponentProps; -export const ToolContent = ({ className, ...props }: ToolContentProps) => ( - -); +export const ToolContent = ({ className, children, ...props }: ToolContentProps) => { + // Only render children if the tool has been opened at least once + const hasBeenOpened = useContext(ToolOpenContext); + + return ( + + {hasBeenOpened ? children : null} + + ); +}; export type ToolInputProps = ComponentProps<"div"> & { input: ToolPart["input"]; diff --git a/src/components/chat/MessageItem.tsx b/src/components/chat/MessageItem.tsx index c2121fd..1e4de98 100644 --- a/src/components/chat/MessageItem.tsx +++ b/src/components/chat/MessageItem.tsx @@ -33,8 +33,50 @@ interface ToolBlock { function parseToolBlocks(content: string): { text: string; tools: ToolBlock[] } { const tools: ToolBlock[] = []; - let text = content; + let text = ''; + // Try to parse as JSON array (new format from chat API) + if (content.startsWith('[')) { + try { + const blocks = JSON.parse(content) as Array<{ + type: string; + text?: string; + id?: string; + name?: string; + input?: unknown; + tool_use_id?: string; + content?: string; + is_error?: boolean; + }>; + + for (const block of blocks) { + if (block.type === 'text' && block.text) { + text += block.text; + } else if (block.type === 'tool_use') { + tools.push({ + type: 'tool_use', + id: block.id, + name: block.name, + input: block.input, + }); + } else if (block.type === 'tool_result') { + tools.push({ + type: 'tool_result', + id: block.tool_use_id, + content: block.content, + is_error: block.is_error, + }); + } + } + + return { text: text.trim(), tools }; + } catch { + // Not valid JSON, fall through to legacy parsing + } + } + + // Legacy format: HTML comments + text = content; const toolUseRegex = //g; let match; while ((match = toolUseRegex.exec(content)) !== null) {