From 397c2c1124fc02c32a057e80d8e4ede502253b92 Mon Sep 17 00:00:00 2001 From: gongzhiyong Date: Sat, 11 Apr 2026 21:59:12 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Gen-UI=E5=8D=A1=E7=89=87error=E6=80=81?= =?UTF-8?q?=20+=20ToolCallStatus=E5=A4=B1=E8=B4=A5=E7=8A=B6=E6=80=81=20+?= =?UTF-8?q?=20=E9=AA=A8=E6=9E=B6=E5=B1=8Floading?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 4个Gen-UI卡片增加errorMessage prop,错误时显示红色提示块,空结果时边框变红 - ToolCallStatus新增failedToolIds prop,失败时显示XCircle红色图标+失败文案 - main.tsx从tool messages status===error派生failedToolIds并传入ToolCallStatus - ToolCallRow展开无uiItem时显示animate-pulse骨架屏占位 - LoadExternalComponent外包animate-in fade-in过渡动画 Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../enterprise/knowledge-result/index.tsx | 14 ++++++-- .../enterprise/sandbox-result/index.tsx | 14 ++++++-- .../enterprise/search-result/index.tsx | 14 ++++++-- .../enterprise/ticket-summary/index.tsx | 14 ++++++-- langgraph/src/components/ToolCallStatus.tsx | 34 ++++++++++++++----- langgraph/src/main.tsx | 9 +++++ 6 files changed, 83 insertions(+), 16 deletions(-) diff --git a/langgraph/src/agent-uis/enterprise/knowledge-result/index.tsx b/langgraph/src/agent-uis/enterprise/knowledge-result/index.tsx index 2b1d597..28b9c6b 100644 --- a/langgraph/src/agent-uis/enterprise/knowledge-result/index.tsx +++ b/langgraph/src/agent-uis/enterprise/knowledge-result/index.tsx @@ -1,4 +1,4 @@ -import { BookOpen } from "lucide-react"; +import { BookOpen, AlertCircle } from "lucide-react"; import { ActionBar } from "@/components/ActionBar"; import { SourceBadge } from "@/components/SourceBadge"; @@ -8,6 +8,7 @@ interface KnowledgeResultProps { results: Array<{ title: string; category: string; snippet: string }>; sourceType?: string; confidence?: "high" | "medium" | "low"; + errorMessage?: string; } export default function KnowledgeResult({ @@ -16,9 +17,10 @@ export default function KnowledgeResult({ results, sourceType, confidence, + errorMessage, }: KnowledgeResultProps) { return ( -
+
{/* Header */}
@@ -36,6 +38,14 @@ export default function KnowledgeResult({

+ {/* Error */} + {errorMessage && ( +
+ +

{errorMessage}

+
+ )} + {/* Results */}
    {results.length === 0 ? ( diff --git a/langgraph/src/agent-uis/enterprise/sandbox-result/index.tsx b/langgraph/src/agent-uis/enterprise/sandbox-result/index.tsx index d4e38fb..1a9a75e 100644 --- a/langgraph/src/agent-uis/enterprise/sandbox-result/index.tsx +++ b/langgraph/src/agent-uis/enterprise/sandbox-result/index.tsx @@ -1,4 +1,4 @@ -import { Terminal, CheckCircle, XCircle } from "lucide-react"; +import { Terminal, CheckCircle, XCircle, AlertCircle } from "lucide-react"; import { cn } from "@/lib/utils"; import { ActionBar } from "@/components/ActionBar"; import { SourceBadge } from "@/components/SourceBadge"; @@ -11,6 +11,7 @@ interface SandboxResultProps { duration_ms?: number; sourceType?: string; confidence?: "high" | "medium" | "low"; + errorMessage?: string; } const LANGUAGE_LABEL: Record = { @@ -27,11 +28,12 @@ export default function SandboxResult({ duration_ms, sourceType, confidence, + errorMessage, }: SandboxResultProps) { const success = exit_code === 0; return ( -
    +
    {/* Header */}
    @@ -68,6 +70,14 @@ export default function SandboxResult({ )}
    + {/* Error */} + {errorMessage && ( +
    + +

    {errorMessage}

    +
    + )} + {/* Output */}
    ;
       sourceType?: string;
       confidence?: "high" | "medium" | "low";
    +  errorMessage?: string;
     }
     
     function getDomain(url: string): string {
    @@ -24,9 +25,10 @@ export default function SearchResult({
       results,
       sourceType,
       confidence,
    +  errorMessage,
     }: SearchResultProps) {
       return (
    -    
    +
    {/* Header */}
    @@ -44,6 +46,14 @@ export default function SearchResult({

    + {/* Error */} + {errorMessage && ( +
    + +

    {errorMessage}

    +
    + )} + {/* Results */}
      {results.length === 0 ? ( diff --git a/langgraph/src/agent-uis/enterprise/ticket-summary/index.tsx b/langgraph/src/agent-uis/enterprise/ticket-summary/index.tsx index 5e2193d..8047f0e 100644 --- a/langgraph/src/agent-uis/enterprise/ticket-summary/index.tsx +++ b/langgraph/src/agent-uis/enterprise/ticket-summary/index.tsx @@ -1,4 +1,4 @@ -import { TicketIcon } from "lucide-react"; +import { TicketIcon, AlertCircle } from "lucide-react"; import { cn } from "@/lib/utils"; import { ActionBar } from "@/components/ActionBar"; import { SourceBadge } from "@/components/SourceBadge"; @@ -16,6 +16,7 @@ interface TicketSummaryProps { stats: Record; sourceType?: string; confidence?: "high" | "medium" | "low"; + errorMessage?: string; } function priorityClass(priority: string): string { @@ -54,9 +55,10 @@ export default function TicketSummary({ stats, sourceType, confidence, + errorMessage, }: TicketSummaryProps) { return ( -
      +
      {/* Header */}
      @@ -84,6 +86,14 @@ export default function TicketSummary({
      )} + {/* Error */} + {errorMessage && ( +
      + +

      {errorMessage}

      +
      + )} + {/* Ticket list */}
        {tickets.length === 0 ? ( diff --git a/langgraph/src/components/ToolCallStatus.tsx b/langgraph/src/components/ToolCallStatus.tsx index cb5c14a..0653068 100644 --- a/langgraph/src/components/ToolCallStatus.tsx +++ b/langgraph/src/components/ToolCallStatus.tsx @@ -1,4 +1,4 @@ -import { Loader2, CheckCircle2, ChevronRight, ChevronDown } from "lucide-react"; +import { Loader2, CheckCircle2, ChevronRight, ChevronDown, XCircle } from "lucide-react"; import { useState } from "react"; import { LoadExternalComponent } from "@langchain/langgraph-sdk/react-ui"; @@ -52,6 +52,7 @@ interface ToolCallStatusProps { toolCalls: ToolCall[]; isLoading: boolean; completedToolIds?: Set; + failedToolIds?: Set; uiItems?: UIMsgLocal[]; stream?: Parameters[0]["stream"]; components?: Parameters[0]["components"]; @@ -60,12 +61,14 @@ interface ToolCallStatusProps { function ToolCallRow({ tc, isDone, + isFailed, uiItem, stream, components, }: { tc: ToolCall; isDone: boolean; + isFailed: boolean; uiItem?: UIMsgLocal; stream?: ToolCallStatusProps["stream"]; components?: ToolCallStatusProps["components"]; @@ -91,21 +94,33 @@ function ToolCallRow({ ) : ( )} - {isDone ? ( + {isFailed ? ( + + ) : isDone ? ( ) : ( )} {label} - {isDone ? (canExpand ? (expanded ? "收起" : "查看结果") : `已完成 · ${label}`) : (tc.name ? (TOOL_LOADING_MAP[tc.name] ?? `正在${label}...`) : "思考中...")} + {isFailed ? `失败 · ${label}` : isDone ? (canExpand ? (expanded ? "收起" : "查看结果") : `已完成 · ${label}`) : (tc.name ? (TOOL_LOADING_MAP[tc.name] ?? `正在${label}...`) : "思考中...")} {expanded && uiItem && stream && components && (
        - +
        + +
        +
        + )} + {expanded && !uiItem && ( +
        +
        +
        +
        +
        )}
        @@ -116,6 +131,7 @@ export default function ToolCallStatus({ toolCalls, isLoading, completedToolIds, + failedToolIds, uiItems = [], stream, components, @@ -139,6 +155,7 @@ export default function ToolCallStatus({
        {toolCalls.map((tc, i) => { const isDone = !isLoading || (tc.id && completedToolIds?.has(tc.id)); + const isFailed = !!(tc.id && failedToolIds?.has(tc.id)); let uiItem: UIMsgLocal | undefined; if (tc.name && UI_NAME_MAP[tc.name]) { const uiName = UI_NAME_MAP[tc.name]; @@ -153,6 +170,7 @@ export default function ToolCallStatus({ key={tc.id ?? i} tc={tc} isDone={!!isDone} + isFailed={isFailed} uiItem={uiItem} stream={stream} components={components} diff --git a/langgraph/src/main.tsx b/langgraph/src/main.tsx index 02d29eb..0848b92 100644 --- a/langgraph/src/main.tsx +++ b/langgraph/src/main.tsx @@ -318,6 +318,14 @@ function App() { .filter(Boolean), ); + // ── Collect failed tool call IDs ───────────────────────────────────────── + const failedToolIds = new Set( + thread.messages + .filter((m) => m.type === "tool" && (m as any).status === "error") + .map((m) => (m as any).tool_call_id as string) + .filter(Boolean), + ); + // ── Find last AI message index ────────────────────────────────────────── const lastAiIdx = thread.messages.reduce((last, m, i) => (m.type === "ai" ? i : last), -1); @@ -490,6 +498,7 @@ function App() { toolCalls={toolCalls} isLoading={thread.isLoading} completedToolIds={completedToolIds} + failedToolIds={failedToolIds} uiItems={uiItems} stream={thread} components={ComponentMap as any}