@tonyclaw/llm-inspector 1.16.3 → 1.16.5
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 +1 -1
- package/.output/public/assets/CompareDrawer-C1w4KUGZ.js +1 -0
- package/.output/public/assets/ReplayDialog-DR2Sgq_g.js +1 -0
- package/.output/public/assets/RequestAnatomy-DAre35kj.js +1 -0
- package/.output/public/assets/ResponseView-ackes7_g.js +1 -0
- package/.output/public/assets/StreamingChunkSequence-GrXwIGKA.js +1 -0
- package/.output/public/assets/index-BGzHFOEX.css +1 -0
- package/.output/public/assets/index-DX88k9br.js +101 -0
- package/.output/public/assets/json-viewer-C_QUhGeu.js +14 -0
- package/.output/public/assets/{main-Cpts3Ifr.js → main-CDMdNDY_.js} +1 -1
- package/.output/server/_libs/lucide-react.mjs +96 -76
- package/.output/server/_ssr/CompareDrawer-ftkJxyk6.mjs +1040 -0
- package/.output/server/_ssr/ReplayDialog-DcmE3lj5.mjs +321 -0
- package/.output/server/_ssr/RequestAnatomy-rK_LNMdG.mjs +351 -0
- package/.output/server/_ssr/ResponseView-CbQ4n-aJ.mjs +601 -0
- package/.output/server/_ssr/StreamingChunkSequence-84FZkIzv.mjs +301 -0
- package/.output/server/_ssr/{index-CjvQZBI0.mjs → index-CDjLoMsk.mjs} +1036 -2352
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/json-viewer-B-qpM5xC.mjs +510 -0
- package/.output/server/_ssr/{router-CO9_4CVh.mjs → router-BrdjOUEW.mjs} +24 -14
- package/.output/server/{_tanstack-start-manifest_v-D-9SW7K3.mjs → _tanstack-start-manifest_v-DmOZEcJ3.mjs} +1 -1
- package/.output/server/index.mjs +72 -30
- package/package.json +1 -1
- package/src/components/OnboardingBanner.tsx +2 -2
- package/src/components/ProxyViewer.tsx +38 -26
- package/src/components/ProxyViewerContainer.tsx +3 -24
- package/src/components/proxy-viewer/CompareDrawer.tsx +6 -6
- package/src/components/proxy-viewer/ConversationGroup.tsx +1 -1
- package/src/components/proxy-viewer/ConversationHeader.tsx +4 -1
- package/src/components/proxy-viewer/LogEntry.tsx +230 -163
- package/src/components/proxy-viewer/LogEntryHeader.tsx +134 -36
- package/src/components/proxy-viewer/StreamingChunkSequence.tsx +1 -1
- package/src/components/proxy-viewer/ThreadConnector.tsx +17 -2
- package/src/components/proxy-viewer/TurnGroup.tsx +94 -71
- 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/formats/anthropic/ContentBlocks.tsx +4 -24
- package/src/components/proxy-viewer/formats/anthropic/thinkingExtract.ts +21 -0
- package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +6 -4
- package/src/components/proxy-viewer/lazy.ts +37 -0
- package/src/components/proxy-viewer/log-formats/anthropic.ts +146 -0
- package/src/components/proxy-viewer/log-formats/openai.ts +127 -0
- package/src/components/proxy-viewer/log-formats/types.ts +7 -0
- package/src/components/proxy-viewer/log-formats/unknown.ts +4 -0
- package/src/components/proxy-viewer/logEntryVisibility.ts +39 -0
- package/src/components/proxy-viewer/useKeyboardNavigation.ts +190 -0
- package/src/components/proxy-viewer/viewerState.ts +8 -0
- package/src/components/ui/crab-variants.tsx +11 -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 +129 -118
- package/src/lib/utils.ts +2 -3
- package/src/routes/api/logs.stream.ts +26 -16
- package/.output/public/assets/index-DRRCmu5p.css +0 -1
- package/.output/public/assets/index-DfjhkDNi.js +0 -107
|
@@ -1,20 +1,40 @@
|
|
|
1
|
-
import { Check, Copy, GitCompareArrows
|
|
2
|
-
import type
|
|
1
|
+
import { Check, Copy, GitCompareArrows } from "lucide-react";
|
|
2
|
+
import { Suspense, type JSX } from "react";
|
|
3
3
|
import { useCallback, useEffect, useMemo, useRef, useState, memo } from "react";
|
|
4
|
-
import { cn } from "../../lib/utils";
|
|
5
4
|
import type { CapturedLog } from "../../proxy/schemas";
|
|
6
5
|
import { stripClaudeCodeBillingHeader } from "../../proxy/claudeCodeStrip";
|
|
7
6
|
import { Button } from "../ui/button";
|
|
8
7
|
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "../ui/tooltip";
|
|
9
|
-
import {
|
|
8
|
+
import { JsonExpansionButton } from "../ui/json-expansion-button";
|
|
9
|
+
import { useJsonBulkExpansion } from "../ui/json-viewer-bulk";
|
|
10
10
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
|
|
11
|
+
import {
|
|
12
|
+
LazyJsonViewer,
|
|
13
|
+
LazyJsonViewerFromString,
|
|
14
|
+
LazyReplayDialog,
|
|
15
|
+
LazyRequestAnatomy,
|
|
16
|
+
LazyResponseView,
|
|
17
|
+
LazyStreamingChunkSequence,
|
|
18
|
+
} from "./lazy";
|
|
19
|
+
import type { AnatomySegment } from "./anatomy/types";
|
|
20
|
+
import { useAnatomyJump } from "./anatomy/useAnatomyJump";
|
|
11
21
|
import { computeHeadersDiff, computeRequestDiff, DiffView } from "./diff";
|
|
12
22
|
import { LogEntryHeader } from "./LogEntryHeader";
|
|
13
|
-
import { ReplayDialog } from "./ReplayDialog";
|
|
14
|
-
import { ResponseView } from "./ResponseView";
|
|
15
|
-
import { StreamingChunkSequence } from "./StreamingChunkSequence";
|
|
16
23
|
import type { CacheTrendEntry } from "./cacheTrend";
|
|
17
24
|
import { getLogFormatAdapter, resolveLogFormat } from "./log-formats";
|
|
25
|
+
import {
|
|
26
|
+
shouldShowHeadersDiffButton,
|
|
27
|
+
shouldShowRawRequestTab,
|
|
28
|
+
shouldShowRequestDiffButton,
|
|
29
|
+
} from "./logEntryVisibility";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Lightweight fallback for lazy-loaded tabs. Renders an empty 1px-tall row so
|
|
33
|
+
* the layout doesn't jump while the chunk is fetching.
|
|
34
|
+
*/
|
|
35
|
+
function TabFallback(): JSX.Element {
|
|
36
|
+
return <div className="h-1" aria-hidden="true" />;
|
|
37
|
+
}
|
|
18
38
|
|
|
19
39
|
export type LogEntryProps = {
|
|
20
40
|
log: CapturedLog;
|
|
@@ -34,46 +54,6 @@ export type LogEntryProps = {
|
|
|
34
54
|
onCompareWithPrevious?: (log: CapturedLog) => void;
|
|
35
55
|
};
|
|
36
56
|
|
|
37
|
-
/**
|
|
38
|
-
* Pure visibility rule for the "Raw Request" tab. Extracted so it can be
|
|
39
|
-
* unit-tested without rendering React. The tab appears only when all three
|
|
40
|
-
* are true: anthropic format, full view mode, strip toggle enabled.
|
|
41
|
-
*/
|
|
42
|
-
export function shouldShowRawRequestTab(
|
|
43
|
-
apiFormat: string,
|
|
44
|
-
viewMode: "simple" | "full",
|
|
45
|
-
strip: boolean,
|
|
46
|
-
): boolean {
|
|
47
|
-
return apiFormat === "anthropic" && viewMode === "full" && strip;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Pure visibility rule for the "Diff with Raw" button in the Headers tab.
|
|
52
|
-
* The button only makes sense when the user is in full mode (where the
|
|
53
|
-
* `Raw Headers` tab is shown) and we actually captured raw headers.
|
|
54
|
-
*/
|
|
55
|
-
export function shouldShowHeadersDiffButton(
|
|
56
|
-
viewMode: "simple" | "full",
|
|
57
|
-
hasRawHeaders: boolean,
|
|
58
|
-
): boolean {
|
|
59
|
-
return viewMode === "full" && hasRawHeaders;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Pure visibility rule for the "Diff with Raw" button in the Request tab.
|
|
64
|
-
* Mirrors the conditions for the `Raw Request` tab itself: full mode plus
|
|
65
|
-
* the strip toggle being on for an anthropic-format request. We also need
|
|
66
|
-
* an actual raw request body to diff against.
|
|
67
|
-
*/
|
|
68
|
-
export function shouldShowRequestDiffButton(
|
|
69
|
-
apiFormat: string,
|
|
70
|
-
viewMode: "simple" | "full",
|
|
71
|
-
strip: boolean,
|
|
72
|
-
hasRawRequest: boolean,
|
|
73
|
-
): boolean {
|
|
74
|
-
return apiFormat === "anthropic" && viewMode === "full" && strip && hasRawRequest;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
57
|
function CopyButton({
|
|
78
58
|
text,
|
|
79
59
|
label,
|
|
@@ -87,23 +67,25 @@ function CopyButton({
|
|
|
87
67
|
}): JSX.Element | null {
|
|
88
68
|
if (text === null) return null;
|
|
89
69
|
return (
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
70
|
+
<Tooltip>
|
|
71
|
+
<TooltipTrigger asChild>
|
|
72
|
+
<Button
|
|
73
|
+
variant="outline"
|
|
74
|
+
size="sm"
|
|
75
|
+
className="h-8 text-xs"
|
|
76
|
+
onClick={onCopy}
|
|
77
|
+
aria-label={label}
|
|
78
|
+
>
|
|
79
|
+
{copied ? (
|
|
80
|
+
<Check className="size-3.5 mr-1 text-emerald-500" />
|
|
81
|
+
) : (
|
|
82
|
+
<Copy className="size-3.5 mr-1" />
|
|
83
|
+
)}
|
|
84
|
+
{copied ? "Copied!" : label}
|
|
85
|
+
</Button>
|
|
86
|
+
</TooltipTrigger>
|
|
87
|
+
<TooltipContent>{copied ? "Copied to clipboard" : label}</TooltipContent>
|
|
88
|
+
</Tooltip>
|
|
107
89
|
);
|
|
108
90
|
}
|
|
109
91
|
|
|
@@ -117,20 +99,16 @@ function DiffToggleButton({
|
|
|
117
99
|
return (
|
|
118
100
|
<Tooltip>
|
|
119
101
|
<TooltipTrigger asChild>
|
|
120
|
-
<
|
|
121
|
-
|
|
102
|
+
<Button
|
|
103
|
+
variant={active ? "default" : "outline"}
|
|
104
|
+
size="sm"
|
|
105
|
+
className="h-8 text-xs"
|
|
122
106
|
onClick={onClick}
|
|
123
107
|
aria-pressed={active}
|
|
124
|
-
className={cn(
|
|
125
|
-
"flex items-center gap-1.5 text-xs px-2 py-1 rounded transition-colors",
|
|
126
|
-
active
|
|
127
|
-
? "bg-primary/10 text-primary"
|
|
128
|
-
: "text-muted-foreground hover:text-foreground hover:bg-muted",
|
|
129
|
-
)}
|
|
130
108
|
>
|
|
131
|
-
<GitCompareArrows className="size-3" />
|
|
109
|
+
<GitCompareArrows className="size-3.5 mr-1" />
|
|
132
110
|
{active ? "Showing diff" : "Diff with Raw"}
|
|
133
|
-
</
|
|
111
|
+
</Button>
|
|
134
112
|
</TooltipTrigger>
|
|
135
113
|
<TooltipContent>
|
|
136
114
|
{active ? "Hide diff view" : "Compare proxy output against the original raw version"}
|
|
@@ -210,6 +188,8 @@ export const LogEntry = memo(function ({
|
|
|
210
188
|
const [headersDiff, setHeadersDiff] = useState<boolean>(false);
|
|
211
189
|
const [requestDiff, setRequestDiff] = useState<boolean>(false);
|
|
212
190
|
const [activeTab, setActiveTab] = useState("request");
|
|
191
|
+
const [expandToPath, setExpandToPath] = useState<string | null>(null);
|
|
192
|
+
const requestJsonRef = useRef<HTMLDivElement | null>(null);
|
|
213
193
|
const resolvedFormat = resolveLogFormat(log);
|
|
214
194
|
const adapter = getLogFormatAdapter(resolvedFormat);
|
|
215
195
|
const requestAnalysis = useMemo(
|
|
@@ -230,6 +210,26 @@ export const LogEntry = memo(function ({
|
|
|
230
210
|
const requestCopy = useCopyFeedback(displayedRequestBody);
|
|
231
211
|
const rawRequestCopy = useCopyFeedback(log.rawRequestBody);
|
|
232
212
|
const responseCopy = useCopyFeedback(log.responseText);
|
|
213
|
+
const requestExpansion = useJsonBulkExpansion(displayedRequestBody);
|
|
214
|
+
const rawRequestExpansion = useJsonBulkExpansion(log.rawRequestBody);
|
|
215
|
+
const anatomySegments = useMemo(
|
|
216
|
+
() =>
|
|
217
|
+
requestExpansion.parsedData !== null
|
|
218
|
+
? adapter.anatomySegments(requestExpansion.parsedData)
|
|
219
|
+
: null,
|
|
220
|
+
[adapter, requestExpansion.parsedData],
|
|
221
|
+
);
|
|
222
|
+
const anatomyPaths = useMemo(() => {
|
|
223
|
+
if (anatomySegments === null) return undefined;
|
|
224
|
+
return new Set(anatomySegments.map((s) => s.path));
|
|
225
|
+
}, [anatomySegments]);
|
|
226
|
+
const jumpToAnatomySegment = useAnatomyJump({
|
|
227
|
+
containerRef: requestJsonRef,
|
|
228
|
+
setExpandToPath,
|
|
229
|
+
ensureTabActive: () => {
|
|
230
|
+
if (activeTab !== "request") setActiveTab("request");
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
233
|
|
|
234
234
|
return (
|
|
235
235
|
<TooltipProvider>
|
|
@@ -242,6 +242,30 @@ export const LogEntry = memo(function ({
|
|
|
242
242
|
onToggle={() => setExpanded(!expanded)}
|
|
243
243
|
responseToolNames={responseAnalysis.toolNames}
|
|
244
244
|
cacheTrend={cacheTrend}
|
|
245
|
+
onReplay={
|
|
246
|
+
onCompareWithPrevious === undefined
|
|
247
|
+
? undefined
|
|
248
|
+
: () => {
|
|
249
|
+
setReplayOpen(true);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
onCopyRequest={
|
|
253
|
+
displayedRequestBody === null
|
|
254
|
+
? undefined
|
|
255
|
+
: (e) => {
|
|
256
|
+
requestCopy.copy(e);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
requestCopied={requestCopy.copied}
|
|
260
|
+
onToggleRequestExpansion={requestExpansion.toggle}
|
|
261
|
+
requestExpansionState={
|
|
262
|
+
requestExpansion.policy === null
|
|
263
|
+
? null
|
|
264
|
+
: {
|
|
265
|
+
isExpanded: requestExpansion.isExpanded,
|
|
266
|
+
isPending: requestExpansion.isPending,
|
|
267
|
+
}
|
|
268
|
+
}
|
|
245
269
|
/>
|
|
246
270
|
|
|
247
271
|
{expanded && (
|
|
@@ -254,6 +278,7 @@ export const LogEntry = memo(function ({
|
|
|
254
278
|
<TabsTrigger value="raw-request">Raw Request</TabsTrigger>
|
|
255
279
|
)}
|
|
256
280
|
<TabsTrigger value="request">Request</TabsTrigger>
|
|
281
|
+
{anatomySegments !== null && <TabsTrigger value="anatomy">Anatomy</TabsTrigger>}
|
|
257
282
|
{viewMode === "full" && <TabsTrigger value="raw">Raw Response</TabsTrigger>}
|
|
258
283
|
<TabsTrigger value="parsed">Response</TabsTrigger>
|
|
259
284
|
</TabsList>
|
|
@@ -261,19 +286,35 @@ export const LogEntry = memo(function ({
|
|
|
261
286
|
{shouldShowRawRequestTab(resolvedFormat, viewMode, strip) && (
|
|
262
287
|
<TabsContent value="raw-request">
|
|
263
288
|
{activeTab === "raw-request" && (
|
|
264
|
-
<div className="px-4
|
|
265
|
-
<div className="flex justify-end mb-2">
|
|
289
|
+
<div className="px-4 pt-1 pb-3">
|
|
290
|
+
<div className="flex justify-end gap-2 mb-2">
|
|
291
|
+
<JsonExpansionButton
|
|
292
|
+
policy={rawRequestExpansion.policy}
|
|
293
|
+
isExpanded={rawRequestExpansion.isExpanded}
|
|
294
|
+
isPending={rawRequestExpansion.isPending}
|
|
295
|
+
onToggle={rawRequestExpansion.toggle}
|
|
296
|
+
/>
|
|
266
297
|
<CopyButton
|
|
267
298
|
text={log.rawRequestBody}
|
|
268
|
-
label="Copy
|
|
299
|
+
label="Copy"
|
|
269
300
|
copied={rawRequestCopy.copied}
|
|
270
301
|
onCopy={rawRequestCopy.copy}
|
|
271
302
|
/>
|
|
272
303
|
</div>
|
|
273
|
-
{log.rawRequestBody
|
|
274
|
-
<JsonViewerFromString text={log.rawRequestBody} defaultExpandDepth={1} />
|
|
275
|
-
) : (
|
|
304
|
+
{log.rawRequestBody === null ? (
|
|
276
305
|
<p className="text-xs text-muted-foreground italic">No request body</p>
|
|
306
|
+
) : rawRequestExpansion.parsedData !== null ? (
|
|
307
|
+
<Suspense fallback={<TabFallback />}>
|
|
308
|
+
<LazyJsonViewer
|
|
309
|
+
data={rawRequestExpansion.parsedData}
|
|
310
|
+
bulkDepth={rawRequestExpansion.bulkDepth}
|
|
311
|
+
bulkRevision={rawRequestExpansion.bulkRevision}
|
|
312
|
+
/>
|
|
313
|
+
</Suspense>
|
|
314
|
+
) : (
|
|
315
|
+
<pre className="font-mono text-xs whitespace-pre-wrap break-words">
|
|
316
|
+
{log.rawRequestBody}
|
|
317
|
+
</pre>
|
|
277
318
|
)}
|
|
278
319
|
</div>
|
|
279
320
|
)}
|
|
@@ -282,86 +323,104 @@ export const LogEntry = memo(function ({
|
|
|
282
323
|
|
|
283
324
|
<TabsContent value="request">
|
|
284
325
|
{activeTab === "request" && (
|
|
285
|
-
<div className="px-4
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
<Button
|
|
305
|
-
variant="outline"
|
|
306
|
-
size="sm"
|
|
307
|
-
className="h-7 text-xs"
|
|
308
|
-
onClick={(e) => {
|
|
309
|
-
e.stopPropagation();
|
|
310
|
-
onCompareWithPrevious(log);
|
|
311
|
-
}}
|
|
312
|
-
>
|
|
313
|
-
<GitCompareArrows className="size-3 mr-1" />
|
|
314
|
-
Diff with Previous
|
|
315
|
-
</Button>
|
|
316
|
-
</TooltipTrigger>
|
|
317
|
-
<TooltipContent>
|
|
318
|
-
Compare this request with the immediately preceding one
|
|
319
|
-
</TooltipContent>
|
|
320
|
-
</Tooltip>
|
|
321
|
-
)}
|
|
322
|
-
<Tooltip>
|
|
323
|
-
<TooltipTrigger asChild>
|
|
324
|
-
<Button
|
|
325
|
-
variant="outline"
|
|
326
|
-
size="sm"
|
|
327
|
-
className="h-7 text-xs"
|
|
326
|
+
<div className="px-4 pt-1 pb-3">
|
|
327
|
+
{/* Per-tab secondary actions (diff toggles, compare-with-previous).
|
|
328
|
+
Replay / Copy / Expand-all JSON now live in the header. */}
|
|
329
|
+
{(shouldShowRequestDiffButton(
|
|
330
|
+
resolvedFormat,
|
|
331
|
+
viewMode,
|
|
332
|
+
strip,
|
|
333
|
+
log.rawRequestBody !== null,
|
|
334
|
+
) ||
|
|
335
|
+
onCompareWithPrevious !== undefined) && (
|
|
336
|
+
<div className="flex justify-end gap-2 mb-2">
|
|
337
|
+
{shouldShowRequestDiffButton(
|
|
338
|
+
resolvedFormat,
|
|
339
|
+
viewMode,
|
|
340
|
+
strip,
|
|
341
|
+
log.rawRequestBody !== null,
|
|
342
|
+
) && (
|
|
343
|
+
<DiffToggleButton
|
|
344
|
+
active={requestDiff}
|
|
328
345
|
onClick={(e) => {
|
|
329
346
|
e.stopPropagation();
|
|
330
|
-
|
|
347
|
+
setRequestDiff(!requestDiff);
|
|
331
348
|
}}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
349
|
+
/>
|
|
350
|
+
)}
|
|
351
|
+
{onCompareWithPrevious !== undefined && (
|
|
352
|
+
<Tooltip>
|
|
353
|
+
<TooltipTrigger asChild>
|
|
354
|
+
<Button
|
|
355
|
+
variant="outline"
|
|
356
|
+
size="sm"
|
|
357
|
+
className="h-8 text-xs"
|
|
358
|
+
onClick={(e) => {
|
|
359
|
+
e.stopPropagation();
|
|
360
|
+
onCompareWithPrevious(log);
|
|
361
|
+
}}
|
|
362
|
+
>
|
|
363
|
+
<GitCompareArrows className="size-3 mr-1" />
|
|
364
|
+
Diff with Previous
|
|
365
|
+
</Button>
|
|
366
|
+
</TooltipTrigger>
|
|
367
|
+
<TooltipContent>
|
|
368
|
+
Compare this request with the immediately preceding one
|
|
369
|
+
</TooltipContent>
|
|
370
|
+
</Tooltip>
|
|
371
|
+
)}
|
|
372
|
+
</div>
|
|
373
|
+
)}
|
|
346
374
|
{requestDiff ? (
|
|
347
375
|
<RequestDiffContent
|
|
348
376
|
rawBody={log.rawRequestBody}
|
|
349
377
|
displayedBody={displayedRequestBody}
|
|
350
378
|
emptyLabel="No transformation applied — raw and sent request bodies are identical."
|
|
351
379
|
/>
|
|
352
|
-
) : displayedRequestBody !== null ? (
|
|
353
|
-
<JsonViewerFromString text={displayedRequestBody} defaultExpandDepth={1} />
|
|
354
380
|
) : (
|
|
355
|
-
<
|
|
381
|
+
<div ref={requestJsonRef}>
|
|
382
|
+
{displayedRequestBody === null ? (
|
|
383
|
+
<p className="text-xs text-muted-foreground italic">No request body</p>
|
|
384
|
+
) : requestExpansion.parsedData !== null ? (
|
|
385
|
+
<Suspense fallback={<TabFallback />}>
|
|
386
|
+
<LazyJsonViewer
|
|
387
|
+
data={requestExpansion.parsedData}
|
|
388
|
+
bulkDepth={requestExpansion.bulkDepth}
|
|
389
|
+
bulkRevision={requestExpansion.bulkRevision}
|
|
390
|
+
anatomyPaths={anatomyPaths}
|
|
391
|
+
expandToPath={expandToPath}
|
|
392
|
+
/>
|
|
393
|
+
</Suspense>
|
|
394
|
+
) : (
|
|
395
|
+
<pre className="font-mono text-xs whitespace-pre-wrap break-words">
|
|
396
|
+
{displayedRequestBody}
|
|
397
|
+
</pre>
|
|
398
|
+
)}
|
|
399
|
+
</div>
|
|
356
400
|
)}
|
|
357
401
|
</div>
|
|
358
402
|
)}
|
|
359
403
|
</TabsContent>
|
|
360
404
|
|
|
405
|
+
{anatomySegments !== null && (
|
|
406
|
+
<TabsContent value="anatomy">
|
|
407
|
+
{activeTab === "anatomy" && (
|
|
408
|
+
<Suspense fallback={<TabFallback />}>
|
|
409
|
+
<LazyRequestAnatomy
|
|
410
|
+
parsed={null}
|
|
411
|
+
inputTokens={log.inputTokens ?? null}
|
|
412
|
+
segments={anatomySegments}
|
|
413
|
+
onSegmentActivate={jumpToAnatomySegment}
|
|
414
|
+
/>
|
|
415
|
+
</Suspense>
|
|
416
|
+
)}
|
|
417
|
+
</TabsContent>
|
|
418
|
+
)}
|
|
419
|
+
|
|
361
420
|
{viewMode === "full" && (
|
|
362
421
|
<TabsContent value="headers">
|
|
363
422
|
{activeTab === "headers" && (
|
|
364
|
-
<div className="px-4
|
|
423
|
+
<div className="px-4 pt-1 pb-3">
|
|
365
424
|
<div className="flex justify-end gap-2 mb-2">
|
|
366
425
|
{shouldShowHeadersDiffButton(
|
|
367
426
|
viewMode,
|
|
@@ -408,7 +467,7 @@ export const LogEntry = memo(function ({
|
|
|
408
467
|
{viewMode === "full" && (
|
|
409
468
|
<TabsContent value="raw-headers">
|
|
410
469
|
{activeTab === "raw-headers" && (
|
|
411
|
-
<div className="px-4
|
|
470
|
+
<div className="px-4 pt-1 pb-3">
|
|
412
471
|
{log.rawHeaders && Object.keys(log.rawHeaders).length > 0 ? (
|
|
413
472
|
<div className="space-y-1 font-mono text-xs">
|
|
414
473
|
{Object.entries(log.rawHeaders)
|
|
@@ -436,7 +495,7 @@ export const LogEntry = memo(function ({
|
|
|
436
495
|
|
|
437
496
|
<TabsContent value="raw">
|
|
438
497
|
{activeTab === "raw" && (
|
|
439
|
-
<div className="px-4
|
|
498
|
+
<div className="px-4 pt-1 pb-3 space-y-3">
|
|
440
499
|
{log.error !== undefined && log.error !== null && (
|
|
441
500
|
<div className="rounded border border-destructive/50 bg-destructive/10 p-3 text-xs">
|
|
442
501
|
<div className="font-semibold text-destructive mb-1">SSE Error</div>
|
|
@@ -446,21 +505,25 @@ export const LogEntry = memo(function ({
|
|
|
446
505
|
<div className="flex justify-end">
|
|
447
506
|
<CopyButton
|
|
448
507
|
text={log.responseText}
|
|
449
|
-
label="Copy
|
|
508
|
+
label="Copy"
|
|
450
509
|
copied={responseCopy.copied}
|
|
451
510
|
onCopy={responseCopy.copy}
|
|
452
511
|
/>
|
|
453
512
|
</div>
|
|
454
513
|
{log.responseText !== null ? (
|
|
455
|
-
<
|
|
514
|
+
<Suspense fallback={<TabFallback />}>
|
|
515
|
+
<LazyJsonViewerFromString text={log.responseText} defaultExpandDepth={0} />
|
|
516
|
+
</Suspense>
|
|
456
517
|
) : (
|
|
457
518
|
<p className="text-xs text-muted-foreground italic">No response</p>
|
|
458
519
|
)}
|
|
459
520
|
{log.streaming === true && (
|
|
460
|
-
<
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
521
|
+
<Suspense fallback={<TabFallback />}>
|
|
522
|
+
<LazyStreamingChunkSequence
|
|
523
|
+
logId={log.id}
|
|
524
|
+
truncated={log.streamingChunksPath !== null}
|
|
525
|
+
/>
|
|
526
|
+
</Suspense>
|
|
464
527
|
)}
|
|
465
528
|
</div>
|
|
466
529
|
)}
|
|
@@ -468,18 +531,20 @@ export const LogEntry = memo(function ({
|
|
|
468
531
|
|
|
469
532
|
<TabsContent value="parsed">
|
|
470
533
|
{activeTab === "parsed" && (
|
|
471
|
-
<div className="px-4
|
|
472
|
-
<
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
534
|
+
<div className="px-4 pt-1 pb-3">
|
|
535
|
+
<Suspense fallback={<TabFallback />}>
|
|
536
|
+
<LazyResponseView
|
|
537
|
+
responseText={log.responseText}
|
|
538
|
+
responseStatus={log.responseStatus}
|
|
539
|
+
streaming={log.streaming}
|
|
540
|
+
inputTokens={log.inputTokens}
|
|
541
|
+
outputTokens={log.outputTokens}
|
|
542
|
+
cacheCreationInputTokens={log.cacheCreationInputTokens}
|
|
543
|
+
cacheReadInputTokens={log.cacheReadInputTokens}
|
|
544
|
+
apiFormat={resolvedFormat}
|
|
545
|
+
error={log.error}
|
|
546
|
+
/>
|
|
547
|
+
</Suspense>
|
|
483
548
|
</div>
|
|
484
549
|
)}
|
|
485
550
|
</TabsContent>
|
|
@@ -487,7 +552,9 @@ export const LogEntry = memo(function ({
|
|
|
487
552
|
</div>
|
|
488
553
|
)}
|
|
489
554
|
</div>
|
|
490
|
-
<
|
|
555
|
+
<Suspense fallback={null}>
|
|
556
|
+
<LazyReplayDialog log={log} open={replayOpen} onOpenChange={setReplayOpen} />
|
|
557
|
+
</Suspense>
|
|
491
558
|
</TooltipProvider>
|
|
492
559
|
);
|
|
493
560
|
});
|