@tonyclaw/llm-inspector 1.16.4 → 1.17.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 +1 -1
- package/.output/public/assets/CompareDrawer-C4fie5g5.js +1 -0
- package/.output/public/assets/ReplayDialog-Dme5uOR9.js +1 -0
- package/.output/public/assets/RequestAnatomy-ChBLDNFH.js +1 -0
- package/.output/public/assets/ResponseView-wGeqBzVU.js +1 -0
- package/.output/public/assets/StreamingChunkSequence-zeJZQLqT.js +1 -0
- package/.output/public/assets/index-DoGvsnbA.css +1 -0
- package/.output/public/assets/index-DpbutOvo.js +101 -0
- package/.output/public/assets/json-viewer-BV-WUszW.js +14 -0
- package/.output/public/assets/{main-DbWwVQFh.js → main-DRu10KNQ.js} +1 -1
- package/.output/server/_libs/lucide-react.mjs +105 -85
- package/.output/server/_ssr/CompareDrawer-C4-CQL5w.mjs +1040 -0
- package/.output/server/_ssr/ReplayDialog-BTb1Bam8.mjs +321 -0
- package/.output/server/_ssr/RequestAnatomy-CZFV1IvL.mjs +351 -0
- package/.output/server/_ssr/ResponseView-CTZekh65.mjs +601 -0
- package/.output/server/_ssr/StreamingChunkSequence-C38Ynabd.mjs +301 -0
- package/.output/server/_ssr/{index-C-z-fZtq.mjs → index-Cnu-QzAy.mjs} +1141 -2443
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/json-viewer-DROqpjS9.mjs +510 -0
- package/.output/server/_ssr/{router-CNM9Kbi0.mjs → router-pP4GCTQx.mjs} +42 -18
- package/.output/server/{_tanstack-start-manifest_v-BWfLeIsC.mjs → _tanstack-start-manifest_v-CphS4rZd.mjs} +1 -1
- package/.output/server/index.mjs +69 -27
- package/package.json +1 -1
- package/src/components/OnboardingBanner.tsx +2 -2
- package/src/components/ProxyViewer.tsx +44 -27
- package/src/components/ProxyViewerContainer.tsx +5 -25
- package/src/components/providers/SettingsDialog.tsx +52 -1
- package/src/components/proxy-viewer/ConversationGroup.tsx +5 -1
- package/src/components/proxy-viewer/ConversationHeader.tsx +4 -1
- package/src/components/proxy-viewer/LogEntry.tsx +217 -181
- package/src/components/proxy-viewer/LogEntryHeader.tsx +181 -40
- package/src/components/proxy-viewer/ThreadConnector.tsx +17 -2
- package/src/components/proxy-viewer/TurnGroup.tsx +124 -72
- 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 +3 -23
- package/src/components/proxy-viewer/formats/anthropic/thinkingExtract.ts +21 -0
- package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +5 -3
- 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 +58 -183
- package/src/lib/runtimeConfig.ts +9 -0
- package/src/lib/useOnboarding.ts +7 -1
- package/src/lib/useStripConfig.ts +33 -2
- package/src/lib/utils.ts +2 -3
- package/src/proxy/config.ts +17 -7
- package/src/routes/api/config.ts +7 -0
- package/src/routes/api/logs.stream.ts +26 -16
- package/.output/public/assets/index-DRRCmu5p.css +0 -1
- package/.output/public/assets/index-X7CHS7fS.js +0 -107
|
@@ -1,25 +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 {
|
|
10
|
-
|
|
11
|
-
JsonViewerFromString,
|
|
12
|
-
JsonExpansionButton,
|
|
13
|
-
useJsonBulkExpansion,
|
|
14
|
-
} from "../ui/json-viewer";
|
|
8
|
+
import { JsonExpansionButton } from "../ui/json-expansion-button";
|
|
9
|
+
import { useJsonBulkExpansion } from "../ui/json-viewer-bulk";
|
|
15
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";
|
|
16
21
|
import { computeHeadersDiff, computeRequestDiff, DiffView } from "./diff";
|
|
17
22
|
import { LogEntryHeader } from "./LogEntryHeader";
|
|
18
|
-
import { ReplayDialog } from "./ReplayDialog";
|
|
19
|
-
import { ResponseView } from "./ResponseView";
|
|
20
|
-
import { StreamingChunkSequence } from "./StreamingChunkSequence";
|
|
21
23
|
import type { CacheTrendEntry } from "./cacheTrend";
|
|
22
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
|
+
}
|
|
23
38
|
|
|
24
39
|
export type LogEntryProps = {
|
|
25
40
|
log: CapturedLog;
|
|
@@ -30,6 +45,8 @@ export type LogEntryProps = {
|
|
|
30
45
|
* cost).
|
|
31
46
|
*/
|
|
32
47
|
strip: boolean;
|
|
48
|
+
/** Slow-response threshold in seconds. `0` disables the warning indicator. */
|
|
49
|
+
slowResponseThresholdSeconds: number;
|
|
33
50
|
/**
|
|
34
51
|
* Per-log cache token trend, looked up in the viewer-level trend map.
|
|
35
52
|
* `null` (or absent) means the header should render with no arrows.
|
|
@@ -39,46 +56,6 @@ export type LogEntryProps = {
|
|
|
39
56
|
onCompareWithPrevious?: (log: CapturedLog) => void;
|
|
40
57
|
};
|
|
41
58
|
|
|
42
|
-
/**
|
|
43
|
-
* Pure visibility rule for the "Raw Request" tab. Extracted so it can be
|
|
44
|
-
* unit-tested without rendering React. The tab appears only when all three
|
|
45
|
-
* are true: anthropic format, full view mode, strip toggle enabled.
|
|
46
|
-
*/
|
|
47
|
-
export function shouldShowRawRequestTab(
|
|
48
|
-
apiFormat: string,
|
|
49
|
-
viewMode: "simple" | "full",
|
|
50
|
-
strip: boolean,
|
|
51
|
-
): boolean {
|
|
52
|
-
return apiFormat === "anthropic" && viewMode === "full" && strip;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Pure visibility rule for the "Diff with Raw" button in the Headers tab.
|
|
57
|
-
* The button only makes sense when the user is in full mode (where the
|
|
58
|
-
* `Raw Headers` tab is shown) and we actually captured raw headers.
|
|
59
|
-
*/
|
|
60
|
-
export function shouldShowHeadersDiffButton(
|
|
61
|
-
viewMode: "simple" | "full",
|
|
62
|
-
hasRawHeaders: boolean,
|
|
63
|
-
): boolean {
|
|
64
|
-
return viewMode === "full" && hasRawHeaders;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Pure visibility rule for the "Diff with Raw" button in the Request tab.
|
|
69
|
-
* Mirrors the conditions for the `Raw Request` tab itself: full mode plus
|
|
70
|
-
* the strip toggle being on for an anthropic-format request. We also need
|
|
71
|
-
* an actual raw request body to diff against.
|
|
72
|
-
*/
|
|
73
|
-
export function shouldShowRequestDiffButton(
|
|
74
|
-
apiFormat: string,
|
|
75
|
-
viewMode: "simple" | "full",
|
|
76
|
-
strip: boolean,
|
|
77
|
-
hasRawRequest: boolean,
|
|
78
|
-
): boolean {
|
|
79
|
-
return apiFormat === "anthropic" && viewMode === "full" && strip && hasRawRequest;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
59
|
function CopyButton({
|
|
83
60
|
text,
|
|
84
61
|
label,
|
|
@@ -92,23 +69,25 @@ function CopyButton({
|
|
|
92
69
|
}): JSX.Element | null {
|
|
93
70
|
if (text === null) return null;
|
|
94
71
|
return (
|
|
95
|
-
<
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
72
|
+
<Tooltip>
|
|
73
|
+
<TooltipTrigger asChild>
|
|
74
|
+
<Button
|
|
75
|
+
variant="outline"
|
|
76
|
+
size="sm"
|
|
77
|
+
className="h-8 text-xs"
|
|
78
|
+
onClick={onCopy}
|
|
79
|
+
aria-label={label}
|
|
80
|
+
>
|
|
81
|
+
{copied ? (
|
|
82
|
+
<Check className="size-3.5 mr-1 text-emerald-500" />
|
|
83
|
+
) : (
|
|
84
|
+
<Copy className="size-3.5 mr-1" />
|
|
85
|
+
)}
|
|
86
|
+
{copied ? "Copied!" : label}
|
|
87
|
+
</Button>
|
|
88
|
+
</TooltipTrigger>
|
|
89
|
+
<TooltipContent>{copied ? "Copied to clipboard" : label}</TooltipContent>
|
|
90
|
+
</Tooltip>
|
|
112
91
|
);
|
|
113
92
|
}
|
|
114
93
|
|
|
@@ -122,20 +101,16 @@ function DiffToggleButton({
|
|
|
122
101
|
return (
|
|
123
102
|
<Tooltip>
|
|
124
103
|
<TooltipTrigger asChild>
|
|
125
|
-
<
|
|
126
|
-
|
|
104
|
+
<Button
|
|
105
|
+
variant={active ? "default" : "outline"}
|
|
106
|
+
size="sm"
|
|
107
|
+
className="h-8 text-xs"
|
|
127
108
|
onClick={onClick}
|
|
128
109
|
aria-pressed={active}
|
|
129
|
-
className={cn(
|
|
130
|
-
"flex items-center gap-1.5 text-xs px-2 py-1 rounded transition-colors",
|
|
131
|
-
active
|
|
132
|
-
? "bg-primary/10 text-primary"
|
|
133
|
-
: "text-muted-foreground hover:text-foreground hover:bg-muted",
|
|
134
|
-
)}
|
|
135
110
|
>
|
|
136
|
-
<GitCompareArrows className="size-3" />
|
|
111
|
+
<GitCompareArrows className="size-3.5 mr-1" />
|
|
137
112
|
{active ? "Showing diff" : "Diff with Raw"}
|
|
138
|
-
</
|
|
113
|
+
</Button>
|
|
139
114
|
</TooltipTrigger>
|
|
140
115
|
<TooltipContent>
|
|
141
116
|
{active ? "Hide diff view" : "Compare proxy output against the original raw version"}
|
|
@@ -207,6 +182,7 @@ export const LogEntry = memo(function ({
|
|
|
207
182
|
log,
|
|
208
183
|
viewMode = "simple",
|
|
209
184
|
strip,
|
|
185
|
+
slowResponseThresholdSeconds,
|
|
210
186
|
cacheTrend = null,
|
|
211
187
|
onCompareWithPrevious,
|
|
212
188
|
}: LogEntryProps): JSX.Element {
|
|
@@ -215,6 +191,8 @@ export const LogEntry = memo(function ({
|
|
|
215
191
|
const [headersDiff, setHeadersDiff] = useState<boolean>(false);
|
|
216
192
|
const [requestDiff, setRequestDiff] = useState<boolean>(false);
|
|
217
193
|
const [activeTab, setActiveTab] = useState("request");
|
|
194
|
+
const [expandToPath, setExpandToPath] = useState<string | null>(null);
|
|
195
|
+
const requestJsonRef = useRef<HTMLDivElement | null>(null);
|
|
218
196
|
const resolvedFormat = resolveLogFormat(log);
|
|
219
197
|
const adapter = getLogFormatAdapter(resolvedFormat);
|
|
220
198
|
const requestAnalysis = useMemo(
|
|
@@ -237,6 +215,24 @@ export const LogEntry = memo(function ({
|
|
|
237
215
|
const responseCopy = useCopyFeedback(log.responseText);
|
|
238
216
|
const requestExpansion = useJsonBulkExpansion(displayedRequestBody);
|
|
239
217
|
const rawRequestExpansion = useJsonBulkExpansion(log.rawRequestBody);
|
|
218
|
+
const anatomySegments = useMemo(
|
|
219
|
+
() =>
|
|
220
|
+
requestExpansion.parsedData !== null
|
|
221
|
+
? adapter.anatomySegments(requestExpansion.parsedData)
|
|
222
|
+
: null,
|
|
223
|
+
[adapter, requestExpansion.parsedData],
|
|
224
|
+
);
|
|
225
|
+
const anatomyPaths = useMemo(() => {
|
|
226
|
+
if (anatomySegments === null) return undefined;
|
|
227
|
+
return new Set(anatomySegments.map((s) => s.path));
|
|
228
|
+
}, [anatomySegments]);
|
|
229
|
+
const jumpToAnatomySegment = useAnatomyJump({
|
|
230
|
+
containerRef: requestJsonRef,
|
|
231
|
+
setExpandToPath,
|
|
232
|
+
ensureTabActive: () => {
|
|
233
|
+
if (activeTab !== "request") setActiveTab("request");
|
|
234
|
+
},
|
|
235
|
+
});
|
|
240
236
|
|
|
241
237
|
return (
|
|
242
238
|
<TooltipProvider>
|
|
@@ -249,6 +245,31 @@ export const LogEntry = memo(function ({
|
|
|
249
245
|
onToggle={() => setExpanded(!expanded)}
|
|
250
246
|
responseToolNames={responseAnalysis.toolNames}
|
|
251
247
|
cacheTrend={cacheTrend}
|
|
248
|
+
slowResponseThresholdSeconds={slowResponseThresholdSeconds}
|
|
249
|
+
onReplay={
|
|
250
|
+
onCompareWithPrevious === undefined
|
|
251
|
+
? undefined
|
|
252
|
+
: () => {
|
|
253
|
+
setReplayOpen(true);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
onCopyRequest={
|
|
257
|
+
displayedRequestBody === null
|
|
258
|
+
? undefined
|
|
259
|
+
: (e) => {
|
|
260
|
+
requestCopy.copy(e);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
requestCopied={requestCopy.copied}
|
|
264
|
+
onToggleRequestExpansion={requestExpansion.toggle}
|
|
265
|
+
requestExpansionState={
|
|
266
|
+
requestExpansion.policy === null
|
|
267
|
+
? null
|
|
268
|
+
: {
|
|
269
|
+
isExpanded: requestExpansion.isExpanded,
|
|
270
|
+
isPending: requestExpansion.isPending,
|
|
271
|
+
}
|
|
272
|
+
}
|
|
252
273
|
/>
|
|
253
274
|
|
|
254
275
|
{expanded && (
|
|
@@ -261,6 +282,7 @@ export const LogEntry = memo(function ({
|
|
|
261
282
|
<TabsTrigger value="raw-request">Raw Request</TabsTrigger>
|
|
262
283
|
)}
|
|
263
284
|
<TabsTrigger value="request">Request</TabsTrigger>
|
|
285
|
+
{anatomySegments !== null && <TabsTrigger value="anatomy">Anatomy</TabsTrigger>}
|
|
264
286
|
{viewMode === "full" && <TabsTrigger value="raw">Raw Response</TabsTrigger>}
|
|
265
287
|
<TabsTrigger value="parsed">Response</TabsTrigger>
|
|
266
288
|
</TabsList>
|
|
@@ -268,7 +290,7 @@ export const LogEntry = memo(function ({
|
|
|
268
290
|
{shouldShowRawRequestTab(resolvedFormat, viewMode, strip) && (
|
|
269
291
|
<TabsContent value="raw-request">
|
|
270
292
|
{activeTab === "raw-request" && (
|
|
271
|
-
<div className="px-4
|
|
293
|
+
<div className="px-4 pt-1 pb-3">
|
|
272
294
|
<div className="flex justify-end gap-2 mb-2">
|
|
273
295
|
<JsonExpansionButton
|
|
274
296
|
policy={rawRequestExpansion.policy}
|
|
@@ -286,11 +308,13 @@ export const LogEntry = memo(function ({
|
|
|
286
308
|
{log.rawRequestBody === null ? (
|
|
287
309
|
<p className="text-xs text-muted-foreground italic">No request body</p>
|
|
288
310
|
) : rawRequestExpansion.parsedData !== null ? (
|
|
289
|
-
<
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
311
|
+
<Suspense fallback={<TabFallback />}>
|
|
312
|
+
<LazyJsonViewer
|
|
313
|
+
data={rawRequestExpansion.parsedData}
|
|
314
|
+
bulkDepth={rawRequestExpansion.bulkDepth}
|
|
315
|
+
bulkRevision={rawRequestExpansion.bulkRevision}
|
|
316
|
+
/>
|
|
317
|
+
</Suspense>
|
|
294
318
|
) : (
|
|
295
319
|
<pre className="font-mono text-xs whitespace-pre-wrap break-words">
|
|
296
320
|
{log.rawRequestBody}
|
|
@@ -303,100 +327,104 @@ export const LogEntry = memo(function ({
|
|
|
303
327
|
|
|
304
328
|
<TabsContent value="request">
|
|
305
329
|
{activeTab === "request" && (
|
|
306
|
-
<div className="px-4
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
<Button
|
|
326
|
-
variant="outline"
|
|
327
|
-
size="sm"
|
|
328
|
-
className="h-7 text-xs"
|
|
329
|
-
onClick={(e) => {
|
|
330
|
-
e.stopPropagation();
|
|
331
|
-
onCompareWithPrevious(log);
|
|
332
|
-
}}
|
|
333
|
-
>
|
|
334
|
-
<GitCompareArrows className="size-3 mr-1" />
|
|
335
|
-
Diff with Previous
|
|
336
|
-
</Button>
|
|
337
|
-
</TooltipTrigger>
|
|
338
|
-
<TooltipContent>
|
|
339
|
-
Compare this request with the immediately preceding one
|
|
340
|
-
</TooltipContent>
|
|
341
|
-
</Tooltip>
|
|
342
|
-
)}
|
|
343
|
-
<Tooltip>
|
|
344
|
-
<TooltipTrigger asChild>
|
|
345
|
-
<Button
|
|
346
|
-
variant="outline"
|
|
347
|
-
size="sm"
|
|
348
|
-
className="h-7 text-xs"
|
|
330
|
+
<div className="px-4 pt-1 pb-3">
|
|
331
|
+
{/* Per-tab secondary actions (diff toggles, compare-with-previous).
|
|
332
|
+
Replay / Copy / Expand-all JSON now live in the header. */}
|
|
333
|
+
{(shouldShowRequestDiffButton(
|
|
334
|
+
resolvedFormat,
|
|
335
|
+
viewMode,
|
|
336
|
+
strip,
|
|
337
|
+
log.rawRequestBody !== null,
|
|
338
|
+
) ||
|
|
339
|
+
onCompareWithPrevious !== undefined) && (
|
|
340
|
+
<div className="flex justify-end gap-2 mb-2">
|
|
341
|
+
{shouldShowRequestDiffButton(
|
|
342
|
+
resolvedFormat,
|
|
343
|
+
viewMode,
|
|
344
|
+
strip,
|
|
345
|
+
log.rawRequestBody !== null,
|
|
346
|
+
) && (
|
|
347
|
+
<DiffToggleButton
|
|
348
|
+
active={requestDiff}
|
|
349
349
|
onClick={(e) => {
|
|
350
350
|
e.stopPropagation();
|
|
351
|
-
|
|
351
|
+
setRequestDiff(!requestDiff);
|
|
352
352
|
}}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
353
|
+
/>
|
|
354
|
+
)}
|
|
355
|
+
{onCompareWithPrevious !== undefined && (
|
|
356
|
+
<Tooltip>
|
|
357
|
+
<TooltipTrigger asChild>
|
|
358
|
+
<Button
|
|
359
|
+
variant="outline"
|
|
360
|
+
size="sm"
|
|
361
|
+
className="h-8 text-xs"
|
|
362
|
+
onClick={(e) => {
|
|
363
|
+
e.stopPropagation();
|
|
364
|
+
onCompareWithPrevious(log);
|
|
365
|
+
}}
|
|
366
|
+
>
|
|
367
|
+
<GitCompareArrows className="size-3 mr-1" />
|
|
368
|
+
Diff with Previous
|
|
369
|
+
</Button>
|
|
370
|
+
</TooltipTrigger>
|
|
371
|
+
<TooltipContent>
|
|
372
|
+
Compare this request with the immediately preceding one
|
|
373
|
+
</TooltipContent>
|
|
374
|
+
</Tooltip>
|
|
375
|
+
)}
|
|
376
|
+
</div>
|
|
377
|
+
)}
|
|
373
378
|
{requestDiff ? (
|
|
374
379
|
<RequestDiffContent
|
|
375
380
|
rawBody={log.rawRequestBody}
|
|
376
381
|
displayedBody={displayedRequestBody}
|
|
377
382
|
emptyLabel="No transformation applied — raw and sent request bodies are identical."
|
|
378
383
|
/>
|
|
379
|
-
) : displayedRequestBody === null ? (
|
|
380
|
-
<p className="text-xs text-muted-foreground italic">No request body</p>
|
|
381
|
-
) : requestExpansion.parsedData !== null ? (
|
|
382
|
-
<JsonViewer
|
|
383
|
-
data={requestExpansion.parsedData}
|
|
384
|
-
bulkDepth={requestExpansion.bulkDepth}
|
|
385
|
-
bulkRevision={requestExpansion.bulkRevision}
|
|
386
|
-
/>
|
|
387
384
|
) : (
|
|
388
|
-
<
|
|
389
|
-
{displayedRequestBody
|
|
390
|
-
|
|
385
|
+
<div ref={requestJsonRef}>
|
|
386
|
+
{displayedRequestBody === null ? (
|
|
387
|
+
<p className="text-xs text-muted-foreground italic">No request body</p>
|
|
388
|
+
) : requestExpansion.parsedData !== null ? (
|
|
389
|
+
<Suspense fallback={<TabFallback />}>
|
|
390
|
+
<LazyJsonViewer
|
|
391
|
+
data={requestExpansion.parsedData}
|
|
392
|
+
bulkDepth={requestExpansion.bulkDepth}
|
|
393
|
+
bulkRevision={requestExpansion.bulkRevision}
|
|
394
|
+
anatomyPaths={anatomyPaths}
|
|
395
|
+
expandToPath={expandToPath}
|
|
396
|
+
/>
|
|
397
|
+
</Suspense>
|
|
398
|
+
) : (
|
|
399
|
+
<pre className="font-mono text-xs whitespace-pre-wrap break-words">
|
|
400
|
+
{displayedRequestBody}
|
|
401
|
+
</pre>
|
|
402
|
+
)}
|
|
403
|
+
</div>
|
|
391
404
|
)}
|
|
392
405
|
</div>
|
|
393
406
|
)}
|
|
394
407
|
</TabsContent>
|
|
395
408
|
|
|
409
|
+
{anatomySegments !== null && (
|
|
410
|
+
<TabsContent value="anatomy">
|
|
411
|
+
{activeTab === "anatomy" && (
|
|
412
|
+
<Suspense fallback={<TabFallback />}>
|
|
413
|
+
<LazyRequestAnatomy
|
|
414
|
+
parsed={null}
|
|
415
|
+
inputTokens={log.inputTokens ?? null}
|
|
416
|
+
segments={anatomySegments}
|
|
417
|
+
onSegmentActivate={jumpToAnatomySegment}
|
|
418
|
+
/>
|
|
419
|
+
</Suspense>
|
|
420
|
+
)}
|
|
421
|
+
</TabsContent>
|
|
422
|
+
)}
|
|
423
|
+
|
|
396
424
|
{viewMode === "full" && (
|
|
397
425
|
<TabsContent value="headers">
|
|
398
426
|
{activeTab === "headers" && (
|
|
399
|
-
<div className="px-4
|
|
427
|
+
<div className="px-4 pt-1 pb-3">
|
|
400
428
|
<div className="flex justify-end gap-2 mb-2">
|
|
401
429
|
{shouldShowHeadersDiffButton(
|
|
402
430
|
viewMode,
|
|
@@ -443,7 +471,7 @@ export const LogEntry = memo(function ({
|
|
|
443
471
|
{viewMode === "full" && (
|
|
444
472
|
<TabsContent value="raw-headers">
|
|
445
473
|
{activeTab === "raw-headers" && (
|
|
446
|
-
<div className="px-4
|
|
474
|
+
<div className="px-4 pt-1 pb-3">
|
|
447
475
|
{log.rawHeaders && Object.keys(log.rawHeaders).length > 0 ? (
|
|
448
476
|
<div className="space-y-1 font-mono text-xs">
|
|
449
477
|
{Object.entries(log.rawHeaders)
|
|
@@ -471,7 +499,7 @@ export const LogEntry = memo(function ({
|
|
|
471
499
|
|
|
472
500
|
<TabsContent value="raw">
|
|
473
501
|
{activeTab === "raw" && (
|
|
474
|
-
<div className="px-4
|
|
502
|
+
<div className="px-4 pt-1 pb-3 space-y-3">
|
|
475
503
|
{log.error !== undefined && log.error !== null && (
|
|
476
504
|
<div className="rounded border border-destructive/50 bg-destructive/10 p-3 text-xs">
|
|
477
505
|
<div className="font-semibold text-destructive mb-1">SSE Error</div>
|
|
@@ -487,15 +515,19 @@ export const LogEntry = memo(function ({
|
|
|
487
515
|
/>
|
|
488
516
|
</div>
|
|
489
517
|
{log.responseText !== null ? (
|
|
490
|
-
<
|
|
518
|
+
<Suspense fallback={<TabFallback />}>
|
|
519
|
+
<LazyJsonViewerFromString text={log.responseText} defaultExpandDepth={0} />
|
|
520
|
+
</Suspense>
|
|
491
521
|
) : (
|
|
492
522
|
<p className="text-xs text-muted-foreground italic">No response</p>
|
|
493
523
|
)}
|
|
494
524
|
{log.streaming === true && (
|
|
495
|
-
<
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
525
|
+
<Suspense fallback={<TabFallback />}>
|
|
526
|
+
<LazyStreamingChunkSequence
|
|
527
|
+
logId={log.id}
|
|
528
|
+
truncated={log.streamingChunksPath !== null}
|
|
529
|
+
/>
|
|
530
|
+
</Suspense>
|
|
499
531
|
)}
|
|
500
532
|
</div>
|
|
501
533
|
)}
|
|
@@ -503,18 +535,20 @@ export const LogEntry = memo(function ({
|
|
|
503
535
|
|
|
504
536
|
<TabsContent value="parsed">
|
|
505
537
|
{activeTab === "parsed" && (
|
|
506
|
-
<div className="px-4
|
|
507
|
-
<
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
538
|
+
<div className="px-4 pt-1 pb-3">
|
|
539
|
+
<Suspense fallback={<TabFallback />}>
|
|
540
|
+
<LazyResponseView
|
|
541
|
+
responseText={log.responseText}
|
|
542
|
+
responseStatus={log.responseStatus}
|
|
543
|
+
streaming={log.streaming}
|
|
544
|
+
inputTokens={log.inputTokens}
|
|
545
|
+
outputTokens={log.outputTokens}
|
|
546
|
+
cacheCreationInputTokens={log.cacheCreationInputTokens}
|
|
547
|
+
cacheReadInputTokens={log.cacheReadInputTokens}
|
|
548
|
+
apiFormat={resolvedFormat}
|
|
549
|
+
error={log.error}
|
|
550
|
+
/>
|
|
551
|
+
</Suspense>
|
|
518
552
|
</div>
|
|
519
553
|
)}
|
|
520
554
|
</TabsContent>
|
|
@@ -522,7 +556,9 @@ export const LogEntry = memo(function ({
|
|
|
522
556
|
</div>
|
|
523
557
|
)}
|
|
524
558
|
</div>
|
|
525
|
-
<
|
|
559
|
+
<Suspense fallback={null}>
|
|
560
|
+
<LazyReplayDialog log={log} open={replayOpen} onOpenChange={setReplayOpen} />
|
|
561
|
+
</Suspense>
|
|
526
562
|
</TooltipProvider>
|
|
527
563
|
);
|
|
528
564
|
});
|