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