From fc56503e2239247d15c22cc2ef913a9b4c45c028 Mon Sep 17 00:00:00 2001 From: gongzhiyong Date: Sun, 12 Apr 2026 14:13:14 +0800 Subject: [PATCH] feat: ToolCallStatus partial_success/fallback_success visual states - Add CornerDownRight, AlertCircle icons for fallback/partial states - New isPartial/isFallback props on ToolCallRow with hint banners - Read execution_log from thread.values in main.tsx, pass to ToolCallStatus - Match by tc.name === entry.tool to determine partial/fallback per row Co-Authored-By: Claude Sonnet 4.6 (1M context) --- langgraph/src/components/ToolCallStatus.tsx | 71 +++++++++++++++++---- langgraph/src/main.tsx | 4 ++ 2 files changed, 64 insertions(+), 11 deletions(-) diff --git a/langgraph/src/components/ToolCallStatus.tsx b/langgraph/src/components/ToolCallStatus.tsx index f4fbc4d..cb29aab 100644 --- a/langgraph/src/components/ToolCallStatus.tsx +++ b/langgraph/src/components/ToolCallStatus.tsx @@ -1,4 +1,4 @@ -import { Loader2, CheckCircle2, ChevronRight, ChevronDown, XCircle, ChevronsUpDown } from "lucide-react"; +import { Loader2, CheckCircle2, ChevronRight, ChevronDown, XCircle, ChevronsUpDown, AlertCircle, CornerDownRight } from "lucide-react"; import { useState, useCallback } from "react"; import { LoadExternalComponent } from "@langchain/langgraph-sdk/react-ui"; @@ -57,12 +57,15 @@ interface ToolCallStatusProps { uiItems?: UIMsgLocal[]; stream?: Parameters[0]["stream"]; components?: Parameters[0]["components"]; + executionLog?: Array<{ tool: string; status: string; inputSummary?: string }>; } interface ToolCallRowProps { tc: ToolCall; isDone: boolean; isFailed: boolean; + isPartial?: boolean; + isFallback?: boolean; uiItem?: UIMsgLocal; stream?: ToolCallStatusProps["stream"]; components?: ToolCallStatusProps["components"]; @@ -74,6 +77,8 @@ function ToolCallRow({ tc, isDone, isFailed, + isPartial, + isFallback, uiItem, stream, components, @@ -88,7 +93,33 @@ function ToolCallRow({ } }, [globalExpanded]); const label = tc.name ? (TOOL_NAME_MAP[tc.name] ?? tc.name) : "工具调用"; - const canExpand = isDone && (!!uiItem || isFailed); + const canExpand = isDone && (!!uiItem || isFailed || isPartial || isFallback); + + // Determine icon and color by priority: isFailed > isFallback > isPartial > isDone > loading + function renderIcon() { + if (isFailed) return ; + if (isFallback) return ; + if (isPartial) return ; + if (isDone) return ; + return ; + } + + function renderLabel() { + if (isFailed) return `${label} · 失败`; + if (isFallback) { + if (canExpand) return `${label} · 已切换到备用 · ${expanded ? "收起" : "查看结果"}`; + return `${label} · 已切换到备用`; + } + if (isPartial) { + if (canExpand) return `${label} · 结果不完整 · ${expanded ? "收起" : "查看结果"}`; + return `${label} · 结果不完整`; + } + if (isDone) { + if (canExpand) return `${label} · ${expanded ? "收起" : "查看结果"}`; + return `${label} · 已完成`; + } + return tc.name ? (TOOL_LOADING_MAP[tc.name] ?? `正在${label}...`) : "思考中..."; + } return (
@@ -107,14 +138,8 @@ function ToolCallRow({ ) : ( )} - {isFailed ? ( - - ) : isDone ? ( - - ) : ( - - )} - {isFailed ? `${label} · 失败` : isDone ? (canExpand ? `${label} · ${expanded ? "收起" : "查看结果"}` : `${label} · 已完成`) : (tc.name ? (TOOL_LOADING_MAP[tc.name] ?? `正在${label}...`) : "思考中...")} + {renderIcon()} + {renderLabel()} {expanded && isFailed && (
@@ -123,6 +148,16 @@ function ToolCallRow({

)} + {expanded && isPartial && !isFailed && ( +
+ 查询完成,但未找到完整匹配结果 +
+ )} + {expanded && isFallback && !isFailed && ( +
+ 主工具执行失败,已自动切换至备选方案 +
+ )} {expanded && !isFailed && uiItem && stream && components && (
@@ -134,7 +169,13 @@ function ToolCallRow({
)} - {expanded && !isFailed && !uiItem && ( + {expanded && !isFailed && !uiItem && (isPartial || isFallback) && ( +
+
+
+
+ )} + {expanded && !isFailed && !uiItem && !isPartial && !isFallback && (
@@ -154,6 +195,7 @@ export default function ToolCallStatus({ uiItems = [], stream, components, + executionLog = [], }: ToolCallStatusProps) { if (!toolCalls.length) return null; @@ -210,12 +252,19 @@ export default function ToolCallStatus({ uiItem = candidates[matchIdx]; } + // Look up execution_log status by tool name + const logEntry = tc.name ? executionLog.find((e) => e.tool === tc.name) : undefined; + const isPartial = logEntry?.status === "partial_success"; + const isFallbackStatus = logEntry?.status === "fallback_success"; + return ( ; + // ── Find last AI message index ────────────────────────────────────────── const lastAiIdx = thread.messages.reduce((last, m, i) => (m.type === "ai" ? i : last), -1); @@ -624,6 +627,7 @@ function App() { uiItems={uiItems} stream={thread} components={ComponentMap as any} + executionLog={executionLog} /> )}