Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 121 additions & 18 deletions web/oss/src/components/DrillInView/BeautifiedJsonView.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {memo, useEffect, useLayoutEffect, useMemo} from "react"
import {memo, useEffect, useLayoutEffect, useMemo, useState} from "react"

import {
Editor as EditorWrapper,
Expand All @@ -12,6 +12,16 @@ import {
ROLE_COLOR_CLASSES,
DEFAULT_ROLE_COLOR_CLASS,
} from "@agenta/ui/cell-renderers"
import {Button} from "antd"

import LargeValuePreview from "./LargeValuePreview"
import {
getPreviewItems,
getRenderStats,
previewValueString,
shouldUsePreview,
type RenderBudgetMode,
} from "./renderBudget"

/**
* "Beautified JSON" view.
Expand Down Expand Up @@ -233,6 +243,39 @@ const getMessageText = (content: unknown): string => {
}
}

const formatToolCall = (toolCall: unknown): string => {
const rec =
toolCall && typeof toolCall === "object" ? (toolCall as Record<string, unknown>) : {}
const fn =
rec.function && typeof rec.function === "object"
? (rec.function as Record<string, unknown>)
: undefined
const name =
(typeof fn?.name === "string" && fn.name) ||
(typeof rec.name === "string" && rec.name) ||
"tool"
const args = fn?.arguments ?? rec.arguments ?? rec.args ?? rec.input

if (args === undefined || args === null || args === "") {
return `${name}()`
}

if (typeof args === "string") {
return `${name}(${previewValueString(args)})`
}

const argStats = getRenderStats(args)
if (shouldUsePreview(argStats, "beautified-json")) {
return `${name}(${previewValueString(args)})`
}

try {
return `${name}(${JSON.stringify(args, null, 2)})`
} catch {
return `${name}(...)`
}
}

const RenderedChatMessages = memo(function RenderedChatMessages({
messages,
keyPrefix,
Expand All @@ -248,31 +291,46 @@ const RenderedChatMessages = memo(function RenderedChatMessages({
const roleColor =
ROLE_COLOR_CLASSES[msg.role.toLowerCase()] ?? DEFAULT_ROLE_COLOR_CLASS
const text = getMessageText(msg.content)
const toolCalls = Array.isArray(msg.tool_calls) ? msg.tool_calls : []
const editorId = `${keyPrefix}-msg-${i}`

return (
<div key={editorId} className="flex flex-col gap-0.5">
<span className={`text-xs font-medium capitalize ${roleColor}`}>
{msg.role}
</span>
<EditorProvider
id={editorId}
initialValue={text}
showToolbar={false}
enableTokens={false}
readOnly
className={EDITOR_RESET_CLASSES}
>
<MarkdownModeSync isMarkdownView={false} />
<EditorWrapper
{text ? (
<EditorProvider
id={editorId}
initialValue={text}
disabled
showToolbar={false}
noProvider
enableTokens={false}
readOnly
boundHeight={false}
/>
</EditorProvider>
className={EDITOR_RESET_CLASSES}
>
<MarkdownModeSync isMarkdownView={false} />
<EditorWrapper
initialValue={text}
disabled
showToolbar={false}
noProvider
readOnly
boundHeight={false}
/>
</EditorProvider>
) : null}
{toolCalls.length > 0 ? (
<div className="flex flex-col gap-1">
{toolCalls.map((toolCall, toolCallIndex) => (
<pre
key={`${editorId}-tool-call-${toolCallIndex}`}
className="m-0 whitespace-pre-wrap break-words rounded bg-[#F6F8FB] px-2 py-1 font-mono text-xs text-[var(--ant-color-text)]"
>
{formatToolCall(toolCall)}
</pre>
))}
</div>
) : null}
</div>
)
})}
Expand Down Expand Up @@ -339,6 +397,10 @@ const RenderedValueBlock = memo(function RenderedValueBlock({
maxDepth?: number
}) {
const value = useMemo(() => simplifyValue(rawValue), [rawValue])
const [renderFull, setRenderFull] = useState(false)
const stats = useMemo(() => getRenderStats(value), [value])
const shouldPreviewValue =
!renderFull && shouldUsePreview(stats, "beautified-json" as RenderBudgetMode)

const chatMessages = useMemo(() => extractChatMessages(value), [value])

Expand All @@ -351,6 +413,18 @@ const RenderedValueBlock = memo(function RenderedValueBlock({
}

if (typeof value === "string") {
if (shouldPreviewValue) {
return (
<LargeValuePreview
compact
value={value}
mode="beautified-json"
stats={stats}
onRenderFull={() => setRenderFull(true)}
/>
)
}

return (
<EditorProvider
id={keyPrefix}
Expand All @@ -377,15 +451,19 @@ const RenderedValueBlock = memo(function RenderedValueBlock({
return <span className="text-[#758391]">—</span>
}

const previewItems = shouldPreviewValue ? getPreviewItems(value) : null

if (
depth < maxDepth &&
Array.isArray(value) &&
value.length > 0 &&
value.some((item) => item && typeof item === "object")
) {
const items = previewItems?.kind === "array" ? (previewItems.items as unknown[]) : value

return (
<div className="flex flex-col gap-2">
{value.map((item, i) => {
{items.map((item, i) => {
const simplified = simplifyValue(item)
if (
typeof simplified === "string" ||
Expand Down Expand Up @@ -424,12 +502,20 @@ const RenderedValueBlock = memo(function RenderedValueBlock({
/>
)
})}
{previewItems?.kind === "array" && previewItems.hiddenCount > 0 ? (
<Button size="small" type="link" onClick={() => setRenderFull(true)}>
Show {previewItems.hiddenCount} more items
</Button>
) : null}
</div>
)
}

if (value && typeof value === "object" && !Array.isArray(value)) {
const entries = Object.entries(value as Record<string, unknown>)
const entries =
previewItems?.kind === "object"
? (previewItems.items as [string, unknown][])
: Object.entries(value as Record<string, unknown>)
return (
<div className="flex flex-col gap-1">
{entries.map(([k, v]) => {
Expand Down Expand Up @@ -490,10 +576,27 @@ const RenderedValueBlock = memo(function RenderedValueBlock({
/>
)
})}
{previewItems?.kind === "object" && previewItems.hiddenCount > 0 ? (
<Button size="small" type="link" onClick={() => setRenderFull(true)}>
Show {previewItems.hiddenCount} more keys
</Button>
) : null}
</div>
)
}

if (shouldPreviewValue) {
return (
<LargeValuePreview
compact
value={value}
mode="beautified-json"
stats={stats}
onRenderFull={() => setRenderFull(true)}
/>
)
}

return (
<EditorProvider
id={keyPrefix}
Expand Down
87 changes: 87 additions & 0 deletions web/oss/src/components/DrillInView/LargeValuePreview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import {useMemo, useState} from "react"

import {CopyIcon} from "@phosphor-icons/react"
import {Button, Typography} from "antd"

import {copyToClipboard} from "@/oss/lib/helpers/copyToClipboard"

import {
formatRenderSize,
getRenderStats,
previewValueString,
stringifyFullValue,
type RenderBudgetMode,
type RenderStats,
} from "./renderBudget"

interface LargeValuePreviewProps {
value: unknown
mode: RenderBudgetMode
stats?: RenderStats
title?: string
onRenderFull?: () => void
compact?: boolean
}

export default function LargeValuePreview({
value,
mode,
stats: statsProp,
title = "Large value preview",
onRenderFull,
compact = false,
}: LargeValuePreviewProps) {
const [isCopying, setIsCopying] = useState(false)
const stats = statsProp ?? getRenderStats(value)
const preview = useMemo(() => previewValueString(value), [value])

const handleCopyFull = async () => {
setIsCopying(true)
try {
await copyToClipboard(stringifyFullValue(value))
} finally {
setIsCopying(false)
}
}

const detailParts = [
stats.type,
formatRenderSize(stats.estimatedChars),
stats.arrayLength !== undefined ? `${stats.arrayLength} items` : null,
stats.objectKeyCount !== undefined ? `${stats.objectKeyCount} keys` : null,
`mode: ${mode}`,
].filter(Boolean)

return (
<div
className={`${compact ? "my-1" : "m-4"} flex flex-col gap-3 rounded-md border border-solid border-[rgba(5,23,41,0.08)] bg-white p-3`}
>
<div className="flex flex-wrap items-center justify-between gap-2">
<div className="flex flex-col gap-0.5">
<Typography.Text strong>{title}</Typography.Text>
<Typography.Text type="secondary" className="text-xs">
{detailParts.join(" · ")}
</Typography.Text>
</div>
<div className="flex items-center gap-2">
<Button
size="small"
icon={<CopyIcon size={13} />}
loading={isCopying}
onClick={handleCopyFull}
>
Copy full
</Button>
{onRenderFull ? (
<Button size="small" onClick={onRenderFull}>
Render full
</Button>
) : null}
</div>
</div>
<pre className="m-0 max-h-[320px] overflow-auto whitespace-pre-wrap break-words rounded bg-[#F6F8FB] p-2 font-mono text-xs text-[var(--ant-color-text)]">
{preview}
</pre>
</div>
)
}
Loading
Loading