From 3742140ecca14b563762ce4e12ffdd4dd15d3dd3 Mon Sep 17 00:00:00 2001 From: mlogclub Date: Wed, 15 Apr 2026 16:48:15 +0800 Subject: [PATCH] feat: add JsonTreeViewer component and refactor AgentRunLogDetailDialog to use it for JSON display --- .../agent-run-logs/_components/detail.tsx | 53 ++++++++++----- web/components/json-tree-viewer.tsx | 67 +++++++++++++++++++ web/package.json | 1 + web/pnpm-lock.yaml | 16 +++++ 4 files changed, 119 insertions(+), 18 deletions(-) create mode 100644 web/components/json-tree-viewer.tsx diff --git a/web/app/(console)/agent-run-logs/_components/detail.tsx b/web/app/(console)/agent-run-logs/_components/detail.tsx index 1105bf3..8ddc54a 100644 --- a/web/app/(console)/agent-run-logs/_components/detail.tsx +++ b/web/app/(console)/agent-run-logs/_components/detail.tsx @@ -5,6 +5,7 @@ import { BotMessageSquareIcon, WorkflowIcon } from "lucide-react" import { toast } from "sonner" import { ImMessageHTML } from "@/components/im-message-html" +import { JsonTreeViewer } from "@/components/json-tree-viewer" import { ProjectDialog } from "@/components/project-dialog" import { Button } from "@/components/ui/button" import { fetchAgentRunLog, type AgentRunLog } from "@/lib/api/admin" @@ -149,21 +150,15 @@ export function AgentRunLogDetailDialog({ ]} /> - - } @@ -177,13 +172,10 @@ export function AgentRunLogDetailDialog({ value={activeLog.replyText} /> - ) : ( @@ -283,6 +275,31 @@ function TextBlock({ ) } +function JsonBlock({ + title, + jsonValue, + fallbackValue, +}: { + title: string + jsonValue: unknown + fallbackValue?: string +}) { + const normalizedFallback = fallbackValue?.trim() || "" + + return ( +
+
{title}
+ {jsonValue ? ( + + ) : ( +
+ {normalizedFallback || "-"} +
+ )} +
+ ) +} + function sanitizeRichHTML(value: string) { if (typeof window === "undefined") { return value diff --git a/web/components/json-tree-viewer.tsx b/web/components/json-tree-viewer.tsx new file mode 100644 index 0000000..33b9881 --- /dev/null +++ b/web/components/json-tree-viewer.tsx @@ -0,0 +1,67 @@ +"use client" + +import type { CSSProperties } from "react" +import JsonView from "@uiw/react-json-view" + +import { cn } from "@/lib/utils" + +type JsonTreeViewerProps = { + value: unknown + emptyText?: string + className?: string + collapsed?: boolean | number +} + +const viewerTheme = { + "--w-rjv-font-family": + 'ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", Menlo, monospace', + "--w-rjv-background-color": "transparent", + "--w-rjv-color": "hsl(var(--foreground))", + "--w-rjv-line-color": "hsl(var(--border))", + "--w-rjv-arrow-color": "hsl(var(--muted-foreground))", + "--w-rjv-info-color": "hsl(var(--muted-foreground))", + "--w-rjv-curlybraces-color": "hsl(var(--foreground))", + "--w-rjv-brackets-color": "hsl(var(--foreground))", + "--w-rjv-colon-color": "hsl(var(--muted-foreground))", + "--w-rjv-ellipsis-color": "hsl(var(--muted-foreground))", + "--w-rjv-key-string": "hsl(199 89% 58%)", + "--w-rjv-type-string-color": "hsl(142 76% 45%)", + "--w-rjv-type-int-color": "hsl(262 83% 70%)", + "--w-rjv-type-float-color": "hsl(262 83% 70%)", + "--w-rjv-type-boolean-color": "hsl(38 92% 50%)", + "--w-rjv-type-null-color": "hsl(347 77% 50%)", +} as CSSProperties + +export function JsonTreeViewer({ + value, + emptyText = "暂无数据", + className, + collapsed = 2, +}: JsonTreeViewerProps) { + if (value == null || value === "") { + return ( +
+ {emptyText} +
+ ) + } + + return ( +
+ } + collapsed={collapsed} + displayDataTypes={false} + displayObjectSize + enableClipboard + shortenTextAfterLength={120} + style={viewerTheme} + /> +
+ ) +} diff --git a/web/package.json b/web/package.json index dd356af..b1f5e18 100644 --- a/web/package.json +++ b/web/package.json @@ -23,6 +23,7 @@ "@tiptap/extension-underline": "3.20.2", "@tiptap/react": "^3.20.2", "@tiptap/starter-kit": "^3.20.2", + "@uiw/react-json-view": "2.0.0-alpha.41", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index dcf8d53..16bff45 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -47,6 +47,9 @@ importers: '@tiptap/starter-kit': specifier: ^3.20.2 version: 3.20.2 + '@uiw/react-json-view': + specifier: 2.0.0-alpha.41 + version: 2.0.0-alpha.41(@babel/runtime@7.28.6)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -1564,6 +1567,13 @@ packages: resolution: {integrity: sha512-zm6xx8UT/Xy2oSr2ZXD0pZo7Jx2XsCoID2IUh9YSTFRu7z+WdwYTRk6LhUftm1crwqbuoF6I8zAFeCMw0YjwDg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@uiw/react-json-view@2.0.0-alpha.41': + resolution: {integrity: sha512-botRpQ5AgymYEsqXSdT2/1LefAJEYfMntvdnx1SqhTQCTW9HygeFZXx9inkYqUmiQZ3+0QlZnodjBvwnUfZhVA==} + peerDependencies: + '@babel/runtime': '>=7.10.0' + react: '>=18.0.0' + react-dom: '>=18.0.0' + '@unrs/resolver-binding-android-arm-eabi@1.11.1': resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} cpu: [arm] @@ -5726,6 +5736,12 @@ snapshots: '@typescript-eslint/types': 8.57.0 eslint-visitor-keys: 5.0.1 + '@uiw/react-json-view@2.0.0-alpha.41(@babel/runtime@7.28.6)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@babel/runtime': 7.28.6 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + '@unrs/resolver-binding-android-arm-eabi@1.11.1': optional: true