From f437024a96ddf3b29dee9ccbdca26917975ce5c4 Mon Sep 17 00:00:00 2001 From: llg-linwei Date: Mon, 9 Feb 2026 14:29:58 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E5=A4=A7=E6=B6=88=E6=81=AF=E4=BC=9A=E8=AF=9D=E6=97=B6=E5=8D=A1?= =?UTF-8?q?=E6=AD=BB=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ai-elements/tool.tsx | 56 ++++++++++++++++++++--------- src/components/chat/MessageItem.tsx | 44 ++++++++++++++++++++++- 2 files changed, 83 insertions(+), 17 deletions(-) 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) {