| ... | ... | @@ -5,12 +5,16 @@ import type { |
| 5 | 5 | ThreadId, |
| 6 | 6 | } from "@t3tools/contracts"; |
| 7 | 7 | import { ExternalLinkIcon, GitBranchIcon, RotateCcwIcon } from "lucide-react"; |
| 8 | | import { memo } from "react"; |
| 8 | import { memo, Suspense, use, useMemo } from "react"; |
| 9 | 9 | |
| 10 | import { useTheme } from "../../hooks/useTheme"; |
| 11 | import { resolveDiffThemeName } from "../../lib/diffRendering"; |
| 12 | import { getSyntaxHighlighterPromise } from "../../lib/syntaxHighlighting"; |
| 10 | 13 | import { useV2ItemSupport } from "../../state/v2ItemSupport"; |
| 11 | 14 | import { formatWorkspaceRelativePath } from "../../filePathDisplay"; |
| 12 | 15 | import { Button } from "../ui/button"; |
| 13 | 16 | import ChatMarkdown from "../ChatMarkdown"; |
| 17 | import { RenderErrorBoundary } from "../RenderErrorBoundary"; |
| 14 | 18 | import { resolveExternalWebLinkHref } from "./externalLinkContextMenu"; |
| 15 | 19 | |
| 16 | 20 | interface V2ItemInspectorProps { |
| ... | ... | @@ -26,12 +30,53 @@ interface V2ItemInspectorProps { |
| 26 | 30 | }) => void; |
| 27 | 31 | } |
| 28 | 32 | |
| 29 | | function StructuredValue({ value }: { readonly value: unknown }) { |
| 33 | function JsonTokens({ text }: { readonly text: string }) { |
| 34 | const { resolvedTheme } = useTheme(); |
| 35 | const highlighter = use(getSyntaxHighlighterPromise("json")); |
| 36 | const { tokens } = useMemo( |
| 37 | () => |
| 38 | highlighter.codeToTokens(text, { lang: "json", theme: resolveDiffThemeName(resolvedTheme) }), |
| 39 | [highlighter, text, resolvedTheme], |
| 40 | ); |
| 41 | return tokens.flatMap((line, lineIndex) => [ |
| 42 | lineIndex > 0 ? "\n" : "", |
| 43 | ...line.map((token) => ( |
| 44 | <span key={token.offset} style={{ color: token.color }}> |
| 45 | {token.content} |
| 46 | </span> |
| 47 | )), |
| 48 | ]); |
| 49 | } |
| 50 | |
| 51 | function StructuredValue({ |
| 52 | value, |
| 53 | highlightJson = false, |
| 54 | }: { |
| 55 | readonly value: unknown; |
| 56 | readonly highlightJson?: boolean; |
| 57 | }) { |
| 30 | 58 | const text = typeof value === "string" ? value : JSON.stringify(value, null, 2); |
| 59 | const isJson = useMemo(() => { |
| 60 | if (!highlightJson || !text) return false; |
| 61 | try { |
| 62 | JSON.parse(text); |
| 63 | return true; |
| 64 | } catch { |
| 65 | return false; |
| 66 | } |
| 67 | }, [highlightJson, text]); |
| 31 | 68 | if (!text) return null; |
| 32 | 69 | return ( |
| 33 | 70 | <pre className="max-h-80 overflow-auto whitespace-pre-wrap break-words rounded-md border border-border/50 bg-background/60 p-2 font-mono text-2xs leading-relaxed text-muted-foreground select-text"> |
| 34 | | {text} |
| 71 | {isJson ? ( |
| 72 | <RenderErrorBoundary fallback={text}> |
| 73 | <Suspense fallback={text}> |
| 74 | <JsonTokens text={text} /> |
| 75 | </Suspense> |
| 76 | </RenderErrorBoundary> |
| 77 | ) : ( |
| 78 | text |
| 79 | )} |
| 35 | 80 | </pre> |
| 36 | 81 | ); |
| 37 | 82 | } |
| ... | ... | @@ -61,7 +106,7 @@ export const V2ItemInspector = memo(function V2ItemInspector(props: V2ItemInspec |
| 61 | 106 | |
| 62 | 107 | {item.type === "command_execution" ? ( |
| 63 | 108 | <div className="space-y-2"> |
| 64 | | <StructuredValue value={item.input} /> |
| 109 | <StructuredValue value={item.input} highlightJson /> |
| 65 | 110 | {item.exitCode !== undefined ? ( |
| 66 | 111 | <p className={item.exitCode === 0 ? "text-success" : "text-destructive"}> |
| 67 | 112 | Process exited with code {item.exitCode} |
| ... | ... | @@ -158,7 +203,7 @@ export const V2ItemInspector = memo(function V2ItemInspector(props: V2ItemInspec |
| 158 | 203 | <p className="mb-1 text-3xs font-medium tracking-wide uppercase text-muted-foreground"> |
| 159 | 204 | Input |
| 160 | 205 | </p> |
| 161 | | <StructuredValue value={item.input} /> |
| 206 | <StructuredValue value={item.input} highlightJson /> |
| 162 | 207 | </div> |
| 163 | 208 | ) : null} |
| 164 | 209 | |