@tonyclaw/llm-inspector 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.output/nitro.json +17 -0
- package/.output/public/assets/alibaba-TTwafVwX.svg +1 -0
- package/.output/public/assets/index-B3RwBPLW.css +1 -0
- package/.output/public/assets/index-s4lwsWvq.js +97 -0
- package/.output/public/assets/main-Cp8AM0Pa.js +17 -0
- package/.output/public/assets/minimax-BPMzvuL-.jpeg +0 -0
- package/.output/public/assets/qwen-CONDcHqt.png +0 -0
- package/.output/public/assets/zhipuai-BPNAnxo-.svg +219 -0
- package/.output/server/_chunks/ssr-renderer.mjs +17 -0
- package/.output/server/_libs/@radix-ui/react-accessible-icon+[...].mjs +1 -0
- package/.output/server/_libs/@radix-ui/react-dismissable-layer+[...].mjs +210 -0
- package/.output/server/_libs/@radix-ui/react-navigation-menu+[...].mjs +1 -0
- package/.output/server/_libs/@radix-ui/react-one-time-password-field+[...].mjs +1 -0
- package/.output/server/_libs/@radix-ui/react-password-toggle-field+[...].mjs +1 -0
- package/.output/server/_libs/@radix-ui/react-use-callback-ref+[...].mjs +11 -0
- package/.output/server/_libs/@radix-ui/react-use-controllable-state+[...].mjs +69 -0
- package/.output/server/_libs/@radix-ui/react-use-effect-event+[...].mjs +1 -0
- package/.output/server/_libs/@radix-ui/react-use-escape-keydown+[...].mjs +17 -0
- package/.output/server/_libs/@radix-ui/react-use-is-hydrated+[...].mjs +1 -0
- package/.output/server/_libs/@radix-ui/react-use-layout-effect+[...].mjs +6 -0
- package/.output/server/_libs/@radix-ui/react-visually-hidden+[...].mjs +34 -0
- package/.output/server/_libs/ajv-formats.mjs +330 -0
- package/.output/server/_libs/ajv.mjs +11444 -0
- package/.output/server/_libs/aria-hidden.mjs +122 -0
- package/.output/server/_libs/atomically.mjs +152 -0
- package/.output/server/_libs/bail.mjs +8 -0
- package/.output/server/_libs/character-entities.mjs +2130 -0
- package/.output/server/_libs/class-variance-authority.mjs +44 -0
- package/.output/server/_libs/clsx.mjs +16 -0
- package/.output/server/_libs/comma-separated-tokens.mjs +10 -0
- package/.output/server/_libs/conf.mjs +635 -0
- package/.output/server/_libs/cookie-es.mjs +58 -0
- package/.output/server/_libs/core-util-is.mjs +75 -0
- package/.output/server/_libs/croner.mjs +1 -0
- package/.output/server/_libs/crossws.mjs +1 -0
- package/.output/server/_libs/debounce-fn.mjs +69 -0
- package/.output/server/_libs/decode-named-character-reference+[...].mjs +8 -0
- package/.output/server/_libs/detect-node-es.mjs +1 -0
- package/.output/server/_libs/devlop.mjs +8 -0
- package/.output/server/_libs/dot-prop.mjs +265 -0
- package/.output/server/_libs/env-paths.mjs +57 -0
- package/.output/server/_libs/estree-util-is-identifier-name.mjs +11 -0
- package/.output/server/_libs/extend.mjs +97 -0
- package/.output/server/_libs/fast-deep-equal.mjs +38 -0
- package/.output/server/_libs/fast-uri.mjs +812 -0
- package/.output/server/_libs/floating-ui__core.mjs +725 -0
- package/.output/server/_libs/floating-ui__dom.mjs +622 -0
- package/.output/server/_libs/floating-ui__react-dom.mjs +292 -0
- package/.output/server/_libs/floating-ui__utils.mjs +320 -0
- package/.output/server/_libs/get-nonce.mjs +9 -0
- package/.output/server/_libs/h3-v2.mjs +276 -0
- package/.output/server/_libs/h3.mjs +400 -0
- package/.output/server/_libs/hast-util-to-jsx-runtime.mjs +388 -0
- package/.output/server/_libs/hast-util-whitespace.mjs +10 -0
- package/.output/server/_libs/hookable.mjs +1 -0
- package/.output/server/_libs/html-url-attributes.mjs +26 -0
- package/.output/server/_libs/immediate.mjs +74 -0
- package/.output/server/_libs/inherits.mjs +50 -0
- package/.output/server/_libs/inline-style-parser.mjs +142 -0
- package/.output/server/_libs/is-plain-obj.mjs +10 -0
- package/.output/server/_libs/isarray.mjs +14 -0
- package/.output/server/_libs/isbot.mjs +20 -0
- package/.output/server/_libs/json-schema-traverse.mjs +180 -0
- package/.output/server/_libs/jszip.mjs +3049 -0
- package/.output/server/_libs/lie.mjs +273 -0
- package/.output/server/_libs/lucide-react.mjs +368 -0
- package/.output/server/_libs/mdast-util-from-markdown.mjs +717 -0
- package/.output/server/_libs/mdast-util-to-hast.mjs +710 -0
- package/.output/server/_libs/mdast-util-to-string.mjs +38 -0
- package/.output/server/_libs/micromark-core-commonmark.mjs +2259 -0
- package/.output/server/_libs/micromark-factory-destination.mjs +94 -0
- package/.output/server/_libs/micromark-factory-label.mjs +63 -0
- package/.output/server/_libs/micromark-factory-space.mjs +24 -0
- package/.output/server/_libs/micromark-factory-title.mjs +65 -0
- package/.output/server/_libs/micromark-factory-whitespace.mjs +22 -0
- package/.output/server/_libs/micromark-util-character.mjs +44 -0
- package/.output/server/_libs/micromark-util-chunked.mjs +36 -0
- package/.output/server/_libs/micromark-util-classify-character+[...].mjs +12 -0
- package/.output/server/_libs/micromark-util-combine-extensions+[...].mjs +41 -0
- package/.output/server/_libs/micromark-util-decode-numeric-character-reference+[...].mjs +19 -0
- package/.output/server/_libs/micromark-util-decode-string.mjs +21 -0
- package/.output/server/_libs/micromark-util-encode.mjs +1 -0
- package/.output/server/_libs/micromark-util-html-tag-name.mjs +69 -0
- package/.output/server/_libs/micromark-util-normalize-identifier+[...].mjs +6 -0
- package/.output/server/_libs/micromark-util-resolve-all.mjs +15 -0
- package/.output/server/_libs/micromark-util-sanitize-uri.mjs +41 -0
- package/.output/server/_libs/micromark-util-subtokenize.mjs +346 -0
- package/.output/server/_libs/micromark.mjs +906 -0
- package/.output/server/_libs/mimic-function.mjs +47 -0
- package/.output/server/_libs/ohash.mjs +1 -0
- package/.output/server/_libs/pako.mjs +4223 -0
- package/.output/server/_libs/process-nextick-args.mjs +48 -0
- package/.output/server/_libs/property-information.mjs +1209 -0
- package/.output/server/_libs/radix-ui.mjs +1 -0
- package/.output/server/_libs/radix-ui__number.mjs +6 -0
- package/.output/server/_libs/radix-ui__primitive.mjs +11 -0
- package/.output/server/_libs/radix-ui__react-accordion.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-alert-dialog.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-arrow.mjs +23 -0
- package/.output/server/_libs/radix-ui__react-aspect-ratio.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-avatar.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-checkbox.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-collapsible.mjs +144 -0
- package/.output/server/_libs/radix-ui__react-collection.mjs +69 -0
- package/.output/server/_libs/radix-ui__react-compose-refs.mjs +39 -0
- package/.output/server/_libs/radix-ui__react-context-menu.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-context.mjs +78 -0
- package/.output/server/_libs/radix-ui__react-dialog.mjs +325 -0
- package/.output/server/_libs/radix-ui__react-direction.mjs +9 -0
- package/.output/server/_libs/radix-ui__react-dropdown-menu.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-focus-guards.mjs +29 -0
- package/.output/server/_libs/radix-ui__react-focus-scope.mjs +206 -0
- package/.output/server/_libs/radix-ui__react-form.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-hover-card.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-id.mjs +14 -0
- package/.output/server/_libs/radix-ui__react-label.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-menu.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-menubar.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-popover.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-popper.mjs +286 -0
- package/.output/server/_libs/radix-ui__react-portal.mjs +16 -0
- package/.output/server/_libs/radix-ui__react-presence.mjs +128 -0
- package/.output/server/_libs/radix-ui__react-primitive.mjs +42 -0
- package/.output/server/_libs/radix-ui__react-progress.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-radio-group.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-roving-focus.mjs +224 -0
- package/.output/server/_libs/radix-ui__react-scroll-area.mjs +721 -0
- package/.output/server/_libs/radix-ui__react-select.mjs +1163 -0
- package/.output/server/_libs/radix-ui__react-separator.mjs +28 -0
- package/.output/server/_libs/radix-ui__react-slider.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-slot.mjs +99 -0
- package/.output/server/_libs/radix-ui__react-switch.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-tabs.mjs +189 -0
- package/.output/server/_libs/radix-ui__react-toast.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-toggle-group.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-toggle.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-toolbar.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-tooltip.mjs +495 -0
- package/.output/server/_libs/radix-ui__react-use-previous.mjs +14 -0
- package/.output/server/_libs/radix-ui__react-use-size.mjs +39 -0
- package/.output/server/_libs/react-dom.mjs +9935 -0
- package/.output/server/_libs/react-markdown.mjs +147 -0
- package/.output/server/_libs/react-remove-scroll-bar.mjs +82 -0
- package/.output/server/_libs/react-remove-scroll.mjs +328 -0
- package/.output/server/_libs/react-style-singleton.mjs +69 -0
- package/.output/server/_libs/react.mjs +515 -0
- package/.output/server/_libs/readable-stream.mjs +1518 -0
- package/.output/server/_libs/remark-parse.mjs +19 -0
- package/.output/server/_libs/remark-rehype.mjs +21 -0
- package/.output/server/_libs/rou3.mjs +8 -0
- package/.output/server/_libs/safe-buffer.mjs +64 -0
- package/.output/server/_libs/semver.mjs +1984 -0
- package/.output/server/_libs/seroval-plugins.mjs +58 -0
- package/.output/server/_libs/seroval.mjs +1765 -0
- package/.output/server/_libs/setimmediate.mjs +1 -0
- package/.output/server/_libs/space-separated-tokens.mjs +6 -0
- package/.output/server/_libs/srvx.mjs +334 -0
- package/.output/server/_libs/stubborn-fs.mjs +91 -0
- package/.output/server/_libs/stubborn-utils.mjs +66 -0
- package/.output/server/_libs/style-to-js.mjs +72 -0
- package/.output/server/_libs/style-to-object.mjs +38 -0
- package/.output/server/_libs/tailwind-merge.mjs +3010 -0
- package/.output/server/_libs/tanstack__history.mjs +217 -0
- package/.output/server/_libs/tanstack__react-router.mjs +1480 -0
- package/.output/server/_libs/tanstack__react-store.mjs +1 -0
- package/.output/server/_libs/tanstack__react-virtual.mjs +44 -0
- package/.output/server/_libs/tanstack__router-core.mjs +4827 -0
- package/.output/server/_libs/tanstack__store.mjs +1 -0
- package/.output/server/_libs/tanstack__virtual-core.mjs +1225 -0
- package/.output/server/_libs/tiny-invariant.mjs +12 -0
- package/.output/server/_libs/tiny-warning.mjs +5 -0
- package/.output/server/_libs/trim-lines.mjs +41 -0
- package/.output/server/_libs/trough.mjs +85 -0
- package/.output/server/_libs/tslib.mjs +576 -0
- package/.output/server/_libs/ufo.mjs +54 -0
- package/.output/server/_libs/uint8array-extras.mjs +69 -0
- package/.output/server/_libs/unctx.mjs +1 -0
- package/.output/server/_libs/ungap__structured-clone.mjs +212 -0
- package/.output/server/_libs/unified.mjs +661 -0
- package/.output/server/_libs/unist-util-is.mjs +100 -0
- package/.output/server/_libs/unist-util-position.mjs +27 -0
- package/.output/server/_libs/unist-util-stringify-position.mjs +27 -0
- package/.output/server/_libs/unist-util-visit-parents.mjs +82 -0
- package/.output/server/_libs/unist-util-visit.mjs +24 -0
- package/.output/server/_libs/unstorage.mjs +1 -0
- package/.output/server/_libs/use-callback-ref.mjs +66 -0
- package/.output/server/_libs/use-sidecar.mjs +106 -0
- package/.output/server/_libs/use-sync-external-store.mjs +1 -0
- package/.output/server/_libs/util-deprecate.mjs +12 -0
- package/.output/server/_libs/vfile-message.mjs +138 -0
- package/.output/server/_libs/vfile.mjs +467 -0
- package/.output/server/_libs/when-exit.mjs +53 -0
- package/.output/server/_libs/zod.mjs +4460 -0
- package/.output/server/_ssr/index-ByCLZu7J.mjs +3061 -0
- package/.output/server/_ssr/index.mjs +1176 -0
- package/.output/server/_ssr/router-Bq_mxeNz.mjs +2872 -0
- package/.output/server/_ssr/start-HYkvq4Ni.mjs +4 -0
- package/.output/server/_tanstack-start-manifest_v-C4E0e9my.mjs +4 -0
- package/.output/server/index.mjs +393 -0
- package/README.md +196 -0
- package/package.json +91 -0
- package/src/assets/logos/alibaba.svg +1 -0
- package/src/assets/logos/anthropic.svg +1 -0
- package/src/assets/logos/deepseek.svg +1 -0
- package/src/assets/logos/minimax.jpeg +0 -0
- package/src/assets/logos/openai.svg +1 -0
- package/src/assets/logos/qwen.png +0 -0
- package/src/assets/logos/zhipuai.svg +219 -0
- package/src/cli.ts +68 -0
- package/src/components/ProxyViewer.tsx +325 -0
- package/src/components/ProxyViewerContainer.tsx +211 -0
- package/src/components/providers/ProviderCard.tsx +186 -0
- package/src/components/providers/ProviderForm.tsx +259 -0
- package/src/components/providers/ProviderLogo.tsx +111 -0
- package/src/components/providers/ProvidersPanel.tsx +259 -0
- package/src/components/providers/SettingsDialog.tsx +39 -0
- package/src/components/proxy-viewer/ConversationGroup.tsx +68 -0
- package/src/components/proxy-viewer/ConversationHeader.tsx +141 -0
- package/src/components/proxy-viewer/LogEntry.tsx +225 -0
- package/src/components/proxy-viewer/LogEntryHeader.tsx +250 -0
- package/src/components/proxy-viewer/ReplayDialog.tsx +208 -0
- package/src/components/proxy-viewer/ResponseView.tsx +161 -0
- package/src/components/proxy-viewer/StreamingChunkSequence.tsx +171 -0
- package/src/components/proxy-viewer/formats/anthropic/ContentBlocks.tsx +139 -0
- package/src/components/proxy-viewer/formats/anthropic/ResponseView.tsx +64 -0
- package/src/components/proxy-viewer/formats/index.tsx +24 -0
- package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +80 -0
- package/src/components/proxy-viewer/index.ts +8 -0
- package/src/components/ui/badge.tsx +47 -0
- package/src/components/ui/button.tsx +47 -0
- package/src/components/ui/collapsible.tsx +21 -0
- package/src/components/ui/dialog.tsx +129 -0
- package/src/components/ui/json-viewer.tsx +464 -0
- package/src/components/ui/scroll-area.tsx +54 -0
- package/src/components/ui/select.tsx +178 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/tabs.tsx +88 -0
- package/src/components/ui/tooltip.tsx +51 -0
- package/src/index.css +11 -0
- package/src/lib/export-logs.ts +51 -0
- package/src/lib/utils.ts +22 -0
- package/src/proxy/chunkStorage.ts +118 -0
- package/src/proxy/constants.ts +36 -0
- package/src/proxy/formats/anthropic/anthropicProvider.ts +75 -0
- package/src/proxy/formats/anthropic/handler.ts +74 -0
- package/src/proxy/formats/anthropic/index.ts +14 -0
- package/src/proxy/formats/anthropic/register.ts +4 -0
- package/src/proxy/formats/anthropic/schemas.ts +217 -0
- package/src/proxy/formats/anthropic/stream.ts +167 -0
- package/src/proxy/formats/handler.ts +46 -0
- package/src/proxy/formats/index.ts +12 -0
- package/src/proxy/formats/jsonSchema.ts +24 -0
- package/src/proxy/formats/openai/alibabaProvider.ts +38 -0
- package/src/proxy/formats/openai/handler.ts +70 -0
- package/src/proxy/formats/openai/index.ts +25 -0
- package/src/proxy/formats/openai/provider.ts +50 -0
- package/src/proxy/formats/openai/register.ts +4 -0
- package/src/proxy/formats/openai/schemas.ts +150 -0
- package/src/proxy/formats/openai/stream.ts +153 -0
- package/src/proxy/formats/protocol.ts +50 -0
- package/src/proxy/formats/providerRegistry.ts +51 -0
- package/src/proxy/formats/providers/index.ts +3 -0
- package/src/proxy/formats/registry.ts +61 -0
- package/src/proxy/handler.ts +389 -0
- package/src/proxy/logIndex.ts +187 -0
- package/src/proxy/logger.ts +99 -0
- package/src/proxy/providers.ts +234 -0
- package/src/proxy/schemas.ts +160 -0
- package/src/proxy/socketTracker.ts +158 -0
- package/src/proxy/store.ts +386 -0
- package/src/router.tsx +16 -0
- package/src/routes/__root.tsx +38 -0
- package/src/routes/api/config.paths.ts +14 -0
- package/src/routes/api/health.ts +11 -0
- package/src/routes/api/logs.$id.chunks.ts +36 -0
- package/src/routes/api/logs.$id.replay.ts +262 -0
- package/src/routes/api/logs.$id.ts +22 -0
- package/src/routes/api/logs.stream.ts +64 -0
- package/src/routes/api/logs.ts +30 -0
- package/src/routes/api/models.ts +10 -0
- package/src/routes/api/providers.$providerId.ts +45 -0
- package/src/routes/api/providers.ts +37 -0
- package/src/routes/api/sessions.ts +10 -0
- package/src/routes/index.tsx +6 -0
- package/src/routes/proxy/$.ts +15 -0
- package/styles/globals.css +121 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
+
import { Slot } from "radix-ui";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../lib/utils";
|
|
6
|
+
|
|
7
|
+
const badgeVariants = cva(
|
|
8
|
+
"inline-flex items-center justify-center rounded-full border border-transparent px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
|
13
|
+
secondary: "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
|
14
|
+
destructive:
|
|
15
|
+
"bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
|
16
|
+
outline:
|
|
17
|
+
"border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
|
18
|
+
ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
|
19
|
+
link: "text-primary underline-offset-4 [a&]:hover:underline",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultVariants: {
|
|
23
|
+
variant: "default",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
function Badge({
|
|
29
|
+
className,
|
|
30
|
+
variant = "default",
|
|
31
|
+
asChild = false,
|
|
32
|
+
...props
|
|
33
|
+
}: React.ComponentProps<"span"> &
|
|
34
|
+
VariantProps<typeof badgeVariants> & { asChild?: boolean }): React.JSX.Element {
|
|
35
|
+
const Comp = asChild ? Slot.Root : "span";
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<Comp
|
|
39
|
+
data-slot="badge"
|
|
40
|
+
data-variant={variant}
|
|
41
|
+
className={cn(badgeVariants({ variant }), className)}
|
|
42
|
+
{...props}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { Badge, badgeVariants };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
|
|
4
|
+
import { cn } from "../../lib/utils";
|
|
5
|
+
|
|
6
|
+
const buttonVariants = cva(
|
|
7
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
12
|
+
destructive: "bg-destructive text-white hover:bg-destructive/90",
|
|
13
|
+
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
14
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
15
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
16
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
17
|
+
},
|
|
18
|
+
size: {
|
|
19
|
+
default: "h-9 px-4 py-2",
|
|
20
|
+
sm: "h-8 rounded-md px-3 text-xs",
|
|
21
|
+
lg: "h-10 rounded-md px-8",
|
|
22
|
+
icon: "size-9",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
defaultVariants: {
|
|
26
|
+
variant: "default",
|
|
27
|
+
size: "default",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
function Button({
|
|
33
|
+
className,
|
|
34
|
+
variant,
|
|
35
|
+
size,
|
|
36
|
+
...props
|
|
37
|
+
}: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants>): React.JSX.Element {
|
|
38
|
+
return (
|
|
39
|
+
<button
|
|
40
|
+
data-slot="button"
|
|
41
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
42
|
+
{...props}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { Button, buttonVariants };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Collapsible as CollapsiblePrimitive } from "radix-ui";
|
|
2
|
+
|
|
3
|
+
function Collapsible({
|
|
4
|
+
...props
|
|
5
|
+
}: React.ComponentProps<typeof CollapsiblePrimitive.Root>): React.JSX.Element {
|
|
6
|
+
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function CollapsibleTrigger({
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>): React.JSX.Element {
|
|
12
|
+
return <CollapsiblePrimitive.CollapsibleTrigger data-slot="collapsible-trigger" {...props} />;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function CollapsibleContent({
|
|
16
|
+
...props
|
|
17
|
+
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>): React.JSX.Element {
|
|
18
|
+
return <CollapsiblePrimitive.CollapsibleContent data-slot="collapsible-content" {...props} />;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { Collapsible, CollapsibleTrigger, CollapsibleContent };
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
4
|
+
import { X } from "lucide-react";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
|
|
7
|
+
import { cn } from "../../lib/utils";
|
|
8
|
+
|
|
9
|
+
function Dialog({
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof DialogPrimitive.Root>): React.JSX.Element {
|
|
12
|
+
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function DialogTrigger({
|
|
16
|
+
...props
|
|
17
|
+
}: React.ComponentProps<typeof DialogPrimitive.Trigger>): React.JSX.Element {
|
|
18
|
+
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function DialogPortal({
|
|
22
|
+
...props
|
|
23
|
+
}: React.ComponentProps<typeof DialogPrimitive.Portal>): React.JSX.Element {
|
|
24
|
+
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function DialogClose({
|
|
28
|
+
...props
|
|
29
|
+
}: React.ComponentProps<typeof DialogPrimitive.Close>): React.JSX.Element {
|
|
30
|
+
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function DialogOverlay({
|
|
34
|
+
className,
|
|
35
|
+
...props
|
|
36
|
+
}: React.ComponentProps<typeof DialogPrimitive.Overlay>): React.JSX.Element {
|
|
37
|
+
return (
|
|
38
|
+
<DialogPrimitive.Overlay
|
|
39
|
+
data-slot="dialog-overlay"
|
|
40
|
+
className={cn(
|
|
41
|
+
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
42
|
+
className,
|
|
43
|
+
)}
|
|
44
|
+
{...props}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function DialogContent({
|
|
50
|
+
className,
|
|
51
|
+
children,
|
|
52
|
+
...props
|
|
53
|
+
}: React.ComponentProps<typeof DialogPrimitive.Content>): React.JSX.Element {
|
|
54
|
+
return (
|
|
55
|
+
<DialogPortal>
|
|
56
|
+
<DialogOverlay />
|
|
57
|
+
<DialogPrimitive.Content
|
|
58
|
+
data-slot="dialog-content"
|
|
59
|
+
className={cn(
|
|
60
|
+
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] rounded-lg",
|
|
61
|
+
className,
|
|
62
|
+
)}
|
|
63
|
+
{...props}
|
|
64
|
+
>
|
|
65
|
+
{children}
|
|
66
|
+
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
|
67
|
+
<X className="size-4" />
|
|
68
|
+
<span className="sr-only">Close</span>
|
|
69
|
+
</DialogPrimitive.Close>
|
|
70
|
+
</DialogPrimitive.Content>
|
|
71
|
+
</DialogPortal>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function DialogHeader({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element {
|
|
76
|
+
return (
|
|
77
|
+
<div
|
|
78
|
+
data-slot="dialog-header"
|
|
79
|
+
className={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)}
|
|
80
|
+
{...props}
|
|
81
|
+
/>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function DialogFooter({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element {
|
|
86
|
+
return (
|
|
87
|
+
<div
|
|
88
|
+
data-slot="dialog-footer"
|
|
89
|
+
className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function DialogTitle({
|
|
96
|
+
className,
|
|
97
|
+
...props
|
|
98
|
+
}: React.ComponentProps<typeof DialogPrimitive.Title>): React.JSX.Element {
|
|
99
|
+
return (
|
|
100
|
+
<DialogPrimitive.Title
|
|
101
|
+
data-slot="dialog-title"
|
|
102
|
+
className={cn("text-lg font-semibold leading-none tracking-tight", className)}
|
|
103
|
+
{...props}
|
|
104
|
+
/>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function DialogDescription({
|
|
109
|
+
className,
|
|
110
|
+
...props
|
|
111
|
+
}: React.ComponentProps<typeof DialogPrimitive.Description>): React.JSX.Element {
|
|
112
|
+
return (
|
|
113
|
+
<DialogPrimitive.Description
|
|
114
|
+
data-slot="dialog-description"
|
|
115
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
116
|
+
{...props}
|
|
117
|
+
/>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export {
|
|
122
|
+
Dialog,
|
|
123
|
+
DialogPortal,
|
|
124
|
+
DialogOverlay,
|
|
125
|
+
DialogTrigger,
|
|
126
|
+
DialogContent,
|
|
127
|
+
DialogHeader,
|
|
128
|
+
DialogTitle,
|
|
129
|
+
};
|
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import { Check, ChevronDown, ChevronRight, ChevronsDown, ChevronsUp, Copy } from "lucide-react";
|
|
2
|
+
import { type JSX, useState } from "react";
|
|
3
|
+
import ReactMarkdown from "react-markdown";
|
|
4
|
+
import { cn } from "../../lib/utils";
|
|
5
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip";
|
|
6
|
+
|
|
7
|
+
type JsonPrimitive = string | number | boolean | null;
|
|
8
|
+
type JsonValue = JsonPrimitive | ReadonlyArray<JsonValue> | Readonly<{ [key: string]: JsonValue }>;
|
|
9
|
+
|
|
10
|
+
type DataType = "string" | "number" | "boolean" | "null" | "array" | "object";
|
|
11
|
+
|
|
12
|
+
function classifyValue(value: JsonValue): DataType {
|
|
13
|
+
if (value === null) return "null";
|
|
14
|
+
if (Array.isArray(value)) return "array";
|
|
15
|
+
switch (typeof value) {
|
|
16
|
+
case "string":
|
|
17
|
+
return "string";
|
|
18
|
+
case "number":
|
|
19
|
+
return "number";
|
|
20
|
+
case "boolean":
|
|
21
|
+
return "boolean";
|
|
22
|
+
case "object":
|
|
23
|
+
return "object";
|
|
24
|
+
case "bigint":
|
|
25
|
+
case "symbol":
|
|
26
|
+
case "undefined":
|
|
27
|
+
case "function":
|
|
28
|
+
return "object";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isExpandable(value: JsonValue): boolean {
|
|
33
|
+
return value !== null && (Array.isArray(value) || typeof value === "object");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getPropertyValue(obj: object, key: string): unknown {
|
|
37
|
+
const descriptor = Object.getOwnPropertyDescriptor(obj, key);
|
|
38
|
+
if (descriptor === undefined) return undefined;
|
|
39
|
+
return descriptor.value;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getEntries(value: JsonValue): ReadonlyArray<readonly [string, JsonValue]> {
|
|
43
|
+
if (Array.isArray(value)) {
|
|
44
|
+
return value.map((item, index) => [String(index), item] as const);
|
|
45
|
+
}
|
|
46
|
+
if (typeof value === "object" && value !== null) {
|
|
47
|
+
return Object.keys(value).map((key) => {
|
|
48
|
+
const raw = getPropertyValue(value, key);
|
|
49
|
+
return [key, safeJsonValue(raw)] as const;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getItemCount(value: JsonValue): number {
|
|
56
|
+
if (Array.isArray(value)) return value.length;
|
|
57
|
+
if (typeof value === "object" && value !== null) return Object.keys(value).length;
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const STRING_TRUNCATE_LIMIT = 120;
|
|
62
|
+
|
|
63
|
+
function StringValue({ text }: { text: string }): JSX.Element {
|
|
64
|
+
const [expanded, setExpanded] = useState(false);
|
|
65
|
+
const isLong = text.length > STRING_TRUNCATE_LIMIT;
|
|
66
|
+
|
|
67
|
+
if (!isLong) {
|
|
68
|
+
return (
|
|
69
|
+
<span className="text-emerald-400 break-all">
|
|
70
|
+
"
|
|
71
|
+
<span className="prose prose-sm dark:prose-invert inline max-w-none [&_p]:inline [&_p]:my-0 [&_code]:text-emerald-300 [&_a]:text-emerald-300">
|
|
72
|
+
<ReactMarkdown>{text}</ReactMarkdown>
|
|
73
|
+
</span>
|
|
74
|
+
"
|
|
75
|
+
</span>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<span className="text-emerald-400 break-all">
|
|
81
|
+
"
|
|
82
|
+
{expanded ? (
|
|
83
|
+
<span
|
|
84
|
+
className="cursor-pointer prose prose-sm dark:prose-invert inline max-w-none [&_p]:inline [&_p]:my-0 [&_code]:text-emerald-300 [&_a]:text-emerald-300"
|
|
85
|
+
onClick={(e) => {
|
|
86
|
+
e.stopPropagation();
|
|
87
|
+
setExpanded(false);
|
|
88
|
+
}}
|
|
89
|
+
onKeyDown={(e) => {
|
|
90
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
91
|
+
e.stopPropagation();
|
|
92
|
+
setExpanded(false);
|
|
93
|
+
}
|
|
94
|
+
}}
|
|
95
|
+
role="button"
|
|
96
|
+
tabIndex={0}
|
|
97
|
+
>
|
|
98
|
+
<ReactMarkdown>{text}</ReactMarkdown>
|
|
99
|
+
</span>
|
|
100
|
+
) : (
|
|
101
|
+
<Tooltip delayDuration={300}>
|
|
102
|
+
<TooltipTrigger
|
|
103
|
+
onClick={(e) => {
|
|
104
|
+
e.stopPropagation();
|
|
105
|
+
setExpanded(true);
|
|
106
|
+
}}
|
|
107
|
+
className="text-left cursor-pointer"
|
|
108
|
+
>
|
|
109
|
+
<span>{text.slice(0, STRING_TRUNCATE_LIMIT)}</span>
|
|
110
|
+
<span className="text-emerald-400/50">…</span>
|
|
111
|
+
</TooltipTrigger>
|
|
112
|
+
<TooltipContent
|
|
113
|
+
side="bottom"
|
|
114
|
+
className="max-w-md text-xs p-2 break-words whitespace-pre-wrap"
|
|
115
|
+
>
|
|
116
|
+
{text.slice(0, 500)}
|
|
117
|
+
{text.length > 500 ? "…" : ""}
|
|
118
|
+
</TooltipContent>
|
|
119
|
+
</Tooltip>
|
|
120
|
+
)}
|
|
121
|
+
"
|
|
122
|
+
</span>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function PrimitiveValue({ value }: { value: JsonValue }): JSX.Element {
|
|
127
|
+
if (value === null) {
|
|
128
|
+
return <span className="text-rose-400 italic">null</span>;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
switch (typeof value) {
|
|
132
|
+
case "string":
|
|
133
|
+
return <StringValue text={value} />;
|
|
134
|
+
case "number":
|
|
135
|
+
return <span className="text-amber-400">{value}</span>;
|
|
136
|
+
case "boolean":
|
|
137
|
+
return <span className="text-blue-400">{value ? "true" : "false"}</span>;
|
|
138
|
+
case "object":
|
|
139
|
+
case "bigint":
|
|
140
|
+
case "symbol":
|
|
141
|
+
case "undefined":
|
|
142
|
+
case "function":
|
|
143
|
+
return <span className="text-muted-foreground">{JSON.stringify(value)}</span>;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function CopyValueButton({ value }: { value: JsonValue }): JSX.Element {
|
|
148
|
+
const [copied, setCopied] = useState(false);
|
|
149
|
+
|
|
150
|
+
function handleCopy(e: React.MouseEvent): void {
|
|
151
|
+
e.stopPropagation();
|
|
152
|
+
void window.navigator.clipboard.writeText(JSON.stringify(value, null, 2)).then(() => {
|
|
153
|
+
setCopied(true);
|
|
154
|
+
setTimeout(() => {
|
|
155
|
+
setCopied(false);
|
|
156
|
+
}, 2000);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<button
|
|
162
|
+
type="button"
|
|
163
|
+
onClick={handleCopy}
|
|
164
|
+
className="inline-flex items-center gap-1 text-[10px] text-muted-foreground hover:text-foreground transition-colors"
|
|
165
|
+
title="Copy JSON"
|
|
166
|
+
>
|
|
167
|
+
{copied ? <Check className="size-3 text-green-500" /> : <Copy className="size-3" />}
|
|
168
|
+
</button>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function CopyButton({ value }: { value: JsonValue }): JSX.Element {
|
|
173
|
+
const [copied, setCopied] = useState(false);
|
|
174
|
+
|
|
175
|
+
function handleCopy(e: React.MouseEvent): void {
|
|
176
|
+
e.stopPropagation();
|
|
177
|
+
void window.navigator.clipboard.writeText(JSON.stringify(value, null, 2)).then(() => {
|
|
178
|
+
setCopied(true);
|
|
179
|
+
setTimeout(() => {
|
|
180
|
+
setCopied(false);
|
|
181
|
+
}, 2000);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
<button
|
|
187
|
+
type="button"
|
|
188
|
+
onClick={handleCopy}
|
|
189
|
+
className="opacity-0 group-hover/row:opacity-100 hover:bg-muted p-0.5 rounded transition-opacity shrink-0"
|
|
190
|
+
title="Copy to clipboard"
|
|
191
|
+
>
|
|
192
|
+
{copied ? (
|
|
193
|
+
<Check className="size-3 text-green-500" />
|
|
194
|
+
) : (
|
|
195
|
+
<Copy className="size-3 text-muted-foreground" />
|
|
196
|
+
)}
|
|
197
|
+
</button>
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
type JsonNodeProps = {
|
|
202
|
+
name: string;
|
|
203
|
+
value: JsonValue;
|
|
204
|
+
level: number;
|
|
205
|
+
defaultExpandDepth: number;
|
|
206
|
+
isArrayItem: boolean;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
function hasExpandableDescendant(value: JsonValue): boolean {
|
|
210
|
+
if (!isExpandable(value)) return false;
|
|
211
|
+
return getEntries(value).some(([, v]) => isExpandable(v));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function ExpandCollapseButton({
|
|
215
|
+
allExpanded,
|
|
216
|
+
onClick,
|
|
217
|
+
}: {
|
|
218
|
+
allExpanded: boolean;
|
|
219
|
+
onClick: (e: React.MouseEvent) => void;
|
|
220
|
+
}): JSX.Element {
|
|
221
|
+
return (
|
|
222
|
+
<button
|
|
223
|
+
type="button"
|
|
224
|
+
onClick={onClick}
|
|
225
|
+
className="opacity-0 group-hover/row:opacity-100 hover:bg-muted p-0.5 rounded transition-opacity shrink-0"
|
|
226
|
+
title={allExpanded ? "Collapse all" : "Expand all"}
|
|
227
|
+
>
|
|
228
|
+
{allExpanded ? (
|
|
229
|
+
<ChevronsUp className="size-3.5 text-muted-foreground" />
|
|
230
|
+
) : (
|
|
231
|
+
<ChevronsDown className="size-3.5 text-muted-foreground" />
|
|
232
|
+
)}
|
|
233
|
+
</button>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function JsonNode({
|
|
238
|
+
name,
|
|
239
|
+
value,
|
|
240
|
+
level,
|
|
241
|
+
defaultExpandDepth,
|
|
242
|
+
isArrayItem,
|
|
243
|
+
}: JsonNodeProps): JSX.Element {
|
|
244
|
+
const [expanded, setExpanded] = useState(level < defaultExpandDepth);
|
|
245
|
+
const [childResetKey, setChildResetKey] = useState(0);
|
|
246
|
+
const [childDepthOverride, setChildDepthOverride] = useState<number | null>(null);
|
|
247
|
+
const [allExpanded, setAllExpanded] = useState(false);
|
|
248
|
+
const expandable = isExpandable(value);
|
|
249
|
+
|
|
250
|
+
const dataType = classifyValue(value);
|
|
251
|
+
const openBracket = dataType === "array" ? "[" : "{";
|
|
252
|
+
const closeBracket = dataType === "array" ? "]" : "}";
|
|
253
|
+
|
|
254
|
+
function handleExpandAll(e: React.MouseEvent): void {
|
|
255
|
+
e.stopPropagation();
|
|
256
|
+
if (allExpanded) {
|
|
257
|
+
setExpanded(false);
|
|
258
|
+
setChildDepthOverride(0);
|
|
259
|
+
setChildResetKey((k) => k + 1);
|
|
260
|
+
setAllExpanded(false);
|
|
261
|
+
} else {
|
|
262
|
+
setExpanded(true);
|
|
263
|
+
setChildDepthOverride(Number.POSITIVE_INFINITY);
|
|
264
|
+
setChildResetKey((k) => k + 1);
|
|
265
|
+
setAllExpanded(true);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const effectiveChildDepth = childDepthOverride ?? defaultExpandDepth;
|
|
270
|
+
|
|
271
|
+
return (
|
|
272
|
+
<div className={cn(level > 0 && "border-l border-border/50 ml-2")}>
|
|
273
|
+
<div
|
|
274
|
+
className={cn(
|
|
275
|
+
"flex items-start gap-1 py-0.5 px-1 -ml-1 rounded-sm group/row",
|
|
276
|
+
expandable && "cursor-pointer hover:bg-muted/50",
|
|
277
|
+
)}
|
|
278
|
+
onClick={
|
|
279
|
+
expandable
|
|
280
|
+
? () => {
|
|
281
|
+
if (expanded) {
|
|
282
|
+
setAllExpanded(false);
|
|
283
|
+
}
|
|
284
|
+
setExpanded(!expanded);
|
|
285
|
+
}
|
|
286
|
+
: undefined
|
|
287
|
+
}
|
|
288
|
+
onKeyDown={
|
|
289
|
+
expandable
|
|
290
|
+
? (e) => {
|
|
291
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
292
|
+
e.preventDefault();
|
|
293
|
+
setExpanded(!expanded);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
: undefined
|
|
297
|
+
}
|
|
298
|
+
role={expandable ? "button" : undefined}
|
|
299
|
+
tabIndex={expandable ? 0 : undefined}
|
|
300
|
+
>
|
|
301
|
+
{expandable ? (
|
|
302
|
+
<span className="w-4 h-5 flex items-center justify-center shrink-0">
|
|
303
|
+
{expanded ? (
|
|
304
|
+
<ChevronDown className="size-3 text-muted-foreground" />
|
|
305
|
+
) : (
|
|
306
|
+
<ChevronRight className="size-3 text-muted-foreground" />
|
|
307
|
+
)}
|
|
308
|
+
</span>
|
|
309
|
+
) : (
|
|
310
|
+
<span className="w-4 shrink-0" />
|
|
311
|
+
)}
|
|
312
|
+
|
|
313
|
+
<span className={cn("shrink-0", isArrayItem ? "text-muted-foreground" : "text-cyan-400")}>
|
|
314
|
+
{isArrayItem ? name : `"${name}"`}
|
|
315
|
+
</span>
|
|
316
|
+
|
|
317
|
+
<span className="text-muted-foreground shrink-0">{expandable ? "" : ":"}</span>
|
|
318
|
+
|
|
319
|
+
{expandable ? (
|
|
320
|
+
<span className="text-muted-foreground">
|
|
321
|
+
{openBracket}
|
|
322
|
+
<span className="text-muted-foreground/60 text-xs">
|
|
323
|
+
{" "}
|
|
324
|
+
{getItemCount(value)} {getItemCount(value) === 1 ? "item" : "items"} {closeBracket}
|
|
325
|
+
</span>
|
|
326
|
+
</span>
|
|
327
|
+
) : (
|
|
328
|
+
<span className="min-w-0">
|
|
329
|
+
<PrimitiveValue value={value} />
|
|
330
|
+
</span>
|
|
331
|
+
)}
|
|
332
|
+
|
|
333
|
+
{expandable && hasExpandableDescendant(value) && (
|
|
334
|
+
<ExpandCollapseButton allExpanded={allExpanded} onClick={handleExpandAll} />
|
|
335
|
+
)}
|
|
336
|
+
<CopyButton value={value} />
|
|
337
|
+
</div>
|
|
338
|
+
|
|
339
|
+
{expandable && expanded && (
|
|
340
|
+
<div className="pl-4" key={childResetKey}>
|
|
341
|
+
{getEntries(value).map(([key, childValue]) => (
|
|
342
|
+
<JsonNode
|
|
343
|
+
key={key}
|
|
344
|
+
name={key}
|
|
345
|
+
value={childValue}
|
|
346
|
+
level={level + 1}
|
|
347
|
+
defaultExpandDepth={effectiveChildDepth}
|
|
348
|
+
isArrayItem={dataType === "array"}
|
|
349
|
+
/>
|
|
350
|
+
))}
|
|
351
|
+
<div className="text-muted-foreground py-0.5 px-1">{closeBracket}</div>
|
|
352
|
+
</div>
|
|
353
|
+
)}
|
|
354
|
+
</div>
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export type JsonViewerProps = {
|
|
359
|
+
data: JsonValue;
|
|
360
|
+
defaultExpandDepth?: number;
|
|
361
|
+
className?: string;
|
|
362
|
+
showCopy?: boolean;
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
export function JsonViewer({
|
|
366
|
+
data,
|
|
367
|
+
defaultExpandDepth = 2,
|
|
368
|
+
className,
|
|
369
|
+
showCopy = false,
|
|
370
|
+
}: JsonViewerProps): JSX.Element {
|
|
371
|
+
const expandable = isExpandable(data);
|
|
372
|
+
|
|
373
|
+
if (!expandable) {
|
|
374
|
+
return (
|
|
375
|
+
<TooltipProvider>
|
|
376
|
+
<div className={cn("font-mono text-xs leading-relaxed", className)}>
|
|
377
|
+
<div className="flex items-center gap-1">
|
|
378
|
+
<PrimitiveValue value={data} />
|
|
379
|
+
{showCopy && <CopyValueButton value={data} />}
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
</TooltipProvider>
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const dataType = classifyValue(data);
|
|
387
|
+
const isArray = dataType === "array";
|
|
388
|
+
|
|
389
|
+
return (
|
|
390
|
+
<TooltipProvider>
|
|
391
|
+
<div className={cn("font-mono text-xs leading-relaxed", className)}>
|
|
392
|
+
{showCopy && <CopyValueButton value={data} />}
|
|
393
|
+
{getEntries(data).map(([key, childValue]) => (
|
|
394
|
+
<JsonNode
|
|
395
|
+
key={key}
|
|
396
|
+
name={key}
|
|
397
|
+
value={childValue}
|
|
398
|
+
level={0}
|
|
399
|
+
defaultExpandDepth={defaultExpandDepth}
|
|
400
|
+
isArrayItem={isArray}
|
|
401
|
+
/>
|
|
402
|
+
))}
|
|
403
|
+
</div>
|
|
404
|
+
</TooltipProvider>
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export type JsonViewerFromStringProps = {
|
|
409
|
+
text: string;
|
|
410
|
+
defaultExpandDepth?: number;
|
|
411
|
+
className?: string;
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
export function JsonViewerFromString({
|
|
415
|
+
text,
|
|
416
|
+
defaultExpandDepth = 2,
|
|
417
|
+
className,
|
|
418
|
+
}: JsonViewerFromStringProps): JSX.Element {
|
|
419
|
+
try {
|
|
420
|
+
const parsed: unknown = JSON.parse(text);
|
|
421
|
+
return (
|
|
422
|
+
<JsonViewer
|
|
423
|
+
data={safeJsonValue(parsed)}
|
|
424
|
+
defaultExpandDepth={defaultExpandDepth}
|
|
425
|
+
className={className}
|
|
426
|
+
/>
|
|
427
|
+
);
|
|
428
|
+
} catch {
|
|
429
|
+
return (
|
|
430
|
+
<pre className={cn("font-mono text-xs whitespace-pre-wrap break-words", className)}>
|
|
431
|
+
{text}
|
|
432
|
+
</pre>
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export function safeJsonValue(value: unknown): JsonValue {
|
|
438
|
+
if (value === null || value === undefined) return null;
|
|
439
|
+
switch (typeof value) {
|
|
440
|
+
case "string":
|
|
441
|
+
return value;
|
|
442
|
+
case "number":
|
|
443
|
+
return value;
|
|
444
|
+
case "boolean":
|
|
445
|
+
return value;
|
|
446
|
+
case "object": {
|
|
447
|
+
if (Array.isArray(value)) {
|
|
448
|
+
return value.map((item: unknown) => safeJsonValue(item));
|
|
449
|
+
}
|
|
450
|
+
const result: Record<string, JsonValue> = {};
|
|
451
|
+
for (const key of Object.keys(value)) {
|
|
452
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
453
|
+
result[key] = safeJsonValue(descriptor?.value);
|
|
454
|
+
}
|
|
455
|
+
return result;
|
|
456
|
+
}
|
|
457
|
+
case "bigint":
|
|
458
|
+
case "symbol":
|
|
459
|
+
case "function":
|
|
460
|
+
case "undefined":
|
|
461
|
+
return String(value);
|
|
462
|
+
}
|
|
463
|
+
return null;
|
|
464
|
+
}
|