@tonyclaw/agent-inspector 2.0.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/cli.js +1611 -0
- package/.output/nitro.json +17 -0
- package/.output/public/assets/CompareDrawer-CU5ZrWcL.js +1 -0
- package/.output/public/assets/ProxyViewerContainer-pEBqVp1d.js +101 -0
- package/.output/public/assets/ReplayDialog-F58yNg5j.js +1 -0
- package/.output/public/assets/RequestAnatomy-C9lT0qE_.js +1 -0
- package/.output/public/assets/ResponseView-DHJq6bnz.js +1 -0
- package/.output/public/assets/StreamingChunkSequence-BTgfpFUT.js +1 -0
- package/.output/public/assets/_sessionId-DsNRbnNm.js +1 -0
- package/.output/public/assets/alibaba-TTwafVwX.svg +1 -0
- package/.output/public/assets/index-CpWG2hFn.css +1 -0
- package/.output/public/assets/index-DmBV8Gve.js +1 -0
- package/.output/public/assets/json-viewer-CZVYLR8j.js +14 -0
- package/.output/public/assets/main-DHs7FBK3.js +18 -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 +22 -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 +2 -0
- package/.output/server/_libs/@radix-ui/react-one-time-password-field+[...].mjs +2 -0
- package/.output/server/_libs/@radix-ui/react-password-toggle-field+[...].mjs +2 -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/cfworker__json-schema.mjs +1 -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/dequal.mjs +27 -0
- package/.output/server/_libs/detect-node-es.mjs +1 -0
- package/.output/server/_libs/devlop.mjs +8 -0
- package/.output/server/_libs/diff.mjs +320 -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 +408 -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 +3051 -0
- package/.output/server/_libs/lie.mjs +273 -0
- package/.output/server/_libs/lucide-react.mjs +492 -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/modelcontextprotocol__server.mjs +9738 -0
- package/.output/server/_libs/ocache.mjs +1 -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 +2 -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 +10781 -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 +14 -0
- package/.output/server/_libs/safe-buffer.mjs +64 -0
- package/.output/server/_libs/semver.mjs +1938 -0
- package/.output/server/_libs/seroval-plugins.mjs +58 -0
- package/.output/server/_libs/seroval.mjs +1765 -0
- package/.output/server/_libs/setimmediate.mjs +152 -0
- package/.output/server/_libs/space-separated-tokens.mjs +6 -0
- package/.output/server/_libs/srvx.mjs +1029 -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/swr.mjs +939 -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 +1 -0
- package/.output/server/_libs/ufo.mjs +54 -0
- package/.output/server/_libs/uint8array-extras.mjs +69 -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 +64 -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 +4524 -0
- package/.output/server/_sessionId-wMLPvC5g.mjs +123 -0
- package/.output/server/_ssr/CompareDrawer-BU4V0uVf.mjs +1041 -0
- package/.output/server/_ssr/ProxyViewerContainer-BnRwFEnn.mjs +5972 -0
- package/.output/server/_ssr/ReplayDialog-C7dn9pd_.mjs +322 -0
- package/.output/server/_ssr/RequestAnatomy-C1rWpe9-.mjs +353 -0
- package/.output/server/_ssr/ResponseView-hGpPaYsf.mjs +602 -0
- package/.output/server/_ssr/StreamingChunkSequence-BRWI1r_G.mjs +302 -0
- package/.output/server/_ssr/index-BKURLVPz.mjs +118 -0
- package/.output/server/_ssr/index.mjs +1184 -0
- package/.output/server/_ssr/json-viewer-BBd2DtQP.mjs +515 -0
- package/.output/server/_ssr/router-BcZ0D6AB.mjs +6317 -0
- package/.output/server/_ssr/start-HYkvq4Ni.mjs +4 -0
- package/.output/server/_tanstack-start-manifest_v-1y8ZVxRI.mjs +4 -0
- package/.output/server/index.mjs +436 -0
- package/.output/server/node_modules/tslib/modules/index.js +70 -0
- package/.output/server/node_modules/tslib/modules/package.json +3 -0
- package/.output/server/node_modules/tslib/package.json +47 -0
- package/.output/server/node_modules/tslib/tslib.js +484 -0
- package/.output/server/package.json +9 -0
- package/LICENSE +21 -0
- package/README.md +52 -0
- package/package.json +110 -0
- package/src/assets/favicon.svg +31 -0
- package/src/assets/logos/alibaba.svg +1 -0
- package/src/assets/logos/anthropic.svg +1 -0
- package/src/assets/logos/claude-code.svg +4 -0
- package/src/assets/logos/deepseek.svg +1 -0
- package/src/assets/logos/mcp.png +0 -0
- package/src/assets/logos/minimax.jpeg +0 -0
- package/src/assets/logos/openai.svg +1 -0
- package/src/assets/logos/opencode.svg +4 -0
- package/src/assets/logos/qwen.png +0 -0
- package/src/assets/logos/zhipuai.svg +219 -0
- package/src/cli/detect-tools.ts +147 -0
- package/src/cli/doctor.ts +521 -0
- package/src/cli/onboard.ts +224 -0
- package/src/cli/templates/command-onboard.ts +17 -0
- package/src/cli/templates/skill-onboard.ts +547 -0
- package/src/cli.ts +345 -0
- package/src/components/OnboardingBanner.tsx +67 -0
- package/src/components/ProxyViewer.tsx +545 -0
- package/src/components/ProxyViewerContainer.tsx +363 -0
- package/src/components/providers/ImportWizardDialog.tsx +349 -0
- package/src/components/providers/ProviderCard.tsx +474 -0
- package/src/components/providers/ProviderForm.tsx +494 -0
- package/src/components/providers/ProviderLogo.tsx +117 -0
- package/src/components/providers/ProvidersPanel.tsx +619 -0
- package/src/components/providers/SettingsDialog.tsx +202 -0
- package/src/components/proxy-viewer/CompareDrawer.tsx +893 -0
- package/src/components/proxy-viewer/ConversationGroup.tsx +107 -0
- package/src/components/proxy-viewer/ConversationHeader.tsx +300 -0
- package/src/components/proxy-viewer/LogEntry.tsx +543 -0
- package/src/components/proxy-viewer/LogEntryHeader.tsx +501 -0
- package/src/components/proxy-viewer/ReplayDialog.tsx +218 -0
- package/src/components/proxy-viewer/ResponseView.tsx +171 -0
- package/src/components/proxy-viewer/StreamingChunkSequence.tsx +188 -0
- package/src/components/proxy-viewer/ThreadConnector.tsx +136 -0
- package/src/components/proxy-viewer/TurnGroup.tsx +337 -0
- package/src/components/proxy-viewer/anatomy/RequestAnatomy.tsx +98 -0
- package/src/components/proxy-viewer/anatomy/SegmentBar.tsx +196 -0
- package/src/components/proxy-viewer/anatomy/tokenEstimate.ts +53 -0
- package/src/components/proxy-viewer/anatomy/types.ts +39 -0
- package/src/components/proxy-viewer/anatomy/useAnatomyJump.ts +114 -0
- package/src/components/proxy-viewer/cacheTrend.ts +50 -0
- package/src/components/proxy-viewer/diff/DiffView.tsx +321 -0
- package/src/components/proxy-viewer/diff/computeDiff.ts +178 -0
- package/src/components/proxy-viewer/diff/index.ts +3 -0
- package/src/components/proxy-viewer/formats/anthropic/ContentBlocks.tsx +157 -0
- package/src/components/proxy-viewer/formats/anthropic/ResponseView.tsx +66 -0
- package/src/components/proxy-viewer/formats/anthropic/thinkingExtract.ts +21 -0
- package/src/components/proxy-viewer/formats/index.tsx +33 -0
- package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +170 -0
- package/src/components/proxy-viewer/index.ts +9 -0
- package/src/components/proxy-viewer/lazy.ts +37 -0
- package/src/components/proxy-viewer/log-formats/anthropic.ts +194 -0
- package/src/components/proxy-viewer/log-formats/index.ts +23 -0
- package/src/components/proxy-viewer/log-formats/openai.ts +167 -0
- package/src/components/proxy-viewer/log-formats/types.ts +40 -0
- package/src/components/proxy-viewer/log-formats/unknown.ts +18 -0
- package/src/components/proxy-viewer/logEntryVisibility.ts +39 -0
- package/src/components/proxy-viewer/requestDiff.ts +277 -0
- package/src/components/proxy-viewer/useCopyFeedback.ts +36 -0
- package/src/components/proxy-viewer/useKeyboardNavigation.ts +190 -0
- package/src/components/proxy-viewer/viewerState.ts +66 -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/confirm-dialog.tsx +51 -0
- package/src/components/ui/crab-logo.tsx +95 -0
- package/src/components/ui/crab-variants.tsx +467 -0
- package/src/components/ui/dialog.tsx +129 -0
- package/src/components/ui/json-expansion-button.tsx +56 -0
- package/src/components/ui/json-viewer-bulk.ts +97 -0
- package/src/components/ui/json-viewer.tsx +494 -0
- package/src/components/ui/mcp-logo.tsx +20 -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/knowledge/candidateStore.ts +63 -0
- package/src/knowledge/distiller.ts +98 -0
- package/src/knowledge/openclawClient.ts +118 -0
- package/src/knowledge/redactor.ts +80 -0
- package/src/knowledge/types.ts +84 -0
- package/src/lib/apiClient.ts +49 -0
- package/src/lib/export-logs.ts +51 -0
- package/src/lib/mask.ts +4 -0
- package/src/lib/objectUtils.ts +22 -0
- package/src/lib/providerContract.ts +26 -0
- package/src/lib/providerTestContract.ts +107 -0
- package/src/lib/runtimeConfig.ts +25 -0
- package/src/lib/serverPort.ts +41 -0
- package/src/lib/sessionUrl.ts +44 -0
- package/src/lib/stopReason.ts +58 -0
- package/src/lib/useOnboarding.ts +80 -0
- package/src/lib/useProviders.ts +30 -0
- package/src/lib/useStripConfig.ts +108 -0
- package/src/lib/utils.ts +21 -0
- package/src/mcp/loopback.ts +76 -0
- package/src/mcp/previewExtractor.ts +166 -0
- package/src/mcp/server.ts +396 -0
- package/src/mcp/toolHandlers.ts +341 -0
- package/src/proxy/chunkStorage.ts +112 -0
- package/src/proxy/claudeCodeStrip.ts +99 -0
- package/src/proxy/config.ts +172 -0
- package/src/proxy/constants.ts +47 -0
- package/src/proxy/dataDir.ts +86 -0
- package/src/proxy/formats/anthropic/anthropicProvider.ts +75 -0
- package/src/proxy/formats/anthropic/handler.ts +71 -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 +237 -0
- package/src/proxy/formats/anthropic/stream.ts +205 -0
- package/src/proxy/formats/handler.ts +46 -0
- package/src/proxy/formats/index.ts +12 -0
- package/src/proxy/formats/jsonSchema.ts +36 -0
- package/src/proxy/formats/openai/alibabaProvider.ts +38 -0
- package/src/proxy/formats/openai/handler.ts +96 -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 +187 -0
- package/src/proxy/formats/openai/stream.ts +206 -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 +66 -0
- package/src/proxy/handler.ts +334 -0
- package/src/proxy/logFinalizer.ts +305 -0
- package/src/proxy/logFinalizer.worker.ts +24 -0
- package/src/proxy/logIndex.ts +268 -0
- package/src/proxy/logger.ts +179 -0
- package/src/proxy/openaiOrphanToolStrip.ts +142 -0
- package/src/proxy/providerImporters.ts +491 -0
- package/src/proxy/providers.ts +613 -0
- package/src/proxy/schemas.ts +209 -0
- package/src/proxy/sessionProcess.ts +140 -0
- package/src/proxy/sessionRuntime.ts +85 -0
- package/src/proxy/sessionSupervisor.ts +283 -0
- package/src/proxy/sessionWorkerEntry.ts +26 -0
- package/src/proxy/socketTracker.ts +255 -0
- package/src/proxy/store.ts +412 -0
- package/src/proxy/upstream.ts +90 -0
- package/src/router.tsx +16 -0
- package/src/routes/__root.tsx +45 -0
- package/src/routes/api/config.paths.ts +14 -0
- package/src/routes/api/config.ts +53 -0
- package/src/routes/api/health.ts +15 -0
- package/src/routes/api/knowledge.candidates.$candidateId.promote.ts +32 -0
- package/src/routes/api/knowledge.candidates.ts +10 -0
- package/src/routes/api/knowledge.project-context.ts +18 -0
- package/src/routes/api/knowledge.search.ts +31 -0
- package/src/routes/api/knowledge.sessions.$sessionId.candidates.ts +16 -0
- package/src/routes/api/logs.$id.chunks.ts +36 -0
- package/src/routes/api/logs.$id.replay.ts +191 -0
- package/src/routes/api/logs.$id.ts +22 -0
- package/src/routes/api/logs.stream.ts +74 -0
- package/src/routes/api/logs.ts +59 -0
- package/src/routes/api/mcp.ts +25 -0
- package/src/routes/api/models.ts +10 -0
- package/src/routes/api/providers.$providerId.test.log.ts +293 -0
- package/src/routes/api/providers.$providerId.ts +50 -0
- package/src/routes/api/providers.export.ts +26 -0
- package/src/routes/api/providers.import.ts +47 -0
- package/src/routes/api/providers.scan.ts +23 -0
- package/src/routes/api/providers.ts +45 -0
- package/src/routes/api/sessions.ts +17 -0
- package/src/routes/index.tsx +6 -0
- package/src/routes/proxy/$.ts +15 -0
- package/src/routes/session/$sessionId.tsx +23 -0
- package/styles/globals.css +188 -0
|
@@ -0,0 +1,893 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import type { JSX } from "react";
|
|
3
|
+
import {
|
|
4
|
+
Check,
|
|
5
|
+
ChevronRight,
|
|
6
|
+
Columns2,
|
|
7
|
+
Copy,
|
|
8
|
+
Equal,
|
|
9
|
+
Minus,
|
|
10
|
+
Pencil,
|
|
11
|
+
Plus,
|
|
12
|
+
Rows3,
|
|
13
|
+
X,
|
|
14
|
+
} from "lucide-react";
|
|
15
|
+
import { cn, formatTokens } from "../../lib/utils";
|
|
16
|
+
import type { CapturedLog } from "../../proxy/schemas";
|
|
17
|
+
import {
|
|
18
|
+
type DiffOp,
|
|
19
|
+
type JsonNode,
|
|
20
|
+
diffTrees,
|
|
21
|
+
normalizeRequest,
|
|
22
|
+
previewNode,
|
|
23
|
+
} from "./requestDiff";
|
|
24
|
+
import { getConversationId } from "./ConversationHeader";
|
|
25
|
+
import { JsonViewerFromString } from "../ui/json-viewer";
|
|
26
|
+
import { Badge } from "../ui/badge";
|
|
27
|
+
import { getLogFormatAdapter, resolveLogFormat } from "./log-formats";
|
|
28
|
+
|
|
29
|
+
export type CompareDrawerProps = {
|
|
30
|
+
/** Log selected first (shown on the left). */
|
|
31
|
+
left: CapturedLog;
|
|
32
|
+
/** Log selected second (shown on the right). */
|
|
33
|
+
right: CapturedLog;
|
|
34
|
+
onClose: () => void;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type EqualOp = Extract<DiffOp, { kind: "equal" }>;
|
|
38
|
+
type AddedOp = Extract<DiffOp, { kind: "added" }>;
|
|
39
|
+
type RemovedOp = Extract<DiffOp, { kind: "removed" }>;
|
|
40
|
+
type ChangedOp = Extract<DiffOp, { kind: "changed" }>;
|
|
41
|
+
|
|
42
|
+
type DiffMode = "unified" | "split";
|
|
43
|
+
|
|
44
|
+
/** Walk the JsonNode tree and pretty-print it back to a JSON string for the
|
|
45
|
+
* expanded-equal-subtree view. The node is a plain object structure so
|
|
46
|
+
* `JSON.stringify` produces correct output. */
|
|
47
|
+
function nodeToJsonString(node: JsonNode, indent = 2): string {
|
|
48
|
+
return JSON.stringify(nodeToJsonValue(node), null, indent);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function nodeToJsonValue(node: JsonNode): unknown {
|
|
52
|
+
switch (node.kind) {
|
|
53
|
+
case "primitive":
|
|
54
|
+
return node.value;
|
|
55
|
+
case "array":
|
|
56
|
+
return node.value.map(nodeToJsonValue);
|
|
57
|
+
case "object": {
|
|
58
|
+
const out: Record<string, unknown> = {};
|
|
59
|
+
for (const [k, v] of Object.entries(node.value)) {
|
|
60
|
+
out[k] = nodeToJsonValue(v);
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The parent path of a JSON path string. E.g. `messages[3].content` →
|
|
68
|
+
* `messages[3]`, `messages[3]` → `messages`, `messages` → `""`. */
|
|
69
|
+
function parentPath(path: string): string {
|
|
70
|
+
if (path === "") return "";
|
|
71
|
+
for (let i = path.length - 1; i >= 0; i--) {
|
|
72
|
+
const ch = path[i];
|
|
73
|
+
if (ch === "." || ch === "[") {
|
|
74
|
+
return path.substring(0, i);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return "";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Group contiguous deep-equal ops (object/array, not primitive) that share
|
|
81
|
+
* a common parent into a single collapsed row, so an unchanged block of N
|
|
82
|
+
* sibling subtrees renders as one row instead of N. Primitive equals
|
|
83
|
+
* always render as their own row (they're 1 line each). */
|
|
84
|
+
type GroupedOp = { kind: "single"; op: DiffOp } | { kind: "equal-run"; ops: EqualOp[] };
|
|
85
|
+
|
|
86
|
+
function isDeepEqual(op: DiffOp): op is EqualOp {
|
|
87
|
+
return op.kind === "equal" && (op.value.kind === "object" || op.value.kind === "array");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function groupContiguousEquals(ops: DiffOp[]): GroupedOp[] {
|
|
91
|
+
const out: GroupedOp[] = [];
|
|
92
|
+
let i = 0;
|
|
93
|
+
while (i < ops.length) {
|
|
94
|
+
const op = ops[i];
|
|
95
|
+
if (op !== undefined && isDeepEqual(op)) {
|
|
96
|
+
const startParent = parentPath(op.path);
|
|
97
|
+
let j = i + 1;
|
|
98
|
+
while (j < ops.length) {
|
|
99
|
+
const next = ops[j];
|
|
100
|
+
if (next === undefined) break;
|
|
101
|
+
if (!isDeepEqual(next)) break;
|
|
102
|
+
if (parentPath(next.path) !== startParent) break;
|
|
103
|
+
j++;
|
|
104
|
+
}
|
|
105
|
+
if (j - i > 1) {
|
|
106
|
+
const equalOps: EqualOp[] = [];
|
|
107
|
+
for (let k = i; k < j; k++) {
|
|
108
|
+
const eop = ops[k];
|
|
109
|
+
if (eop !== undefined && eop.kind === "equal") {
|
|
110
|
+
equalOps.push(eop);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
out.push({ kind: "equal-run", ops: equalOps });
|
|
114
|
+
i = j;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (op !== undefined) {
|
|
119
|
+
out.push({ kind: "single", op });
|
|
120
|
+
}
|
|
121
|
+
i++;
|
|
122
|
+
}
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Visual configuration for each diff kind. Centralized so the unified row,
|
|
127
|
+
* summary chips, and split columns all use the same colors / icons. */
|
|
128
|
+
type DiffKind = "added" | "removed" | "changed" | "equal";
|
|
129
|
+
|
|
130
|
+
const KIND_VISUAL: Record<
|
|
131
|
+
DiffKind,
|
|
132
|
+
{
|
|
133
|
+
icon: typeof Plus;
|
|
134
|
+
/** Accent color for the icon + label. */
|
|
135
|
+
accent: string;
|
|
136
|
+
/** Background tint applied to the row. */
|
|
137
|
+
bg: string;
|
|
138
|
+
/** Thick left-border color. */
|
|
139
|
+
border: string;
|
|
140
|
+
/** Short text label (uppercase, used in the row badge). */
|
|
141
|
+
label: string;
|
|
142
|
+
}
|
|
143
|
+
> = {
|
|
144
|
+
added: {
|
|
145
|
+
icon: Plus,
|
|
146
|
+
accent: "text-emerald-600 dark:text-emerald-400",
|
|
147
|
+
bg: "bg-emerald-500/5 hover:bg-emerald-500/10",
|
|
148
|
+
border: "border-l-emerald-500",
|
|
149
|
+
label: "ADDED",
|
|
150
|
+
},
|
|
151
|
+
removed: {
|
|
152
|
+
icon: Minus,
|
|
153
|
+
accent: "text-rose-600 dark:text-rose-400",
|
|
154
|
+
bg: "bg-rose-500/5 hover:bg-rose-500/10",
|
|
155
|
+
border: "border-l-rose-500",
|
|
156
|
+
label: "REMOVED",
|
|
157
|
+
},
|
|
158
|
+
changed: {
|
|
159
|
+
icon: Pencil,
|
|
160
|
+
accent: "text-amber-600 dark:text-amber-400",
|
|
161
|
+
bg: "bg-amber-500/5 hover:bg-amber-500/10",
|
|
162
|
+
border: "border-l-amber-500",
|
|
163
|
+
label: "CHANGED",
|
|
164
|
+
},
|
|
165
|
+
equal: {
|
|
166
|
+
icon: Equal,
|
|
167
|
+
accent: "text-muted-foreground/70",
|
|
168
|
+
bg: "bg-muted/20 hover:bg-muted/30",
|
|
169
|
+
border: "border-l-muted-foreground/20",
|
|
170
|
+
label: "EQUAL",
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
function EqualRunRow({
|
|
175
|
+
ops,
|
|
176
|
+
expanded,
|
|
177
|
+
onToggle,
|
|
178
|
+
}: {
|
|
179
|
+
ops: EqualOp[];
|
|
180
|
+
expanded: boolean;
|
|
181
|
+
onToggle: () => void;
|
|
182
|
+
}): JSX.Element {
|
|
183
|
+
const first = ops[0];
|
|
184
|
+
const last = ops[ops.length - 1];
|
|
185
|
+
if (first === undefined || last === undefined) {
|
|
186
|
+
return <div className="text-muted-foreground/40 text-xs">—</div>;
|
|
187
|
+
}
|
|
188
|
+
const firstPath = first.path;
|
|
189
|
+
const lastPath = last.path;
|
|
190
|
+
const label = ops.length === 1 ? firstPath : `${firstPath} … ${lastPath}`;
|
|
191
|
+
const summary =
|
|
192
|
+
first.value.kind === "array"
|
|
193
|
+
? `${ops.length} equal arrays`
|
|
194
|
+
: first.value.kind === "object"
|
|
195
|
+
? `${ops.length} equal objects`
|
|
196
|
+
: "equal";
|
|
197
|
+
|
|
198
|
+
const v = KIND_VISUAL.equal;
|
|
199
|
+
|
|
200
|
+
return (
|
|
201
|
+
<div className={cn("border-l-4 rounded-sm", v.border, v.bg)}>
|
|
202
|
+
<button
|
|
203
|
+
type="button"
|
|
204
|
+
onClick={onToggle}
|
|
205
|
+
className="w-full text-left flex items-center gap-2 px-3 py-1.5 text-xs text-muted-foreground cursor-pointer"
|
|
206
|
+
>
|
|
207
|
+
<ChevronRight
|
|
208
|
+
className={cn("size-3 transition-transform shrink-0", expanded && "rotate-90")}
|
|
209
|
+
/>
|
|
210
|
+
<v.icon className={cn("size-3 shrink-0", v.accent)} />
|
|
211
|
+
<span className="font-mono truncate flex-1" title={`${firstPath} … ${lastPath}`}>
|
|
212
|
+
{label}
|
|
213
|
+
</span>
|
|
214
|
+
<span className={cn("text-[10px] uppercase tracking-wider shrink-0", v.accent)}>
|
|
215
|
+
{v.label}
|
|
216
|
+
</span>
|
|
217
|
+
<span className="text-muted-foreground/60 shrink-0">({summary})</span>
|
|
218
|
+
</button>
|
|
219
|
+
{expanded && (
|
|
220
|
+
<div className="ml-5 mt-1 mb-2 space-y-2 pr-2">
|
|
221
|
+
{ops.map((op) => (
|
|
222
|
+
<div key={op.path} className="border border-border/50 rounded p-2 bg-muted/20">
|
|
223
|
+
<div className="font-mono text-xs text-muted-foreground mb-1">{op.path}</div>
|
|
224
|
+
<JsonViewerFromString text={nodeToJsonString(op.value)} defaultExpandDepth={0} />
|
|
225
|
+
</div>
|
|
226
|
+
))}
|
|
227
|
+
</div>
|
|
228
|
+
)}
|
|
229
|
+
</div>
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** A single diff row in the unified view. Renders the icon, path, kind label,
|
|
234
|
+
* value preview, and a copy-path button. `data-diff-idx` lets the parent
|
|
235
|
+
* scroll the row into view when a summary chip is clicked.
|
|
236
|
+
*
|
|
237
|
+
* When `expanded` is true (controlled by the parent), renders the full
|
|
238
|
+
* added / removed / changed subtree below the row. Primitive changes and
|
|
239
|
+
* primitive equal values are not expandable (the value is already fully
|
|
240
|
+
* shown on the row). */
|
|
241
|
+
function UnifiedOpRow({
|
|
242
|
+
op,
|
|
243
|
+
idx,
|
|
244
|
+
copiedPath,
|
|
245
|
+
onCopyPath,
|
|
246
|
+
expanded,
|
|
247
|
+
onToggle,
|
|
248
|
+
}: {
|
|
249
|
+
op: AddedOp | RemovedOp | ChangedOp | EqualOp;
|
|
250
|
+
idx: number;
|
|
251
|
+
copiedPath: string | null;
|
|
252
|
+
onCopyPath: (path: string) => void;
|
|
253
|
+
expanded: boolean;
|
|
254
|
+
onToggle: () => void;
|
|
255
|
+
}): JSX.Element {
|
|
256
|
+
const v = KIND_VISUAL[op.kind];
|
|
257
|
+
const Icon = v.icon;
|
|
258
|
+
|
|
259
|
+
// True iff this row has a useful subtree to drill into. Primitives (and
|
|
260
|
+
// the root "equal" pseudo-op) have no extra detail beyond the preview.
|
|
261
|
+
const isExpandable =
|
|
262
|
+
op.kind === "added" || op.kind === "removed"
|
|
263
|
+
? op.value.kind === "object" || op.value.kind === "array"
|
|
264
|
+
: op.kind === "changed"
|
|
265
|
+
? op.left.kind === "object" ||
|
|
266
|
+
op.left.kind === "array" ||
|
|
267
|
+
op.right.kind === "object" ||
|
|
268
|
+
op.right.kind === "array"
|
|
269
|
+
: false; // equal (single primitive) — preview is the whole value
|
|
270
|
+
|
|
271
|
+
// For changed rows, show the left and right previews stacked. For added /
|
|
272
|
+
// removed / equal, one preview is enough.
|
|
273
|
+
const preview =
|
|
274
|
+
op.kind === "changed"
|
|
275
|
+
? [
|
|
276
|
+
{
|
|
277
|
+
text: previewNode(op.left, 400),
|
|
278
|
+
tone: "text-rose-700 dark:text-rose-300 line-through",
|
|
279
|
+
},
|
|
280
|
+
{ text: previewNode(op.right, 400), tone: "text-emerald-700 dark:text-emerald-300" },
|
|
281
|
+
]
|
|
282
|
+
: op.kind === "removed"
|
|
283
|
+
? [
|
|
284
|
+
{
|
|
285
|
+
text: previewNode(op.value, 400),
|
|
286
|
+
tone: "text-rose-700 dark:text-rose-300 line-through",
|
|
287
|
+
},
|
|
288
|
+
]
|
|
289
|
+
: op.kind === "added"
|
|
290
|
+
? [{ text: previewNode(op.value, 400), tone: "text-emerald-700 dark:text-emerald-300" }]
|
|
291
|
+
: [{ text: previewNode(op.value, 400), tone: "text-muted-foreground" }];
|
|
292
|
+
|
|
293
|
+
const justCopied = copiedPath === op.path && op.path !== "";
|
|
294
|
+
|
|
295
|
+
return (
|
|
296
|
+
<div
|
|
297
|
+
data-diff-idx={idx}
|
|
298
|
+
data-diff-kind={op.kind}
|
|
299
|
+
className={cn("border-l-4 rounded-sm px-3 py-2 my-0.5 transition-colors", v.border, v.bg)}
|
|
300
|
+
>
|
|
301
|
+
<button
|
|
302
|
+
type="button"
|
|
303
|
+
onClick={onToggle}
|
|
304
|
+
disabled={!isExpandable}
|
|
305
|
+
className={cn(
|
|
306
|
+
"w-full flex items-center gap-2 text-xs text-left rounded-sm",
|
|
307
|
+
isExpandable ? "cursor-pointer" : "cursor-default",
|
|
308
|
+
)}
|
|
309
|
+
aria-expanded={isExpandable ? expanded : undefined}
|
|
310
|
+
aria-label={
|
|
311
|
+
isExpandable
|
|
312
|
+
? expanded
|
|
313
|
+
? `Collapse ${op.path || "root"}`
|
|
314
|
+
: `Expand ${op.path || "root"}`
|
|
315
|
+
: undefined
|
|
316
|
+
}
|
|
317
|
+
>
|
|
318
|
+
{isExpandable ? (
|
|
319
|
+
<ChevronRight
|
|
320
|
+
className={cn(
|
|
321
|
+
"size-3 shrink-0 transition-transform",
|
|
322
|
+
v.accent,
|
|
323
|
+
expanded && "rotate-90",
|
|
324
|
+
)}
|
|
325
|
+
/>
|
|
326
|
+
) : (
|
|
327
|
+
<span className="size-3 shrink-0" aria-hidden="true" />
|
|
328
|
+
)}
|
|
329
|
+
<Icon className={cn("size-3.5 shrink-0", v.accent)} strokeWidth={2.5} />
|
|
330
|
+
<span className="font-mono truncate flex-1 min-w-0" title={op.path || "(root)"}>
|
|
331
|
+
{op.path === "" ? "(root)" : op.path}
|
|
332
|
+
</span>
|
|
333
|
+
<span
|
|
334
|
+
className={cn(
|
|
335
|
+
"text-[9px] font-bold uppercase tracking-wider shrink-0 px-1.5 py-0.5 rounded",
|
|
336
|
+
v.accent,
|
|
337
|
+
op.kind === "equal" ? "bg-muted/40" : "bg-background/60",
|
|
338
|
+
)}
|
|
339
|
+
>
|
|
340
|
+
{v.label}
|
|
341
|
+
</span>
|
|
342
|
+
{op.path !== "" && (
|
|
343
|
+
<span
|
|
344
|
+
role="button"
|
|
345
|
+
tabIndex={0}
|
|
346
|
+
onClick={(e) => {
|
|
347
|
+
e.stopPropagation();
|
|
348
|
+
onCopyPath(op.path);
|
|
349
|
+
}}
|
|
350
|
+
onKeyDown={(e) => {
|
|
351
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
352
|
+
e.stopPropagation();
|
|
353
|
+
e.preventDefault();
|
|
354
|
+
onCopyPath(op.path);
|
|
355
|
+
}
|
|
356
|
+
}}
|
|
357
|
+
className={cn(
|
|
358
|
+
"shrink-0 p-1 rounded transition-colors cursor-pointer inline-flex items-center justify-center",
|
|
359
|
+
justCopied
|
|
360
|
+
? "text-emerald-500"
|
|
361
|
+
: "text-muted-foreground/50 hover:text-foreground hover:bg-muted",
|
|
362
|
+
)}
|
|
363
|
+
aria-label={justCopied ? "Copied" : "Copy"}
|
|
364
|
+
title={justCopied ? "Copied!" : "Copy"}
|
|
365
|
+
>
|
|
366
|
+
{justCopied ? <Check className="size-3" /> : <Copy className="size-3" />}
|
|
367
|
+
</span>
|
|
368
|
+
)}
|
|
369
|
+
</button>
|
|
370
|
+
{preview.map((p, i) => (
|
|
371
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: preview list is rebuilt on every render and is positional
|
|
372
|
+
<div key={i} className={cn("font-mono text-xs mt-1 break-all pl-5", p.tone)}>
|
|
373
|
+
{p.text}
|
|
374
|
+
</div>
|
|
375
|
+
))}
|
|
376
|
+
<div
|
|
377
|
+
className="overflow-hidden transition-all duration-200"
|
|
378
|
+
style={{ maxHeight: expanded && isExpandable ? "2000px" : "0" }}
|
|
379
|
+
aria-hidden={!expanded}
|
|
380
|
+
>
|
|
381
|
+
{expanded && isExpandable && op.kind !== "equal" ? <ExpandedSubtree op={op} /> : null}
|
|
382
|
+
</div>
|
|
383
|
+
</div>
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/** Render the full subtree for an expanded diff row.
|
|
388
|
+
* - added / removed: a single JsonViewerFromString for the value.
|
|
389
|
+
* - changed (object/array on either side): two viewers side by side.
|
|
390
|
+
* - changed (both primitives): a small "values are shown above" note. */
|
|
391
|
+
function ExpandedSubtree({ op }: { op: AddedOp | RemovedOp | ChangedOp }): JSX.Element {
|
|
392
|
+
if (op.kind === "added" || op.kind === "removed") {
|
|
393
|
+
return (
|
|
394
|
+
<div className="pl-5 mt-2 border border-border/50 rounded p-2 bg-muted/20">
|
|
395
|
+
<JsonViewerFromString text={nodeToJsonString(op.value)} defaultExpandDepth={0} />
|
|
396
|
+
</div>
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
// changed
|
|
400
|
+
const leftIsStructured = op.left.kind === "object" || op.left.kind === "array";
|
|
401
|
+
const rightIsStructured = op.right.kind === "object" || op.right.kind === "array";
|
|
402
|
+
if (!leftIsStructured && !rightIsStructured) {
|
|
403
|
+
return (
|
|
404
|
+
<div className="pl-5 mt-2 text-xs text-muted-foreground/70 italic">
|
|
405
|
+
Primitive values are shown inline above.
|
|
406
|
+
</div>
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
return (
|
|
410
|
+
<div className="pl-5 mt-2 grid grid-cols-1 md:grid-cols-2 gap-2">
|
|
411
|
+
<div className="border border-rose-500/30 rounded p-2 bg-rose-500/5">
|
|
412
|
+
<div className="text-[10px] uppercase tracking-wider text-rose-500 mb-1">Old</div>
|
|
413
|
+
<JsonViewerFromString text={nodeToJsonString(op.left)} defaultExpandDepth={0} />
|
|
414
|
+
</div>
|
|
415
|
+
<div className="border border-emerald-500/30 rounded p-2 bg-emerald-500/5">
|
|
416
|
+
<div className="text-[10px] uppercase tracking-wider text-emerald-500 mb-1">New</div>
|
|
417
|
+
<JsonViewerFromString text={nodeToJsonString(op.right)} defaultExpandDepth={0} />
|
|
418
|
+
</div>
|
|
419
|
+
</div>
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/** Summary chips at the top of the body. Each chip is a button that scrolls
|
|
424
|
+
* the body to the first op of that kind. Disabled when the count is 0. */
|
|
425
|
+
function SummaryChips({
|
|
426
|
+
counts,
|
|
427
|
+
onJumpTo,
|
|
428
|
+
}: {
|
|
429
|
+
counts: { added: number; removed: number; changed: number };
|
|
430
|
+
onJumpTo: (kind: "added" | "removed" | "changed") => void;
|
|
431
|
+
}): JSX.Element {
|
|
432
|
+
const total = counts.added + counts.removed + counts.changed;
|
|
433
|
+
return (
|
|
434
|
+
<div className="px-4 py-2 border-b border-border bg-muted/20 flex items-center gap-2 text-xs flex-wrap">
|
|
435
|
+
<span className="text-muted-foreground font-medium">
|
|
436
|
+
{total} {total === 1 ? "change" : "changes"}
|
|
437
|
+
</span>
|
|
438
|
+
<button
|
|
439
|
+
type="button"
|
|
440
|
+
onClick={() => onJumpTo("removed")}
|
|
441
|
+
disabled={counts.removed === 0}
|
|
442
|
+
className={cn(
|
|
443
|
+
"inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",
|
|
444
|
+
counts.removed > 0
|
|
445
|
+
? "border-rose-500/40 text-rose-600 dark:text-rose-400 bg-rose-500/10 hover:bg-rose-500/20"
|
|
446
|
+
: "border-border text-muted-foreground/40 cursor-not-allowed",
|
|
447
|
+
)}
|
|
448
|
+
title={counts.removed > 0 ? "Jump to first removed" : "No removals"}
|
|
449
|
+
>
|
|
450
|
+
<Minus className="size-3" />
|
|
451
|
+
{counts.removed} removed
|
|
452
|
+
</button>
|
|
453
|
+
<button
|
|
454
|
+
type="button"
|
|
455
|
+
onClick={() => onJumpTo("added")}
|
|
456
|
+
disabled={counts.added === 0}
|
|
457
|
+
className={cn(
|
|
458
|
+
"inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",
|
|
459
|
+
counts.added > 0
|
|
460
|
+
? "border-emerald-500/40 text-emerald-600 dark:text-emerald-400 bg-emerald-500/10 hover:bg-emerald-500/20"
|
|
461
|
+
: "border-border text-muted-foreground/40 cursor-not-allowed",
|
|
462
|
+
)}
|
|
463
|
+
title={counts.added > 0 ? "Jump to first added" : "No additions"}
|
|
464
|
+
>
|
|
465
|
+
<Plus className="size-3" />
|
|
466
|
+
{counts.added} added
|
|
467
|
+
</button>
|
|
468
|
+
<button
|
|
469
|
+
type="button"
|
|
470
|
+
onClick={() => onJumpTo("changed")}
|
|
471
|
+
disabled={counts.changed === 0}
|
|
472
|
+
className={cn(
|
|
473
|
+
"inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",
|
|
474
|
+
counts.changed > 0
|
|
475
|
+
? "border-amber-500/40 text-amber-600 dark:text-amber-400 bg-amber-500/10 hover:bg-amber-500/20"
|
|
476
|
+
: "border-border text-muted-foreground/40 cursor-not-allowed",
|
|
477
|
+
)}
|
|
478
|
+
title={counts.changed > 0 ? "Jump to first changed" : "No changes"}
|
|
479
|
+
>
|
|
480
|
+
<Pencil className="size-3" />
|
|
481
|
+
{counts.changed} changed
|
|
482
|
+
</button>
|
|
483
|
+
</div>
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/** Mode toggle in the header: unified (single column) or split (path | left | right). */
|
|
488
|
+
function ModeToggle({
|
|
489
|
+
mode,
|
|
490
|
+
onChange,
|
|
491
|
+
}: {
|
|
492
|
+
mode: DiffMode;
|
|
493
|
+
onChange: (mode: DiffMode) => void;
|
|
494
|
+
}): JSX.Element {
|
|
495
|
+
return (
|
|
496
|
+
<div className="inline-flex rounded-md border border-border overflow-hidden">
|
|
497
|
+
<button
|
|
498
|
+
type="button"
|
|
499
|
+
onClick={() => onChange("unified")}
|
|
500
|
+
aria-pressed={mode === "unified"}
|
|
501
|
+
className={cn(
|
|
502
|
+
"flex items-center gap-1 px-2 py-1 text-xs transition-colors cursor-pointer",
|
|
503
|
+
mode === "unified"
|
|
504
|
+
? "bg-muted text-foreground"
|
|
505
|
+
: "hover:bg-muted/50 text-muted-foreground",
|
|
506
|
+
)}
|
|
507
|
+
title="Unified view (single column, emphasized diffs)"
|
|
508
|
+
>
|
|
509
|
+
<Rows3 className="size-3" />
|
|
510
|
+
Unified
|
|
511
|
+
</button>
|
|
512
|
+
<button
|
|
513
|
+
type="button"
|
|
514
|
+
onClick={() => onChange("split")}
|
|
515
|
+
aria-pressed={mode === "split"}
|
|
516
|
+
className={cn(
|
|
517
|
+
"flex items-center gap-1 px-2 py-1 text-xs transition-colors border-l border-border cursor-pointer",
|
|
518
|
+
mode === "split" ? "bg-muted text-foreground" : "hover:bg-muted/50 text-muted-foreground",
|
|
519
|
+
)}
|
|
520
|
+
title="Split view (path | left | right)"
|
|
521
|
+
>
|
|
522
|
+
<Columns2 className="size-3" />
|
|
523
|
+
Split
|
|
524
|
+
</button>
|
|
525
|
+
</div>
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function SideSummary({ log, side }: { log: CapturedLog; side: "left" | "right" }): JSX.Element {
|
|
530
|
+
const conversationId = getConversationId(log);
|
|
531
|
+
return (
|
|
532
|
+
<div className="flex-1 min-w-0 space-y-1 text-xs">
|
|
533
|
+
<div className="flex items-center gap-2">
|
|
534
|
+
<Badge
|
|
535
|
+
variant="outline"
|
|
536
|
+
className={cn(
|
|
537
|
+
"text-[10px] px-1.5 py-0 h-5 font-mono shrink-0",
|
|
538
|
+
side === "left"
|
|
539
|
+
? "border-rose-500/40 text-rose-400"
|
|
540
|
+
: "border-emerald-500/40 text-emerald-400",
|
|
541
|
+
)}
|
|
542
|
+
>
|
|
543
|
+
{side === "left" ? "← Left" : "Right →"}
|
|
544
|
+
</Badge>
|
|
545
|
+
<span className="font-mono text-blue-400/80">#{log.id}</span>
|
|
546
|
+
{log.model !== null && (
|
|
547
|
+
<span className="font-mono text-muted-foreground truncate">{log.model}</span>
|
|
548
|
+
)}
|
|
549
|
+
</div>
|
|
550
|
+
<div className="flex items-center gap-3 text-muted-foreground font-mono">
|
|
551
|
+
{log.cacheCreationInputTokens !== null && log.cacheCreationInputTokens > 0 && (
|
|
552
|
+
<span className="text-emerald-400">
|
|
553
|
+
Cache +{formatTokens(log.cacheCreationInputTokens)}
|
|
554
|
+
</span>
|
|
555
|
+
)}
|
|
556
|
+
{log.cacheReadInputTokens !== null && log.cacheReadInputTokens > 0 && (
|
|
557
|
+
<span className="text-purple-400">Cache ~{formatTokens(log.cacheReadInputTokens)}</span>
|
|
558
|
+
)}
|
|
559
|
+
<span className="truncate" title={log.timestamp}>
|
|
560
|
+
{log.timestamp}
|
|
561
|
+
</span>
|
|
562
|
+
</div>
|
|
563
|
+
<div className="text-muted-foreground/70 font-mono truncate" title={conversationId}>
|
|
564
|
+
session: {conversationId}
|
|
565
|
+
</div>
|
|
566
|
+
</div>
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
export function CompareDrawer({ left, right, onClose }: CompareDrawerProps): JSX.Element {
|
|
571
|
+
// Memoize the diff so re-renders (e.g. parent re-renders) don't recompute.
|
|
572
|
+
const ops = useMemo<DiffOp[]>(() => {
|
|
573
|
+
const leftRequest = getLogFormatAdapter(resolveLogFormat(left)).analyzeRequest(
|
|
574
|
+
left.rawRequestBody,
|
|
575
|
+
);
|
|
576
|
+
const rightRequest = getLogFormatAdapter(resolveLogFormat(right)).analyzeRequest(
|
|
577
|
+
right.rawRequestBody,
|
|
578
|
+
);
|
|
579
|
+
const l = normalizeRequest(leftRequest.comparisonValue);
|
|
580
|
+
const r = normalizeRequest(rightRequest.comparisonValue);
|
|
581
|
+
return diffTrees(l, r);
|
|
582
|
+
}, [
|
|
583
|
+
left.apiFormat,
|
|
584
|
+
left.path,
|
|
585
|
+
left.rawRequestBody,
|
|
586
|
+
right.apiFormat,
|
|
587
|
+
right.path,
|
|
588
|
+
right.rawRequestBody,
|
|
589
|
+
]);
|
|
590
|
+
|
|
591
|
+
const grouped = useMemo(() => groupContiguousEquals(ops), [ops]);
|
|
592
|
+
|
|
593
|
+
// Count diff ops by kind (ignoring equal-run groups, which are always
|
|
594
|
+
// collapsed and not actionable). Drives the summary chips.
|
|
595
|
+
const counts = useMemo(() => {
|
|
596
|
+
let added = 0;
|
|
597
|
+
let removed = 0;
|
|
598
|
+
let changed = 0;
|
|
599
|
+
for (const g of grouped) {
|
|
600
|
+
if (g.kind !== "single") continue;
|
|
601
|
+
switch (g.op.kind) {
|
|
602
|
+
case "added":
|
|
603
|
+
added++;
|
|
604
|
+
break;
|
|
605
|
+
case "removed":
|
|
606
|
+
removed++;
|
|
607
|
+
break;
|
|
608
|
+
case "changed":
|
|
609
|
+
changed++;
|
|
610
|
+
break;
|
|
611
|
+
case "equal":
|
|
612
|
+
break;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return { added, removed, changed };
|
|
616
|
+
}, [grouped]);
|
|
617
|
+
|
|
618
|
+
// Track which collapsed equal runs are expanded.
|
|
619
|
+
const [expandedRuns, setExpandedRuns] = useState<Set<number>>(new Set());
|
|
620
|
+
const toggleRun = (idx: number) => {
|
|
621
|
+
setExpandedRuns((prev) => {
|
|
622
|
+
const next = new Set(prev);
|
|
623
|
+
if (next.has(idx)) next.delete(idx);
|
|
624
|
+
else next.add(idx);
|
|
625
|
+
return next;
|
|
626
|
+
});
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
// Track which individual diff rows (added / removed / changed) are
|
|
630
|
+
// expanded. Independent of `expandedRuns` (which is for equal-run groups)
|
|
631
|
+
// so the two expand affordances don't interfere.
|
|
632
|
+
const [expandedRows, setExpandedRows] = useState<Set<number>>(new Set());
|
|
633
|
+
const toggleRow = (idx: number) => {
|
|
634
|
+
setExpandedRows((prev) => {
|
|
635
|
+
const next = new Set(prev);
|
|
636
|
+
if (next.has(idx)) next.delete(idx);
|
|
637
|
+
else next.add(idx);
|
|
638
|
+
return next;
|
|
639
|
+
});
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
// Reset per-row expand state when the comparison pair changes.
|
|
643
|
+
useEffect(() => {
|
|
644
|
+
setExpandedRows(new Set());
|
|
645
|
+
}, [left.id, right.id]);
|
|
646
|
+
|
|
647
|
+
const [mode, setMode] = useState<DiffMode>("unified");
|
|
648
|
+
|
|
649
|
+
// Body scroll container — used to scroll a target row into view when a
|
|
650
|
+
// summary chip is clicked.
|
|
651
|
+
const bodyRef = useRef<HTMLDivElement>(null);
|
|
652
|
+
|
|
653
|
+
// Most-recently-copied path. Used to flash the icon from Copy → Check.
|
|
654
|
+
const [copiedPath, setCopiedPath] = useState<string | null>(null);
|
|
655
|
+
const copyResetTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
656
|
+
const onCopyPath = (path: string) => {
|
|
657
|
+
void window.navigator.clipboard.writeText(path).then(() => {
|
|
658
|
+
setCopiedPath(path);
|
|
659
|
+
if (copyResetTimer.current !== null) clearTimeout(copyResetTimer.current);
|
|
660
|
+
copyResetTimer.current = setTimeout(() => setCopiedPath(null), 1500);
|
|
661
|
+
});
|
|
662
|
+
};
|
|
663
|
+
useEffect(() => {
|
|
664
|
+
return () => {
|
|
665
|
+
if (copyResetTimer.current !== null) clearTimeout(copyResetTimer.current);
|
|
666
|
+
};
|
|
667
|
+
}, []);
|
|
668
|
+
|
|
669
|
+
// Find the grouped-index of the first op matching `kind`, and scroll to it.
|
|
670
|
+
const jumpToKind = (kind: "added" | "removed" | "changed") => {
|
|
671
|
+
const idx = grouped.findIndex((g) => g.kind === "single" && g.op.kind === kind);
|
|
672
|
+
if (idx === -1) return;
|
|
673
|
+
const root = bodyRef.current;
|
|
674
|
+
if (root === null) return;
|
|
675
|
+
const el = root.querySelector(`[data-diff-idx="${idx}"]`);
|
|
676
|
+
if (el !== null) {
|
|
677
|
+
el.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
// Esc keybinding + body scroll lock while the drawer is open.
|
|
682
|
+
useEffect(() => {
|
|
683
|
+
const onKey = (e: KeyboardEvent) => {
|
|
684
|
+
if (e.key === "Escape") onClose();
|
|
685
|
+
};
|
|
686
|
+
document.addEventListener("keydown", onKey);
|
|
687
|
+
const prevOverflow = document.body.style.overflow;
|
|
688
|
+
document.body.style.overflow = "hidden";
|
|
689
|
+
return () => {
|
|
690
|
+
document.removeEventListener("keydown", onKey);
|
|
691
|
+
document.body.style.overflow = prevOverflow;
|
|
692
|
+
};
|
|
693
|
+
}, [onClose]);
|
|
694
|
+
|
|
695
|
+
const sameSession = getConversationId(left) === getConversationId(right);
|
|
696
|
+
const allEqual = ops.length === 1 && ops[0]?.kind === "equal";
|
|
697
|
+
|
|
698
|
+
return (
|
|
699
|
+
<div
|
|
700
|
+
className="fixed inset-0 z-50 flex justify-end"
|
|
701
|
+
role="dialog"
|
|
702
|
+
aria-modal="true"
|
|
703
|
+
aria-label="Compare two log requests"
|
|
704
|
+
>
|
|
705
|
+
{/* Backdrop */}
|
|
706
|
+
<button
|
|
707
|
+
type="button"
|
|
708
|
+
onClick={onClose}
|
|
709
|
+
aria-label="Close compare drawer"
|
|
710
|
+
className="absolute inset-0 bg-black/40 cursor-default"
|
|
711
|
+
tabIndex={-1}
|
|
712
|
+
/>
|
|
713
|
+
|
|
714
|
+
{/* Drawer panel */}
|
|
715
|
+
<div
|
|
716
|
+
className={cn(
|
|
717
|
+
"relative bg-background border-l border-border shadow-xl",
|
|
718
|
+
"w-full md:w-[70vw] max-w-[1100px] flex flex-col h-full",
|
|
719
|
+
)}
|
|
720
|
+
onClick={(e) => e.stopPropagation()}
|
|
721
|
+
onKeyDown={(e) => e.stopPropagation()}
|
|
722
|
+
>
|
|
723
|
+
{/* Header */}
|
|
724
|
+
<div className="flex items-start gap-4 px-4 py-3 border-b border-border">
|
|
725
|
+
<div className="flex-1 flex gap-4 min-w-0">
|
|
726
|
+
<SideSummary log={left} side="left" />
|
|
727
|
+
<SideSummary log={right} side="right" />
|
|
728
|
+
</div>
|
|
729
|
+
<div className="flex items-center gap-2 shrink-0">
|
|
730
|
+
<ModeToggle mode={mode} onChange={setMode} />
|
|
731
|
+
<button
|
|
732
|
+
type="button"
|
|
733
|
+
onClick={onClose}
|
|
734
|
+
aria-label="Close"
|
|
735
|
+
className="p-1 rounded text-muted-foreground hover:text-foreground hover:bg-muted cursor-pointer"
|
|
736
|
+
>
|
|
737
|
+
<X className="size-4" />
|
|
738
|
+
</button>
|
|
739
|
+
</div>
|
|
740
|
+
</div>
|
|
741
|
+
|
|
742
|
+
{!sameSession && (
|
|
743
|
+
<div className="px-4 py-1.5 text-xs text-amber-400 bg-amber-500/10 border-b border-border">
|
|
744
|
+
Heads up: the two selected logs are from different sessions.
|
|
745
|
+
</div>
|
|
746
|
+
)}
|
|
747
|
+
|
|
748
|
+
{allEqual ? (
|
|
749
|
+
<div className="flex-1 min-h-0 overflow-y-auto flex items-center justify-center text-muted-foreground text-sm">
|
|
750
|
+
The two Request payloads are identical.
|
|
751
|
+
</div>
|
|
752
|
+
) : (
|
|
753
|
+
<>
|
|
754
|
+
<SummaryChips counts={counts} onJumpTo={jumpToKind} />
|
|
755
|
+
<div ref={bodyRef} className="flex-1 min-h-0 overflow-y-auto">
|
|
756
|
+
{mode === "unified" ? (
|
|
757
|
+
<div className="px-3 py-2 space-y-0.5">
|
|
758
|
+
{grouped.map((g, i) => {
|
|
759
|
+
if (g.kind === "equal-run") {
|
|
760
|
+
return (
|
|
761
|
+
<EqualRunRow
|
|
762
|
+
key={`r${i}`}
|
|
763
|
+
ops={g.ops}
|
|
764
|
+
expanded={expandedRuns.has(i)}
|
|
765
|
+
onToggle={() => toggleRun(i)}
|
|
766
|
+
/>
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
const op = g.op;
|
|
770
|
+
return (
|
|
771
|
+
<UnifiedOpRow
|
|
772
|
+
key={`o${i}`}
|
|
773
|
+
op={op}
|
|
774
|
+
idx={i}
|
|
775
|
+
copiedPath={copiedPath}
|
|
776
|
+
onCopyPath={onCopyPath}
|
|
777
|
+
expanded={expandedRows.has(i)}
|
|
778
|
+
onToggle={() => toggleRow(i)}
|
|
779
|
+
/>
|
|
780
|
+
);
|
|
781
|
+
})}
|
|
782
|
+
</div>
|
|
783
|
+
) : (
|
|
784
|
+
<SplitBody grouped={grouped} left={left} right={right} />
|
|
785
|
+
)}
|
|
786
|
+
</div>
|
|
787
|
+
</>
|
|
788
|
+
)}
|
|
789
|
+
</div>
|
|
790
|
+
</div>
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/** Legacy 3-column (path | left | right) view, retained as a toggle option. */
|
|
795
|
+
function SplitBody({
|
|
796
|
+
grouped,
|
|
797
|
+
left,
|
|
798
|
+
right,
|
|
799
|
+
}: {
|
|
800
|
+
grouped: GroupedOp[];
|
|
801
|
+
left: CapturedLog;
|
|
802
|
+
right: CapturedLog;
|
|
803
|
+
}): JSX.Element {
|
|
804
|
+
return (
|
|
805
|
+
<div className="grid grid-cols-[200px_1fr_1fr] gap-x-2 gap-y-0.5 px-3 py-2 text-xs">
|
|
806
|
+
{/* Column headers */}
|
|
807
|
+
<div className="grid grid-cols-[200px_1fr_1fr] gap-x-2 col-span-3 pb-2 mb-2 border-b border-border text-[10px] uppercase tracking-wider text-muted-foreground">
|
|
808
|
+
<span>Path</span>
|
|
809
|
+
<span>Left (Log #{left.id})</span>
|
|
810
|
+
<span>Right (Log #{right.id})</span>
|
|
811
|
+
</div>
|
|
812
|
+
|
|
813
|
+
{grouped.map((g, i) => {
|
|
814
|
+
if (g.kind === "equal-run") {
|
|
815
|
+
return (
|
|
816
|
+
<div
|
|
817
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: positional in the grouped list
|
|
818
|
+
key={i}
|
|
819
|
+
className="col-span-3 px-2 py-1 text-xs text-muted-foreground/60"
|
|
820
|
+
>
|
|
821
|
+
{g.ops.length} equal siblings collapsed — switch to Unified to expand
|
|
822
|
+
</div>
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
const op = g.op;
|
|
826
|
+
if (op.kind === "equal") {
|
|
827
|
+
return (
|
|
828
|
+
<div
|
|
829
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: positional in the grouped list
|
|
830
|
+
key={i}
|
|
831
|
+
className="col-span-3 grid grid-cols-[200px_1fr_1fr] gap-x-2 px-2 py-0.5 text-muted-foreground"
|
|
832
|
+
>
|
|
833
|
+
<span className="font-mono text-xs truncate" title={op.path}>
|
|
834
|
+
{op.path}
|
|
835
|
+
</span>
|
|
836
|
+
<span className="font-mono text-xs break-all opacity-60">
|
|
837
|
+
{previewNode(op.value, 200)}
|
|
838
|
+
</span>
|
|
839
|
+
<span className="font-mono text-xs break-all opacity-60">
|
|
840
|
+
{previewNode(op.value, 200)}
|
|
841
|
+
</span>
|
|
842
|
+
</div>
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
if (op.kind === "added") {
|
|
846
|
+
return (
|
|
847
|
+
<div
|
|
848
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: positional in the grouped list
|
|
849
|
+
key={i}
|
|
850
|
+
className="col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-emerald-400/70 bg-emerald-500/5"
|
|
851
|
+
>
|
|
852
|
+
<div className="font-mono text-xs text-muted-foreground mb-0.5">{op.path}</div>
|
|
853
|
+
<div className="font-mono break-all text-emerald-300/90">
|
|
854
|
+
+ {previewNode(op.value, 400)}
|
|
855
|
+
</div>
|
|
856
|
+
</div>
|
|
857
|
+
);
|
|
858
|
+
}
|
|
859
|
+
if (op.kind === "removed") {
|
|
860
|
+
return (
|
|
861
|
+
<div
|
|
862
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: positional in the grouped list
|
|
863
|
+
key={i}
|
|
864
|
+
className="col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-rose-400/70 bg-rose-500/5"
|
|
865
|
+
>
|
|
866
|
+
<div className="font-mono text-xs text-muted-foreground mb-0.5">{op.path}</div>
|
|
867
|
+
<div className="font-mono break-all text-rose-300/90 line-through">
|
|
868
|
+
− {previewNode(op.value, 400)}
|
|
869
|
+
</div>
|
|
870
|
+
</div>
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
return (
|
|
874
|
+
<div
|
|
875
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: positional in the grouped list
|
|
876
|
+
key={i}
|
|
877
|
+
className="col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-amber-400/70 bg-amber-500/5"
|
|
878
|
+
>
|
|
879
|
+
<div className="font-mono text-xs text-muted-foreground mb-1">{op.path}</div>
|
|
880
|
+
<div className="grid grid-cols-2 gap-2">
|
|
881
|
+
<div className="font-mono text-rose-300/90 break-all line-through">
|
|
882
|
+
{previewNode(op.left, 400)}
|
|
883
|
+
</div>
|
|
884
|
+
<div className="font-mono text-emerald-300/90 break-all">
|
|
885
|
+
{previewNode(op.right, 400)}
|
|
886
|
+
</div>
|
|
887
|
+
</div>
|
|
888
|
+
</div>
|
|
889
|
+
);
|
|
890
|
+
})}
|
|
891
|
+
</div>
|
|
892
|
+
);
|
|
893
|
+
}
|