feat: add JsonTreeViewer component and refactor AgentRunLogDetailDialog to use it for JSON display
This commit is contained in:
@@ -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({
|
||||
]}
|
||||
/>
|
||||
|
||||
<TextBlock
|
||||
<JsonBlock
|
||||
title="动态工具选择"
|
||||
value={
|
||||
activeToolSearchTrace
|
||||
? JSON.stringify(activeToolSearchTrace, null, 2)
|
||||
: activeLog.toolSearchTrace
|
||||
}
|
||||
jsonValue={activeToolSearchTrace}
|
||||
fallbackValue={activeLog.toolSearchTrace}
|
||||
/>
|
||||
<TextBlock
|
||||
<JsonBlock
|
||||
title="Graph Tool 调用"
|
||||
value={
|
||||
activeGraphToolTrace
|
||||
? JSON.stringify(activeGraphToolTrace, null, 2)
|
||||
: activeLog.graphToolTrace
|
||||
}
|
||||
jsonValue={activeGraphToolTrace}
|
||||
fallbackValue={activeLog.graphToolTrace}
|
||||
/>
|
||||
<TextBlock
|
||||
icon={<BotMessageSquareIcon className="size-4" />}
|
||||
@@ -177,13 +172,10 @@ export function AgentRunLogDetailDialog({
|
||||
value={activeLog.replyText}
|
||||
/>
|
||||
<TextBlock title="错误信息" value={activeLog.errorMessage} tone="danger" />
|
||||
<TextBlock
|
||||
<JsonBlock
|
||||
title="链路 Trace"
|
||||
value={
|
||||
activeTraceData
|
||||
? JSON.stringify(activeTraceData, null, 2)
|
||||
: activeLog.traceData
|
||||
}
|
||||
jsonValue={activeTraceData}
|
||||
fallbackValue={activeLog.traceData}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
@@ -283,6 +275,31 @@ function TextBlock({
|
||||
)
|
||||
}
|
||||
|
||||
function JsonBlock({
|
||||
title,
|
||||
jsonValue,
|
||||
fallbackValue,
|
||||
}: {
|
||||
title: string
|
||||
jsonValue: unknown
|
||||
fallbackValue?: string
|
||||
}) {
|
||||
const normalizedFallback = fallbackValue?.trim() || ""
|
||||
|
||||
return (
|
||||
<div className="rounded-lg border p-4">
|
||||
<div className="text-sm font-medium">{title}</div>
|
||||
{jsonValue ? (
|
||||
<JsonTreeViewer value={jsonValue} className="mt-3" />
|
||||
) : (
|
||||
<div className="mt-3 select-text whitespace-pre-wrap wrap-break-word text-sm text-muted-foreground">
|
||||
{normalizedFallback || "-"}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function sanitizeRichHTML(value: string) {
|
||||
if (typeof window === "undefined") {
|
||||
return value
|
||||
|
||||
@@ -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 (
|
||||
<div
|
||||
className={cn(
|
||||
"rounded-md border bg-muted/20 px-4 py-3 font-mono text-xs text-muted-foreground",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{emptyText}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn("rounded-md border bg-muted/20 p-3", className)}>
|
||||
<JsonView
|
||||
value={value as Record<string, unknown>}
|
||||
collapsed={collapsed}
|
||||
displayDataTypes={false}
|
||||
displayObjectSize
|
||||
enableClipboard
|
||||
shortenTextAfterLength={120}
|
||||
style={viewerTheme}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
Generated
+16
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user