@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,225 @@
|
|
|
1
|
+
import { Check, Copy, RotateCcw } from "lucide-react";
|
|
2
|
+
import type { JSX } from "react";
|
|
3
|
+
import { useMemo, useState } from "react";
|
|
4
|
+
import { cn } from "../../lib/utils";
|
|
5
|
+
import { type CapturedLog, parseRequest } from "../../proxy/schemas";
|
|
6
|
+
import { Button } from "../ui/button";
|
|
7
|
+
import { JsonViewerFromString } from "../ui/json-viewer";
|
|
8
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
|
|
9
|
+
import { LogEntryHeader } from "./LogEntryHeader";
|
|
10
|
+
import { ReplayDialog } from "./ReplayDialog";
|
|
11
|
+
import { ResponseView } from "./ResponseView";
|
|
12
|
+
import { StreamingChunkSequence } from "./StreamingChunkSequence";
|
|
13
|
+
|
|
14
|
+
export type LogEntryProps = {
|
|
15
|
+
log: CapturedLog;
|
|
16
|
+
viewMode?: "simple" | "full";
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function CopyButton({
|
|
20
|
+
text,
|
|
21
|
+
label,
|
|
22
|
+
copied,
|
|
23
|
+
onCopy,
|
|
24
|
+
}: {
|
|
25
|
+
text: string | null;
|
|
26
|
+
label: string;
|
|
27
|
+
copied: boolean;
|
|
28
|
+
onCopy: (e: React.MouseEvent) => void;
|
|
29
|
+
}): JSX.Element | null {
|
|
30
|
+
if (text === null) return null;
|
|
31
|
+
return (
|
|
32
|
+
<button
|
|
33
|
+
type="button"
|
|
34
|
+
onClick={onCopy}
|
|
35
|
+
className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors px-2 py-1 rounded hover:bg-muted"
|
|
36
|
+
>
|
|
37
|
+
{copied ? (
|
|
38
|
+
<>
|
|
39
|
+
<Check className="size-3 text-green-500" />
|
|
40
|
+
<span className="text-green-500">Copied!</span>
|
|
41
|
+
</>
|
|
42
|
+
) : (
|
|
43
|
+
<>
|
|
44
|
+
<Copy className="size-3" />
|
|
45
|
+
<span>{label}</span>
|
|
46
|
+
</>
|
|
47
|
+
)}
|
|
48
|
+
</button>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function LogEntry({ log, viewMode = "simple" }: LogEntryProps): JSX.Element {
|
|
53
|
+
const [expanded, setExpanded] = useState<boolean>(false);
|
|
54
|
+
const [requestCopied, setRequestCopied] = useState<boolean>(false);
|
|
55
|
+
const [responseCopied, setResponseCopied] = useState<boolean>(false);
|
|
56
|
+
const [replayOpen, setReplayOpen] = useState<boolean>(false);
|
|
57
|
+
const parsedRequest = useMemo(() => parseRequest(log.rawRequestBody), [log.rawRequestBody]);
|
|
58
|
+
|
|
59
|
+
function handleCopyRequest(e: React.MouseEvent): void {
|
|
60
|
+
e.stopPropagation();
|
|
61
|
+
if (log.rawRequestBody === null) return;
|
|
62
|
+
void window.navigator.clipboard.writeText(log.rawRequestBody).then(() => {
|
|
63
|
+
setRequestCopied(true);
|
|
64
|
+
setTimeout(() => setRequestCopied(false), 2000);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function handleCopyResponse(e: React.MouseEvent): void {
|
|
69
|
+
e.stopPropagation();
|
|
70
|
+
if (log.responseText === null) return;
|
|
71
|
+
void window.navigator.clipboard.writeText(log.responseText).then(() => {
|
|
72
|
+
setResponseCopied(true);
|
|
73
|
+
setTimeout(() => setResponseCopied(false), 2000);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<>
|
|
79
|
+
<div className={cn("border border-border rounded-lg mb-3 overflow-hidden")}>
|
|
80
|
+
<LogEntryHeader
|
|
81
|
+
log={log}
|
|
82
|
+
parsedRequest={parsedRequest}
|
|
83
|
+
expanded={expanded}
|
|
84
|
+
onToggle={() => setExpanded(!expanded)}
|
|
85
|
+
/>
|
|
86
|
+
|
|
87
|
+
{expanded && (
|
|
88
|
+
<div onClick={(e) => e.stopPropagation()} onKeyDown={(e) => e.stopPropagation()}>
|
|
89
|
+
<Tabs defaultValue="request">
|
|
90
|
+
<TabsList className="mx-4 mt-2">
|
|
91
|
+
{viewMode === "full" && <TabsTrigger value="raw-headers">Raw Headers</TabsTrigger>}
|
|
92
|
+
{viewMode === "full" && <TabsTrigger value="headers">Headers</TabsTrigger>}
|
|
93
|
+
<TabsTrigger value="request">Request</TabsTrigger>
|
|
94
|
+
{viewMode === "full" && <TabsTrigger value="raw">Raw Response</TabsTrigger>}
|
|
95
|
+
<TabsTrigger value="parsed">Parsed Response</TabsTrigger>
|
|
96
|
+
</TabsList>
|
|
97
|
+
|
|
98
|
+
<TabsContent value="request">
|
|
99
|
+
<div className="px-4 py-3">
|
|
100
|
+
<div className="flex justify-end gap-2 mb-2">
|
|
101
|
+
<Button
|
|
102
|
+
variant="outline"
|
|
103
|
+
size="sm"
|
|
104
|
+
className="h-7 text-xs"
|
|
105
|
+
onClick={(e) => {
|
|
106
|
+
e.stopPropagation();
|
|
107
|
+
setReplayOpen(true);
|
|
108
|
+
}}
|
|
109
|
+
>
|
|
110
|
+
<RotateCcw className="size-3 mr-1" />
|
|
111
|
+
Replay
|
|
112
|
+
</Button>
|
|
113
|
+
<CopyButton
|
|
114
|
+
text={log.rawRequestBody}
|
|
115
|
+
label="Copy Request"
|
|
116
|
+
copied={requestCopied}
|
|
117
|
+
onCopy={handleCopyRequest}
|
|
118
|
+
/>
|
|
119
|
+
</div>
|
|
120
|
+
{log.rawRequestBody !== null ? (
|
|
121
|
+
<JsonViewerFromString text={log.rawRequestBody} defaultExpandDepth={1} />
|
|
122
|
+
) : (
|
|
123
|
+
<p className="text-xs text-muted-foreground italic">No request body</p>
|
|
124
|
+
)}
|
|
125
|
+
</div>
|
|
126
|
+
</TabsContent>
|
|
127
|
+
|
|
128
|
+
{viewMode === "full" && (
|
|
129
|
+
<TabsContent value="headers">
|
|
130
|
+
<div className="px-4 py-3">
|
|
131
|
+
{log.headers && Object.keys(log.headers).length > 0 ? (
|
|
132
|
+
<div className="space-y-1 font-mono text-xs">
|
|
133
|
+
{Object.entries(log.headers)
|
|
134
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
135
|
+
.map(([key, value]) => (
|
|
136
|
+
<div key={key} className="flex gap-2">
|
|
137
|
+
<span className="text-blue-600 dark:text-blue-400 font-semibold shrink-0">
|
|
138
|
+
{key}:
|
|
139
|
+
</span>
|
|
140
|
+
<span className="text-muted-foreground truncate" title={value}>
|
|
141
|
+
{value}
|
|
142
|
+
</span>
|
|
143
|
+
</div>
|
|
144
|
+
))}
|
|
145
|
+
</div>
|
|
146
|
+
) : (
|
|
147
|
+
<p className="text-xs text-muted-foreground italic">No headers captured</p>
|
|
148
|
+
)}
|
|
149
|
+
</div>
|
|
150
|
+
</TabsContent>
|
|
151
|
+
)}
|
|
152
|
+
|
|
153
|
+
{viewMode === "full" && (
|
|
154
|
+
<TabsContent value="raw-headers">
|
|
155
|
+
<div className="px-4 py-3">
|
|
156
|
+
{log.rawHeaders && Object.keys(log.rawHeaders).length > 0 ? (
|
|
157
|
+
<div className="space-y-1 font-mono text-xs">
|
|
158
|
+
{Object.entries(log.rawHeaders)
|
|
159
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
160
|
+
.map(([key, value]) => (
|
|
161
|
+
<div key={key} className="flex gap-2">
|
|
162
|
+
<span className="text-blue-600 dark:text-blue-400 font-semibold shrink-0">
|
|
163
|
+
{key}:
|
|
164
|
+
</span>
|
|
165
|
+
<span className="text-muted-foreground truncate" title={value}>
|
|
166
|
+
{value}
|
|
167
|
+
</span>
|
|
168
|
+
</div>
|
|
169
|
+
))}
|
|
170
|
+
</div>
|
|
171
|
+
) : (
|
|
172
|
+
<p className="text-xs text-muted-foreground italic">
|
|
173
|
+
No raw headers captured
|
|
174
|
+
</p>
|
|
175
|
+
)}
|
|
176
|
+
</div>
|
|
177
|
+
</TabsContent>
|
|
178
|
+
)}
|
|
179
|
+
|
|
180
|
+
<TabsContent value="raw">
|
|
181
|
+
<div className="px-4 py-3 space-y-3">
|
|
182
|
+
<div className="flex justify-end">
|
|
183
|
+
<CopyButton
|
|
184
|
+
text={log.responseText}
|
|
185
|
+
label="Copy Response"
|
|
186
|
+
copied={responseCopied}
|
|
187
|
+
onCopy={handleCopyResponse}
|
|
188
|
+
/>
|
|
189
|
+
</div>
|
|
190
|
+
{log.responseText !== null ? (
|
|
191
|
+
<JsonViewerFromString text={log.responseText} defaultExpandDepth={1} />
|
|
192
|
+
) : (
|
|
193
|
+
<p className="text-xs text-muted-foreground italic">No response</p>
|
|
194
|
+
)}
|
|
195
|
+
{log.streaming === true && (
|
|
196
|
+
<StreamingChunkSequence
|
|
197
|
+
logId={log.id}
|
|
198
|
+
truncated={log.streamingChunksPath !== null}
|
|
199
|
+
/>
|
|
200
|
+
)}
|
|
201
|
+
</div>
|
|
202
|
+
</TabsContent>
|
|
203
|
+
|
|
204
|
+
<TabsContent value="parsed">
|
|
205
|
+
<div className="px-4 py-3">
|
|
206
|
+
<ResponseView
|
|
207
|
+
responseText={log.responseText}
|
|
208
|
+
responseStatus={log.responseStatus}
|
|
209
|
+
streaming={log.streaming}
|
|
210
|
+
inputTokens={log.inputTokens}
|
|
211
|
+
outputTokens={log.outputTokens}
|
|
212
|
+
cacheCreationInputTokens={log.cacheCreationInputTokens}
|
|
213
|
+
cacheReadInputTokens={log.cacheReadInputTokens}
|
|
214
|
+
apiFormat={log.apiFormat}
|
|
215
|
+
/>
|
|
216
|
+
</div>
|
|
217
|
+
</TabsContent>
|
|
218
|
+
</Tabs>
|
|
219
|
+
</div>
|
|
220
|
+
)}
|
|
221
|
+
</div>
|
|
222
|
+
<ReplayDialog log={log} open={replayOpen} onOpenChange={setReplayOpen} />
|
|
223
|
+
</>
|
|
224
|
+
);
|
|
225
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChevronDown,
|
|
3
|
+
ChevronRight,
|
|
4
|
+
Clock,
|
|
5
|
+
FileTerminal,
|
|
6
|
+
Globe,
|
|
7
|
+
Loader2,
|
|
8
|
+
MessageSquare,
|
|
9
|
+
Radio,
|
|
10
|
+
User,
|
|
11
|
+
Wrench,
|
|
12
|
+
Zap,
|
|
13
|
+
} from "lucide-react";
|
|
14
|
+
import type { JSX } from "react";
|
|
15
|
+
import { cn, formatTokens, getStatusCategory, type StatusCategory } from "../../lib/utils";
|
|
16
|
+
import type { CapturedLog, InspectorRequest } from "../../proxy/schemas";
|
|
17
|
+
import { Badge } from "../ui/badge";
|
|
18
|
+
import { ProviderLogo, detectProvider } from "../providers/ProviderLogo";
|
|
19
|
+
|
|
20
|
+
const API_FORMAT_LABELS: Record<"anthropic" | "openai" | "unknown", string> = {
|
|
21
|
+
anthropic: "Anthropic",
|
|
22
|
+
openai: "OpenAI",
|
|
23
|
+
unknown: "Unknown",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function formatElapsed(ms: number): string {
|
|
27
|
+
if (ms < 1000) return `${ms}ms`;
|
|
28
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const STATUS_BADGE_CLASSES: Record<StatusCategory, string> = {
|
|
32
|
+
success: "bg-emerald-500/15 text-emerald-400 border-emerald-500/25",
|
|
33
|
+
client_error: "bg-amber-500/15 text-amber-400 border-amber-500/25",
|
|
34
|
+
server_error: "",
|
|
35
|
+
pending: "bg-muted text-muted-foreground border-border",
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type LogEntryHeaderProps = {
|
|
39
|
+
log: CapturedLog;
|
|
40
|
+
parsedRequest: InspectorRequest | null;
|
|
41
|
+
expanded: boolean;
|
|
42
|
+
onToggle: () => void;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export function LogEntryHeader({
|
|
46
|
+
log,
|
|
47
|
+
parsedRequest,
|
|
48
|
+
expanded,
|
|
49
|
+
onToggle,
|
|
50
|
+
}: LogEntryHeaderProps): JSX.Element {
|
|
51
|
+
const statusCategory = getStatusCategory(log.responseStatus);
|
|
52
|
+
|
|
53
|
+
const hasTokens = log.inputTokens !== null || log.outputTokens !== null;
|
|
54
|
+
|
|
55
|
+
const messageCount = parsedRequest !== null ? parsedRequest.messages.length : null;
|
|
56
|
+
|
|
57
|
+
const toolCount =
|
|
58
|
+
parsedRequest !== null && parsedRequest.tools !== undefined && parsedRequest.tools.length > 0
|
|
59
|
+
? parsedRequest.tools.length
|
|
60
|
+
: null;
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div
|
|
64
|
+
role="button"
|
|
65
|
+
tabIndex={0}
|
|
66
|
+
className={cn(
|
|
67
|
+
"flex items-center gap-2.5 px-3 py-2 cursor-pointer transition-colors",
|
|
68
|
+
"hover:bg-muted/50",
|
|
69
|
+
"select-none",
|
|
70
|
+
)}
|
|
71
|
+
onClick={onToggle}
|
|
72
|
+
onKeyDown={(e) => {
|
|
73
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
74
|
+
e.preventDefault();
|
|
75
|
+
onToggle();
|
|
76
|
+
}
|
|
77
|
+
}}
|
|
78
|
+
>
|
|
79
|
+
{/* Request ID */}
|
|
80
|
+
<span className="text-blue-400/80 font-mono text-xs font-semibold tabular-nums shrink-0">
|
|
81
|
+
#{log.id}
|
|
82
|
+
</span>
|
|
83
|
+
|
|
84
|
+
{/* Model */}
|
|
85
|
+
{log.model !== null && (
|
|
86
|
+
<>
|
|
87
|
+
<ProviderLogo provider={detectProvider(log.model)} className="size-4 shrink-0" />
|
|
88
|
+
<Badge variant="secondary" className="text-[10px] px-1.5 py-0 h-5 font-mono">
|
|
89
|
+
{log.model}
|
|
90
|
+
</Badge>
|
|
91
|
+
</>
|
|
92
|
+
)}
|
|
93
|
+
|
|
94
|
+
{/* API Format Badge */}
|
|
95
|
+
<Badge
|
|
96
|
+
variant="outline"
|
|
97
|
+
className={cn(
|
|
98
|
+
"text-[10px] px-1.5 py-0 h-5 font-mono",
|
|
99
|
+
log.apiFormat === "openai" && "border-blue-500/40 text-blue-400",
|
|
100
|
+
log.apiFormat === "anthropic" && "border-orange-500/40 text-orange-400",
|
|
101
|
+
log.apiFormat === "unknown" && "border-muted text-muted-foreground",
|
|
102
|
+
)}
|
|
103
|
+
>
|
|
104
|
+
{API_FORMAT_LABELS[log.apiFormat] ?? log.apiFormat}
|
|
105
|
+
</Badge>
|
|
106
|
+
|
|
107
|
+
{/* Response Status */}
|
|
108
|
+
{statusCategory === "server_error" ? (
|
|
109
|
+
<Badge variant="destructive" className="text-[10px] px-1.5 py-0 h-5 font-mono tabular-nums">
|
|
110
|
+
{log.responseStatus}
|
|
111
|
+
</Badge>
|
|
112
|
+
) : statusCategory === "pending" ? (
|
|
113
|
+
<Badge
|
|
114
|
+
variant="outline"
|
|
115
|
+
className={cn(
|
|
116
|
+
"text-[10px] px-1.5 py-0 h-5 font-mono tabular-nums",
|
|
117
|
+
STATUS_BADGE_CLASSES[statusCategory],
|
|
118
|
+
)}
|
|
119
|
+
>
|
|
120
|
+
<Loader2 className="size-3 animate-spin" />
|
|
121
|
+
</Badge>
|
|
122
|
+
) : (
|
|
123
|
+
<Badge
|
|
124
|
+
variant="outline"
|
|
125
|
+
className={cn(
|
|
126
|
+
"text-[10px] px-1.5 py-0 h-5 font-mono tabular-nums",
|
|
127
|
+
STATUS_BADGE_CLASSES[statusCategory],
|
|
128
|
+
)}
|
|
129
|
+
>
|
|
130
|
+
{log.responseStatus}
|
|
131
|
+
</Badge>
|
|
132
|
+
)}
|
|
133
|
+
|
|
134
|
+
{/* Elapsed time */}
|
|
135
|
+
{log.elapsedMs !== null && (
|
|
136
|
+
<span className="flex items-center gap-1 text-muted-foreground text-xs shrink-0">
|
|
137
|
+
<Clock className="size-3" />
|
|
138
|
+
<span className="font-mono tabular-nums">{formatElapsed(log.elapsedMs)}</span>
|
|
139
|
+
</span>
|
|
140
|
+
)}
|
|
141
|
+
|
|
142
|
+
{/* Token counts */}
|
|
143
|
+
{hasTokens && (
|
|
144
|
+
<span className="flex items-center gap-1 text-xs shrink-0">
|
|
145
|
+
<Zap className="size-3 text-muted-foreground" />
|
|
146
|
+
<span className="font-mono tabular-nums">
|
|
147
|
+
<span className={log.inputTokens !== null ? "text-blue-400" : "text-muted-foreground"}>
|
|
148
|
+
IN {log.inputTokens !== null ? formatTokens(log.inputTokens) : "—"}
|
|
149
|
+
</span>
|
|
150
|
+
{" / "}
|
|
151
|
+
<span
|
|
152
|
+
className={log.outputTokens !== null ? "text-amber-400" : "text-muted-foreground"}
|
|
153
|
+
>
|
|
154
|
+
OUT {log.outputTokens !== null ? formatTokens(log.outputTokens) : "—"}
|
|
155
|
+
</span>
|
|
156
|
+
</span>
|
|
157
|
+
</span>
|
|
158
|
+
)}
|
|
159
|
+
{/* Cache tokens */}
|
|
160
|
+
{log.cacheCreationInputTokens !== null && log.cacheCreationInputTokens > 0 && (
|
|
161
|
+
<span className="flex items-center gap-1 text-xs shrink-0">
|
|
162
|
+
<span className="font-mono tabular-nums text-emerald-400">
|
|
163
|
+
Cache +{formatTokens(log.cacheCreationInputTokens)}
|
|
164
|
+
</span>
|
|
165
|
+
</span>
|
|
166
|
+
)}
|
|
167
|
+
{log.cacheReadInputTokens !== null && log.cacheReadInputTokens > 0 && (
|
|
168
|
+
<span className="flex items-center gap-1 text-xs shrink-0">
|
|
169
|
+
<span className="font-mono tabular-nums text-purple-400">
|
|
170
|
+
Cache ~{formatTokens(log.cacheReadInputTokens)}
|
|
171
|
+
</span>
|
|
172
|
+
</span>
|
|
173
|
+
)}
|
|
174
|
+
|
|
175
|
+
{/* Message count */}
|
|
176
|
+
{messageCount !== null && (
|
|
177
|
+
<span className="flex items-center gap-1 text-muted-foreground text-xs shrink-0">
|
|
178
|
+
<MessageSquare className="size-3" />
|
|
179
|
+
<span className="font-mono tabular-nums">{messageCount}</span>
|
|
180
|
+
</span>
|
|
181
|
+
)}
|
|
182
|
+
|
|
183
|
+
{/* Tool count */}
|
|
184
|
+
{toolCount !== null && (
|
|
185
|
+
<span className="flex items-center gap-1 text-muted-foreground text-xs shrink-0">
|
|
186
|
+
<Wrench className="size-3" />
|
|
187
|
+
<span className="font-mono tabular-nums">{toolCount}</span>
|
|
188
|
+
</span>
|
|
189
|
+
)}
|
|
190
|
+
|
|
191
|
+
{/* Origin */}
|
|
192
|
+
{log.origin !== null && (
|
|
193
|
+
<span
|
|
194
|
+
className="flex items-center gap-1 text-muted-foreground text-xs shrink-0"
|
|
195
|
+
title={`Origin: ${log.origin}`}
|
|
196
|
+
>
|
|
197
|
+
<Globe className="size-3" />
|
|
198
|
+
<span className="font-mono tabular-nums truncate max-w-[120px]" title={log.origin}>
|
|
199
|
+
{log.origin}
|
|
200
|
+
</span>
|
|
201
|
+
</span>
|
|
202
|
+
)}
|
|
203
|
+
|
|
204
|
+
{/* User-Agent */}
|
|
205
|
+
{log.userAgent !== null && (
|
|
206
|
+
<span
|
|
207
|
+
className="flex items-center gap-1 text-muted-foreground text-xs shrink-0"
|
|
208
|
+
title={`User-Agent: ${log.userAgent}`}
|
|
209
|
+
>
|
|
210
|
+
<User className="size-3" />
|
|
211
|
+
<span className="font-mono tabular-nums truncate max-w-[150px]" title={log.userAgent}>
|
|
212
|
+
{log.userAgent}
|
|
213
|
+
</span>
|
|
214
|
+
</span>
|
|
215
|
+
)}
|
|
216
|
+
|
|
217
|
+
{/* Client info (PID + project folder) */}
|
|
218
|
+
{(log.clientPid !== null || log.clientProjectFolder !== null) && (
|
|
219
|
+
<span
|
|
220
|
+
className="flex items-center gap-1 text-purple-400/80 text-xs shrink-0"
|
|
221
|
+
title={
|
|
222
|
+
log.clientCwd !== null
|
|
223
|
+
? `PID: ${log.clientPid ?? "?"} | CWD: ${log.clientCwd}`
|
|
224
|
+
: `PID: ${log.clientPid ?? "?"}`
|
|
225
|
+
}
|
|
226
|
+
>
|
|
227
|
+
<FileTerminal className="size-3" />
|
|
228
|
+
{log.clientProjectFolder !== null ? (
|
|
229
|
+
<span className="font-mono tabular-nums">{log.clientProjectFolder}</span>
|
|
230
|
+
) : (
|
|
231
|
+
<span className="font-mono tabular-nums">PID {log.clientPid}</span>
|
|
232
|
+
)}
|
|
233
|
+
</span>
|
|
234
|
+
)}
|
|
235
|
+
|
|
236
|
+
{/* Streaming indicator */}
|
|
237
|
+
{log.streaming && <Radio className="size-3 text-muted-foreground/60 shrink-0" />}
|
|
238
|
+
|
|
239
|
+
{/* Spacer */}
|
|
240
|
+
<span className="flex-1 min-w-0" />
|
|
241
|
+
|
|
242
|
+
{/* Expand chevron */}
|
|
243
|
+
{expanded ? (
|
|
244
|
+
<ChevronDown className="size-4 text-muted-foreground shrink-0" />
|
|
245
|
+
) : (
|
|
246
|
+
<ChevronRight className="size-4 text-muted-foreground shrink-0" />
|
|
247
|
+
)}
|
|
248
|
+
</div>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { RotateCcw } from "lucide-react";
|
|
2
|
+
import type { JSX } from "react";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { Button } from "../ui/button";
|
|
6
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog";
|
|
7
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
|
|
8
|
+
import { ResponseView } from "./ResponseView";
|
|
9
|
+
import type { CapturedLog } from "../../proxy/schemas";
|
|
10
|
+
|
|
11
|
+
const ReplayResultSchema = z.object({
|
|
12
|
+
success: z.boolean(),
|
|
13
|
+
error: z.string().optional(),
|
|
14
|
+
responseStatus: z.number().optional(),
|
|
15
|
+
responseText: z.string().optional(),
|
|
16
|
+
inputTokens: z.number().optional(),
|
|
17
|
+
outputTokens: z.number().optional(),
|
|
18
|
+
elapsedMs: z.number().optional(),
|
|
19
|
+
streaming: z.boolean().optional(),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
type ReplayResult = z.infer<typeof ReplayResultSchema>;
|
|
23
|
+
|
|
24
|
+
type ReplayDialogProps = {
|
|
25
|
+
log: CapturedLog;
|
|
26
|
+
open: boolean;
|
|
27
|
+
onOpenChange: (open: boolean) => void;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export function ReplayDialog({ log, open, onOpenChange }: ReplayDialogProps): JSX.Element {
|
|
31
|
+
const [modifiedBody, setModifiedBody] = useState<string>(() => {
|
|
32
|
+
return log.rawRequestBody ?? "{}";
|
|
33
|
+
});
|
|
34
|
+
const [replayResult, setReplayResult] = useState<ReplayResult | null>(null);
|
|
35
|
+
const [loading, setLoading] = useState(false);
|
|
36
|
+
const [error, setError] = useState<string | null>(null);
|
|
37
|
+
|
|
38
|
+
async function handleReplay() {
|
|
39
|
+
setLoading(true);
|
|
40
|
+
setError(null);
|
|
41
|
+
setReplayResult(null);
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const res = await fetch(`/api/logs/${log.id}/replay`, {
|
|
45
|
+
method: "POST",
|
|
46
|
+
headers: { "Content-Type": "application/json" },
|
|
47
|
+
body: JSON.stringify({ modifiedBody }),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const json: unknown = await res.json();
|
|
51
|
+
const parsed = ReplayResultSchema.safeParse(json);
|
|
52
|
+
if (!parsed.success) {
|
|
53
|
+
setError("Invalid response from server");
|
|
54
|
+
setLoading(false);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const data = parsed.data;
|
|
58
|
+
setReplayResult(data);
|
|
59
|
+
if (!data.success) {
|
|
60
|
+
setError(data.error ?? "Replay failed");
|
|
61
|
+
}
|
|
62
|
+
} catch (err) {
|
|
63
|
+
setError(err instanceof Error ? err.message : "Network error");
|
|
64
|
+
} finally {
|
|
65
|
+
setLoading(false);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function handleClose() {
|
|
70
|
+
setReplayResult(null);
|
|
71
|
+
setError(null);
|
|
72
|
+
onOpenChange(false);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<Dialog open={open} onOpenChange={handleClose}>
|
|
77
|
+
<DialogContent className="max-w-4xl max-h-[85vh] overflow-auto">
|
|
78
|
+
<DialogHeader>
|
|
79
|
+
<DialogTitle className="flex items-center gap-2">
|
|
80
|
+
<RotateCcw className="size-4" />
|
|
81
|
+
Replay Request #{log.id}
|
|
82
|
+
</DialogTitle>
|
|
83
|
+
</DialogHeader>
|
|
84
|
+
|
|
85
|
+
<Tabs defaultValue="modified">
|
|
86
|
+
<TabsList>
|
|
87
|
+
<TabsTrigger value="modified">Modified Request</TabsTrigger>
|
|
88
|
+
<TabsTrigger value="original">Original Response</TabsTrigger>
|
|
89
|
+
{replayResult && <TabsTrigger value="replay">Replay Response</TabsTrigger>}
|
|
90
|
+
</TabsList>
|
|
91
|
+
|
|
92
|
+
<TabsContent value="modified" className="space-y-4">
|
|
93
|
+
<div>
|
|
94
|
+
<label className="text-sm font-medium mb-2 block">Request Body (JSON)</label>
|
|
95
|
+
<textarea
|
|
96
|
+
className="w-full h-64 p-3 font-mono text-xs bg-muted rounded-md border border-input resize-none focus:outline-none focus:ring-2 focus:ring-ring"
|
|
97
|
+
value={modifiedBody}
|
|
98
|
+
onChange={(e) => setModifiedBody(e.target.value)}
|
|
99
|
+
spellCheck={false}
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
{error !== null && error !== "" && (
|
|
104
|
+
<div className="text-sm text-destructive bg-destructive/10 px-3 py-2 rounded-md">
|
|
105
|
+
{error}
|
|
106
|
+
</div>
|
|
107
|
+
)}
|
|
108
|
+
|
|
109
|
+
<div className="flex justify-end">
|
|
110
|
+
<Button
|
|
111
|
+
onClick={() => {
|
|
112
|
+
void handleReplay();
|
|
113
|
+
}}
|
|
114
|
+
disabled={loading}
|
|
115
|
+
>
|
|
116
|
+
{loading ? "Replaying..." : "Replay"}
|
|
117
|
+
</Button>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
{replayResult && replayResult.success && (
|
|
121
|
+
<Tabs defaultValue="parsed">
|
|
122
|
+
<TabsList>
|
|
123
|
+
<TabsTrigger value="parsed">Parsed Response</TabsTrigger>
|
|
124
|
+
<TabsTrigger value="raw">Raw Response</TabsTrigger>
|
|
125
|
+
</TabsList>
|
|
126
|
+
<TabsContent value="parsed">
|
|
127
|
+
<ResponseView
|
|
128
|
+
responseText={replayResult.responseText ?? null}
|
|
129
|
+
responseStatus={replayResult.responseStatus ?? null}
|
|
130
|
+
streaming={replayResult.streaming ?? false}
|
|
131
|
+
inputTokens={replayResult.inputTokens ?? null}
|
|
132
|
+
outputTokens={replayResult.outputTokens ?? null}
|
|
133
|
+
apiFormat={log.apiFormat}
|
|
134
|
+
/>
|
|
135
|
+
</TabsContent>
|
|
136
|
+
<TabsContent value="raw">
|
|
137
|
+
<pre className="font-mono text-xs whitespace-pre-wrap bg-muted p-3 rounded-md max-h-96 overflow-auto">
|
|
138
|
+
{replayResult.responseText ?? "No response"}
|
|
139
|
+
</pre>
|
|
140
|
+
</TabsContent>
|
|
141
|
+
</Tabs>
|
|
142
|
+
)}
|
|
143
|
+
</TabsContent>
|
|
144
|
+
|
|
145
|
+
<TabsContent value="original">
|
|
146
|
+
{log.responseText !== null ? (
|
|
147
|
+
<Tabs defaultValue="parsed">
|
|
148
|
+
<TabsList>
|
|
149
|
+
<TabsTrigger value="parsed">Parsed Response</TabsTrigger>
|
|
150
|
+
<TabsTrigger value="raw">Raw Response</TabsTrigger>
|
|
151
|
+
</TabsList>
|
|
152
|
+
<TabsContent value="parsed">
|
|
153
|
+
<ResponseView
|
|
154
|
+
responseText={log.responseText}
|
|
155
|
+
responseStatus={log.responseStatus}
|
|
156
|
+
streaming={log.streaming}
|
|
157
|
+
inputTokens={log.inputTokens}
|
|
158
|
+
outputTokens={log.outputTokens}
|
|
159
|
+
cacheCreationInputTokens={log.cacheCreationInputTokens}
|
|
160
|
+
cacheReadInputTokens={log.cacheReadInputTokens}
|
|
161
|
+
apiFormat={log.apiFormat}
|
|
162
|
+
/>
|
|
163
|
+
</TabsContent>
|
|
164
|
+
<TabsContent value="raw">
|
|
165
|
+
<pre className="font-mono text-xs whitespace-pre-wrap bg-muted p-3 rounded-md max-h-96 overflow-auto">
|
|
166
|
+
{log.responseText}
|
|
167
|
+
</pre>
|
|
168
|
+
</TabsContent>
|
|
169
|
+
</Tabs>
|
|
170
|
+
) : (
|
|
171
|
+
<p className="text-sm text-muted-foreground italic">No original response</p>
|
|
172
|
+
)}
|
|
173
|
+
</TabsContent>
|
|
174
|
+
|
|
175
|
+
{replayResult && replayResult.success && (
|
|
176
|
+
<TabsContent value="replay">
|
|
177
|
+
{replayResult.responseText !== null ? (
|
|
178
|
+
<Tabs defaultValue="parsed">
|
|
179
|
+
<TabsList>
|
|
180
|
+
<TabsTrigger value="parsed">Parsed Response</TabsTrigger>
|
|
181
|
+
<TabsTrigger value="raw">Raw Response</TabsTrigger>
|
|
182
|
+
</TabsList>
|
|
183
|
+
<TabsContent value="parsed">
|
|
184
|
+
<ResponseView
|
|
185
|
+
responseText={replayResult.responseText ?? null}
|
|
186
|
+
responseStatus={replayResult.responseStatus ?? null}
|
|
187
|
+
streaming={replayResult.streaming ?? false}
|
|
188
|
+
inputTokens={replayResult.inputTokens ?? null}
|
|
189
|
+
outputTokens={replayResult.outputTokens ?? null}
|
|
190
|
+
apiFormat={log.apiFormat}
|
|
191
|
+
/>
|
|
192
|
+
</TabsContent>
|
|
193
|
+
<TabsContent value="raw">
|
|
194
|
+
<pre className="font-mono text-xs whitespace-pre-wrap bg-muted p-3 rounded-md max-h-96 overflow-auto">
|
|
195
|
+
{replayResult.responseText}
|
|
196
|
+
</pre>
|
|
197
|
+
</TabsContent>
|
|
198
|
+
</Tabs>
|
|
199
|
+
) : (
|
|
200
|
+
<p className="text-sm text-muted-foreground italic">No replay response</p>
|
|
201
|
+
)}
|
|
202
|
+
</TabsContent>
|
|
203
|
+
)}
|
|
204
|
+
</Tabs>
|
|
205
|
+
</DialogContent>
|
|
206
|
+
</Dialog>
|
|
207
|
+
);
|
|
208
|
+
}
|