langfuse
131 строка · 3.7 Кб
1import { useState } from "react";
2import { Button } from "@/src/components/ui/button";
3import { Check, ChevronsDownUp, ChevronsUpDown, Copy } from "lucide-react";
4import { cn } from "@/src/utils/tailwind";
5import { default as React18JsonView } from "react18-json-view";
6import { deepParseJson } from "@/src/utils/json";
7import { Skeleton } from "@/src/components/ui/skeleton";
8
9export function JSONView(props: {
10json?: unknown;
11title?: string;
12className?: string;
13isLoading?: boolean;
14}) {
15// some users ingest stringified json nested in json, parse it
16const parsedJson = deepParseJson(props.json);
17
18return (
19<div className={cn("rounded-md border", props.className)}>
20{props.title ? (
21<div className="border-b px-3 py-1 text-xs font-medium">
22{props.title}
23</div>
24) : undefined}
25<div className="flex gap-2 whitespace-pre-wrap break-words p-3 text-xs">
26{props.isLoading ? (
27<Skeleton className="h-3 w-3/4" />
28) : (
29<React18JsonView
30src={parsedJson}
31theme="github"
32collapseObjectsAfterLength={20}
33collapseStringsAfterLength={500}
34displaySize={"collapsed"}
35matchesURL={true}
36customizeCopy={(node) => stringifyJsonNode(node)}
37className="w-full"
38/>
39)}
40</div>
41</div>
42);
43}
44
45export function CodeView(props: {
46content: string | undefined | null;
47className?: string;
48defaultCollapsed?: boolean;
49scrollable?: boolean;
50title?: string;
51}) {
52const [isCopied, setIsCopied] = useState(false);
53const [isCollapsed, setCollapsed] = useState(props.defaultCollapsed);
54
55const handleCopy = () => {
56setIsCopied(true);
57void navigator.clipboard.writeText(props.content ?? "");
58setTimeout(() => setIsCopied(false), 1000);
59};
60
61const handleShowAll = () => setCollapsed(!isCollapsed);
62
63return (
64<div className={cn("max-w-full rounded-md border ", props.className)}>
65{props.title ? (
66<div className="border-b px-3 py-1 text-xs font-medium">
67{props.title}
68</div>
69) : undefined}
70<div className="flex gap-2">
71<code
72className={cn(
73"relative flex-1 whitespace-pre-wrap break-all px-4 py-3 font-mono text-xs",
74isCollapsed ? `line-clamp-6` : "block",
75props.scrollable ? "max-h-60 overflow-y-scroll" : undefined,
76)}
77>
78{props.content}
79</code>
80<div className="flex gap-2 py-2 pr-2">
81{props.defaultCollapsed ? (
82<Button variant="secondary" size="xs" onClick={handleShowAll}>
83{isCollapsed ? (
84<ChevronsUpDown className="h-3 w-3" />
85) : (
86<ChevronsDownUp className="h-3 w-3" />
87)}
88</Button>
89) : undefined}
90<Button variant="secondary" size="xs" onClick={handleCopy}>
91{isCopied ? (
92<Check className="h-3 w-3" />
93) : (
94<Copy className="h-3 w-3" />
95)}
96</Button>
97</div>
98</div>
99</div>
100);
101}
102
103function stringifyJsonNode(node: unknown) {
104// return single string nodes without quotes
105if (typeof node === "string") {
106return node;
107}
108
109try {
110return JSON.stringify(
111node,
112(key, value) => {
113switch (typeof value) {
114case "bigint":
115return String(value) + "n";
116case "number":
117case "boolean":
118case "object":
119case "string":
120return value as string;
121default:
122return String(value);
123}
124},
1254,
126);
127} catch (error) {
128console.error("JSON stringify error", error);
129return "Error: JSON.stringify failed";
130}
131}
132