@tonyclaw/llm-inspector 1.14.6 → 1.14.8
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/index-CdnotuLh.js +105 -0
- package/.output/public/assets/index-vP91146S.css +1 -0
- package/.output/public/assets/{main-Dp5657Eq.js → main-CJ4MreBr.js} +1 -1
- package/.output/server/_libs/lucide-react.mjs +87 -79
- package/.output/server/_libs/radix-ui__react-id.mjs +1 -1
- package/.output/server/_libs/radix-ui__react-tooltip.mjs +1 -1
- package/.output/server/_ssr/{index-D2yS8VvO.mjs → index-9uTJ4xYR.mjs} +813 -595
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-DCfjmmJu.mjs → router-BKnjB_zi.mjs} +2 -2
- package/.output/server/{_tanstack-start-manifest_v-DupqJc5d.mjs → _tanstack-start-manifest_v-IsglLVKy.mjs} +1 -1
- package/.output/server/index.mjs +26 -26
- package/package.json +1 -1
- package/src/components/ProxyViewer.tsx +114 -146
- package/src/components/providers/ImportWizardDialog.tsx +6 -0
- package/src/components/providers/ProviderCard.tsx +79 -26
- package/src/components/providers/ProviderForm.tsx +37 -22
- package/src/components/providers/ProvidersPanel.tsx +118 -58
- package/src/components/providers/SettingsDialog.tsx +25 -15
- package/src/components/proxy-viewer/ConversationGroup.tsx +50 -10
- package/src/components/proxy-viewer/ConversationHeader.tsx +48 -2
- package/src/components/proxy-viewer/LogEntry.tsx +116 -45
- package/src/components/proxy-viewer/LogEntryHeader.tsx +89 -71
- package/src/components/proxy-viewer/ReplayDialog.tsx +16 -6
- package/src/components/proxy-viewer/StreamingChunkSequence.tsx +24 -16
- package/src/components/proxy-viewer/ThreadConnector.tsx +104 -0
- package/src/components/proxy-viewer/index.ts +2 -1
- package/src/components/ui/confirm-dialog.tsx +51 -0
- package/src/lib/stopReason.ts +57 -0
- package/.output/public/assets/index-BFNoWwFI.css +0 -1
- package/.output/public/assets/index-LH-YtFEM.js +0 -105
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ChevronDown,
|
|
3
|
+
ChevronRight,
|
|
4
|
+
Clock,
|
|
5
|
+
GitBranch,
|
|
6
|
+
Loader2,
|
|
7
|
+
MessageSquare,
|
|
8
|
+
Zap,
|
|
9
|
+
} from "lucide-react";
|
|
2
10
|
import type { JSX } from "react";
|
|
3
11
|
import { cn, formatTokens } from "../../lib/utils";
|
|
4
12
|
import type { CapturedLog } from "../../proxy/schemas";
|
|
@@ -10,6 +18,8 @@ const API_FORMAT_LABELS: Record<"anthropic" | "openai" | "unknown", string> = {
|
|
|
10
18
|
unknown: "Unknown",
|
|
11
19
|
};
|
|
12
20
|
|
|
21
|
+
export type ViewMode = "flat" | "thread";
|
|
22
|
+
|
|
13
23
|
export type ConversationHeaderProps = {
|
|
14
24
|
conversationId: string;
|
|
15
25
|
startTime: string;
|
|
@@ -23,6 +33,13 @@ export type ConversationHeaderProps = {
|
|
|
23
33
|
/** Hide the API format badge on the header (used when the group contains
|
|
24
34
|
* mixed formats — the per-log badges are shown instead). */
|
|
25
35
|
hideApiFormat?: boolean;
|
|
36
|
+
/** When true and the group is collapsed, show a spinner instead of the
|
|
37
|
+
* expand chevron to indicate an in-flight request inside the group. */
|
|
38
|
+
isLoading?: boolean;
|
|
39
|
+
/** Current display mode for this group (flat cards or threaded timeline). */
|
|
40
|
+
viewMode?: ViewMode;
|
|
41
|
+
/** Toggle between flat and thread display modes for this group. */
|
|
42
|
+
onToggleViewMode?: () => void;
|
|
26
43
|
};
|
|
27
44
|
|
|
28
45
|
function formatTimestamp(iso: string): string {
|
|
@@ -41,6 +58,9 @@ export function ConversationHeader({
|
|
|
41
58
|
expanded,
|
|
42
59
|
onToggle,
|
|
43
60
|
hideApiFormat = false,
|
|
61
|
+
isLoading = false,
|
|
62
|
+
viewMode,
|
|
63
|
+
onToggleViewMode,
|
|
44
64
|
}: ConversationHeaderProps): JSX.Element {
|
|
45
65
|
return (
|
|
46
66
|
<div
|
|
@@ -60,13 +80,39 @@ export function ConversationHeader({
|
|
|
60
80
|
}
|
|
61
81
|
}}
|
|
62
82
|
>
|
|
63
|
-
{/* Expand chevron */}
|
|
83
|
+
{/* Expand chevron — shows spinner when collapsed and group has pending logs */}
|
|
64
84
|
{expanded ? (
|
|
65
85
|
<ChevronDown className="size-4 text-muted-foreground shrink-0" />
|
|
86
|
+
) : isLoading ? (
|
|
87
|
+
<Loader2 className="size-4 animate-spin text-muted-foreground shrink-0" />
|
|
66
88
|
) : (
|
|
67
89
|
<ChevronRight className="size-4 text-muted-foreground shrink-0" />
|
|
68
90
|
)}
|
|
69
91
|
|
|
92
|
+
{/* Thread/flat view toggle — only shown when expanded */}
|
|
93
|
+
{expanded && onToggleViewMode !== undefined && (
|
|
94
|
+
<button
|
|
95
|
+
type="button"
|
|
96
|
+
onClick={(e) => {
|
|
97
|
+
e.stopPropagation();
|
|
98
|
+
onToggleViewMode();
|
|
99
|
+
}}
|
|
100
|
+
className={cn(
|
|
101
|
+
"px-1.5 py-0.5 rounded text-[10px] font-mono transition-colors shrink-0 cursor-pointer",
|
|
102
|
+
viewMode === "thread"
|
|
103
|
+
? "bg-amber-500/15 text-amber-400 border border-amber-500/30"
|
|
104
|
+
: "bg-muted text-muted-foreground border border-border hover:text-foreground",
|
|
105
|
+
)}
|
|
106
|
+
title={
|
|
107
|
+
viewMode === "thread"
|
|
108
|
+
? "Thread view — click for flat view"
|
|
109
|
+
: "Flat view — click for thread view"
|
|
110
|
+
}
|
|
111
|
+
>
|
|
112
|
+
<GitBranch className="size-3" />
|
|
113
|
+
</button>
|
|
114
|
+
)}
|
|
115
|
+
|
|
70
116
|
{/* Conversation ID */}
|
|
71
117
|
<span
|
|
72
118
|
className="text-purple-400/90 font-mono text-xs font-semibold shrink-0"
|
|
@@ -5,6 +5,7 @@ import { cn } from "../../lib/utils";
|
|
|
5
5
|
import { type CapturedLog, parseRequest } from "../../proxy/schemas";
|
|
6
6
|
import { stripClaudeCodeBillingHeader } from "../../proxy/claudeCodeStrip";
|
|
7
7
|
import { Button } from "../ui/button";
|
|
8
|
+
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "../ui/tooltip";
|
|
8
9
|
import { JsonViewerFromString } from "../ui/json-viewer";
|
|
9
10
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
|
|
10
11
|
import { computeHeadersDiff, computeRequestDiff, DiffView } from "./diff";
|
|
@@ -31,10 +32,8 @@ export type LogEntryProps = {
|
|
|
31
32
|
* `null` (or absent) means the header should render with no arrows.
|
|
32
33
|
*/
|
|
33
34
|
cacheTrend?: CacheTrendEntry | null;
|
|
34
|
-
/**
|
|
35
|
-
|
|
36
|
-
/** Toggle this log in/out of the comparison selection. */
|
|
37
|
-
onToggleSelect?: (logId: number) => void;
|
|
35
|
+
/** Callback to open CompareDrawer with this log and its immediate predecessor. */
|
|
36
|
+
onCompareWithPrevious?: () => void;
|
|
38
37
|
};
|
|
39
38
|
|
|
40
39
|
/**
|
|
@@ -118,21 +117,29 @@ function DiffToggleButton({
|
|
|
118
117
|
onClick: (e: React.MouseEvent) => void;
|
|
119
118
|
}): JSX.Element {
|
|
120
119
|
return (
|
|
121
|
-
<
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
120
|
+
<TooltipProvider>
|
|
121
|
+
<Tooltip>
|
|
122
|
+
<TooltipTrigger asChild>
|
|
123
|
+
<button
|
|
124
|
+
type="button"
|
|
125
|
+
onClick={onClick}
|
|
126
|
+
aria-pressed={active}
|
|
127
|
+
className={cn(
|
|
128
|
+
"flex items-center gap-1.5 text-xs px-2 py-1 rounded transition-colors",
|
|
129
|
+
active
|
|
130
|
+
? "bg-primary/10 text-primary"
|
|
131
|
+
: "text-muted-foreground hover:text-foreground hover:bg-muted",
|
|
132
|
+
)}
|
|
133
|
+
>
|
|
134
|
+
<GitCompareArrows className="size-3" />
|
|
135
|
+
{active ? "Showing diff" : "Diff with Raw"}
|
|
136
|
+
</button>
|
|
137
|
+
</TooltipTrigger>
|
|
138
|
+
<TooltipContent>
|
|
139
|
+
{active ? "Hide diff view" : "Compare proxy output against the original raw version"}
|
|
140
|
+
</TooltipContent>
|
|
141
|
+
</Tooltip>
|
|
142
|
+
</TooltipProvider>
|
|
136
143
|
);
|
|
137
144
|
}
|
|
138
145
|
|
|
@@ -142,8 +149,7 @@ export const LogEntry = memo(function ({
|
|
|
142
149
|
suppressApiFormatBadge = false,
|
|
143
150
|
strip,
|
|
144
151
|
cacheTrend = null,
|
|
145
|
-
|
|
146
|
-
onToggleSelect,
|
|
152
|
+
onCompareWithPrevious,
|
|
147
153
|
}: LogEntryProps): JSX.Element {
|
|
148
154
|
const [expanded, setExpanded] = useState<boolean>(false);
|
|
149
155
|
const [requestCopied, setRequestCopied] = useState<boolean>(false);
|
|
@@ -199,12 +205,7 @@ export const LogEntry = memo(function ({
|
|
|
199
205
|
|
|
200
206
|
return (
|
|
201
207
|
<>
|
|
202
|
-
<div
|
|
203
|
-
className={cn(
|
|
204
|
-
"border border-border rounded-lg mb-3 overflow-hidden",
|
|
205
|
-
isSelected && "border-l-2 border-l-amber-400",
|
|
206
|
-
)}
|
|
207
|
-
>
|
|
208
|
+
<div className="border border-border rounded-lg mb-3 overflow-hidden">
|
|
208
209
|
<LogEntryHeader
|
|
209
210
|
log={log}
|
|
210
211
|
parsedRequest={parsedRequest}
|
|
@@ -212,21 +213,61 @@ export const LogEntry = memo(function ({
|
|
|
212
213
|
onToggle={() => setExpanded(!expanded)}
|
|
213
214
|
suppressApiFormatBadge={suppressApiFormatBadge}
|
|
214
215
|
cacheTrend={cacheTrend}
|
|
215
|
-
isSelected={isSelected}
|
|
216
|
-
onToggleSelect={onToggleSelect}
|
|
217
216
|
/>
|
|
218
217
|
|
|
219
218
|
{expanded && (
|
|
220
219
|
<div onClick={(e) => e.stopPropagation()} onKeyDown={(e) => e.stopPropagation()}>
|
|
221
220
|
<Tabs defaultValue="request">
|
|
222
221
|
<TabsList className="mx-4 mt-2">
|
|
223
|
-
{viewMode === "full" &&
|
|
224
|
-
|
|
222
|
+
{viewMode === "full" && (
|
|
223
|
+
<TooltipProvider>
|
|
224
|
+
<Tooltip>
|
|
225
|
+
<TooltipTrigger asChild>
|
|
226
|
+
<TabsTrigger value="raw-headers">Raw Headers</TabsTrigger>
|
|
227
|
+
</TooltipTrigger>
|
|
228
|
+
<TooltipContent>
|
|
229
|
+
HTTP headers received from the upstream provider
|
|
230
|
+
</TooltipContent>
|
|
231
|
+
</Tooltip>
|
|
232
|
+
</TooltipProvider>
|
|
233
|
+
)}
|
|
234
|
+
{viewMode === "full" && (
|
|
235
|
+
<TooltipProvider>
|
|
236
|
+
<Tooltip>
|
|
237
|
+
<TooltipTrigger asChild>
|
|
238
|
+
<TabsTrigger value="headers">Headers</TabsTrigger>
|
|
239
|
+
</TooltipTrigger>
|
|
240
|
+
<TooltipContent>
|
|
241
|
+
Request and response headers sent and received
|
|
242
|
+
</TooltipContent>
|
|
243
|
+
</Tooltip>
|
|
244
|
+
</TooltipProvider>
|
|
245
|
+
)}
|
|
225
246
|
{shouldShowRawRequestTab(log.apiFormat, viewMode, strip) && (
|
|
226
|
-
<
|
|
247
|
+
<TooltipProvider>
|
|
248
|
+
<Tooltip>
|
|
249
|
+
<TooltipTrigger asChild>
|
|
250
|
+
<TabsTrigger value="raw-request">Raw Request</TabsTrigger>
|
|
251
|
+
</TooltipTrigger>
|
|
252
|
+
<TooltipContent>
|
|
253
|
+
Exact HTTP request sent to the upstream provider
|
|
254
|
+
</TooltipContent>
|
|
255
|
+
</Tooltip>
|
|
256
|
+
</TooltipProvider>
|
|
227
257
|
)}
|
|
228
258
|
<TabsTrigger value="request">Request</TabsTrigger>
|
|
229
|
-
{viewMode === "full" &&
|
|
259
|
+
{viewMode === "full" && (
|
|
260
|
+
<TooltipProvider>
|
|
261
|
+
<Tooltip>
|
|
262
|
+
<TooltipTrigger asChild>
|
|
263
|
+
<TabsTrigger value="raw">Raw Response</TabsTrigger>
|
|
264
|
+
</TooltipTrigger>
|
|
265
|
+
<TooltipContent>
|
|
266
|
+
Exact HTTP response from the upstream provider
|
|
267
|
+
</TooltipContent>
|
|
268
|
+
</Tooltip>
|
|
269
|
+
</TooltipProvider>
|
|
270
|
+
)}
|
|
230
271
|
<TabsTrigger value="parsed">Response</TabsTrigger>
|
|
231
272
|
</TabsList>
|
|
232
273
|
|
|
@@ -267,18 +308,48 @@ export const LogEntry = memo(function ({
|
|
|
267
308
|
}}
|
|
268
309
|
/>
|
|
269
310
|
)}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
311
|
+
{onCompareWithPrevious !== undefined && (
|
|
312
|
+
<TooltipProvider>
|
|
313
|
+
<Tooltip>
|
|
314
|
+
<TooltipTrigger asChild>
|
|
315
|
+
<Button
|
|
316
|
+
variant="outline"
|
|
317
|
+
size="sm"
|
|
318
|
+
className="h-7 text-xs"
|
|
319
|
+
onClick={(e) => {
|
|
320
|
+
e.stopPropagation();
|
|
321
|
+
onCompareWithPrevious();
|
|
322
|
+
}}
|
|
323
|
+
>
|
|
324
|
+
<GitCompareArrows className="size-3 mr-1" />
|
|
325
|
+
Diff with Previous
|
|
326
|
+
</Button>
|
|
327
|
+
</TooltipTrigger>
|
|
328
|
+
<TooltipContent>
|
|
329
|
+
Compare this request with the immediately preceding one
|
|
330
|
+
</TooltipContent>
|
|
331
|
+
</Tooltip>
|
|
332
|
+
</TooltipProvider>
|
|
333
|
+
)}
|
|
334
|
+
<TooltipProvider>
|
|
335
|
+
<Tooltip>
|
|
336
|
+
<TooltipTrigger asChild>
|
|
337
|
+
<Button
|
|
338
|
+
variant="outline"
|
|
339
|
+
size="sm"
|
|
340
|
+
className="h-7 text-xs"
|
|
341
|
+
onClick={(e) => {
|
|
342
|
+
e.stopPropagation();
|
|
343
|
+
setReplayOpen(true);
|
|
344
|
+
}}
|
|
345
|
+
>
|
|
346
|
+
<RotateCcw className="size-3 mr-1" />
|
|
347
|
+
Replay
|
|
348
|
+
</Button>
|
|
349
|
+
</TooltipTrigger>
|
|
350
|
+
<TooltipContent>Re-send this request to the provider</TooltipContent>
|
|
351
|
+
</Tooltip>
|
|
352
|
+
</TooltipProvider>
|
|
282
353
|
<CopyButton
|
|
283
354
|
text={displayedRequestBody}
|
|
284
355
|
label="Copy Request"
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ArrowDown,
|
|
3
3
|
ArrowUp,
|
|
4
|
-
Check,
|
|
5
4
|
ChevronDown,
|
|
6
5
|
ChevronRight,
|
|
7
6
|
Clock,
|
|
@@ -20,6 +19,7 @@ import { cn, formatTokens, getStatusCategory, type StatusCategory } from "../../
|
|
|
20
19
|
import type { CapturedLog, InspectorRequest } from "../../proxy/schemas";
|
|
21
20
|
import { Badge } from "../ui/badge";
|
|
22
21
|
import { ProviderLogo, detectProvider } from "../providers/ProviderLogo";
|
|
22
|
+
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "../ui/tooltip";
|
|
23
23
|
import type { CacheTrend } from "./cacheTrend";
|
|
24
24
|
|
|
25
25
|
function formatElapsed(ms: number): string {
|
|
@@ -67,10 +67,6 @@ export type LogEntryHeaderProps = {
|
|
|
67
67
|
* the corresponding cache span renders as it did before — no arrow.
|
|
68
68
|
*/
|
|
69
69
|
cacheTrend?: { creation: CacheTrend | null; read: CacheTrend | null } | null;
|
|
70
|
-
/** Whether this log is currently marked for comparison. */
|
|
71
|
-
isSelected?: boolean;
|
|
72
|
-
/** Toggle this log in/out of the comparison selection. */
|
|
73
|
-
onToggleSelect?: (logId: number) => void;
|
|
74
70
|
};
|
|
75
71
|
|
|
76
72
|
export const LogEntryHeader = memo(function ({
|
|
@@ -80,8 +76,6 @@ export const LogEntryHeader = memo(function ({
|
|
|
80
76
|
onToggle,
|
|
81
77
|
suppressApiFormatBadge = false,
|
|
82
78
|
cacheTrend = null,
|
|
83
|
-
isSelected = false,
|
|
84
|
-
onToggleSelect,
|
|
85
79
|
}: LogEntryHeaderProps): JSX.Element {
|
|
86
80
|
const statusCategory = getStatusCategory(log.responseStatus);
|
|
87
81
|
|
|
@@ -111,40 +105,23 @@ export const LogEntryHeader = memo(function ({
|
|
|
111
105
|
}
|
|
112
106
|
}}
|
|
113
107
|
>
|
|
114
|
-
{/* Selection checkbox (for log-request comparison) */}
|
|
115
|
-
{onToggleSelect !== undefined && (
|
|
116
|
-
<button
|
|
117
|
-
type="button"
|
|
118
|
-
onClick={(e) => {
|
|
119
|
-
e.stopPropagation();
|
|
120
|
-
onToggleSelect(log.id);
|
|
121
|
-
}}
|
|
122
|
-
aria-label={isSelected ? "Deselect for comparison" : "Select for comparison"}
|
|
123
|
-
aria-pressed={isSelected}
|
|
124
|
-
className={cn(
|
|
125
|
-
"shrink-0 size-4 rounded-sm border flex items-center justify-center transition-colors cursor-pointer",
|
|
126
|
-
isSelected
|
|
127
|
-
? "bg-amber-400 border-amber-400 text-amber-950"
|
|
128
|
-
: "border-muted-foreground/40 hover:border-amber-400 hover:bg-amber-400/10",
|
|
129
|
-
)}
|
|
130
|
-
>
|
|
131
|
-
{isSelected && <Check className="size-3" strokeWidth={3} />}
|
|
132
|
-
</button>
|
|
133
|
-
)}
|
|
134
|
-
|
|
135
108
|
{/* Request ID */}
|
|
136
109
|
<span className="text-blue-400/80 font-mono text-xs font-semibold tabular-nums shrink-0">
|
|
137
110
|
#{log.id}
|
|
138
111
|
</span>
|
|
139
112
|
|
|
140
|
-
{/* Model */}
|
|
113
|
+
{/* Model — logo icon only, model name in tooltip */}
|
|
141
114
|
{log.model !== null && (
|
|
142
|
-
|
|
143
|
-
<
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
115
|
+
<TooltipProvider>
|
|
116
|
+
<Tooltip>
|
|
117
|
+
<TooltipTrigger asChild>
|
|
118
|
+
<span className="shrink-0">
|
|
119
|
+
<ProviderLogo provider={detectProvider(log.model)} className="size-4" />
|
|
120
|
+
</span>
|
|
121
|
+
</TooltipTrigger>
|
|
122
|
+
<TooltipContent>{log.model}</TooltipContent>
|
|
123
|
+
</Tooltip>
|
|
124
|
+
</TooltipProvider>
|
|
148
125
|
)}
|
|
149
126
|
|
|
150
127
|
{/* API Format Badge */}
|
|
@@ -220,36 +197,64 @@ export const LogEntryHeader = memo(function ({
|
|
|
220
197
|
)}
|
|
221
198
|
{/* Cache tokens */}
|
|
222
199
|
{log.cacheCreationInputTokens !== null && log.cacheCreationInputTokens > 0 && (
|
|
223
|
-
<
|
|
224
|
-
<
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
200
|
+
<TooltipProvider>
|
|
201
|
+
<Tooltip>
|
|
202
|
+
<TooltipTrigger asChild>
|
|
203
|
+
<span className="flex items-center gap-1 text-xs shrink-0">
|
|
204
|
+
<CacheTrendIndicator trend={cacheTrend?.creation ?? null} />
|
|
205
|
+
<span className="font-mono tabular-nums text-emerald-400">
|
|
206
|
+
Cache +{formatTokens(log.cacheCreationInputTokens)}
|
|
207
|
+
</span>
|
|
208
|
+
</span>
|
|
209
|
+
</TooltipTrigger>
|
|
210
|
+
<TooltipContent>Tokens cached for reuse, reducing future API cost</TooltipContent>
|
|
211
|
+
</Tooltip>
|
|
212
|
+
</TooltipProvider>
|
|
229
213
|
)}
|
|
230
214
|
{log.cacheReadInputTokens !== null && log.cacheReadInputTokens > 0 && (
|
|
231
|
-
<
|
|
232
|
-
<
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
215
|
+
<TooltipProvider>
|
|
216
|
+
<Tooltip>
|
|
217
|
+
<TooltipTrigger asChild>
|
|
218
|
+
<span className="flex items-center gap-1 text-xs shrink-0">
|
|
219
|
+
<CacheTrendIndicator trend={cacheTrend?.read ?? null} />
|
|
220
|
+
<span className="font-mono tabular-nums text-purple-400">
|
|
221
|
+
Cache ~{formatTokens(log.cacheReadInputTokens)}
|
|
222
|
+
</span>
|
|
223
|
+
</span>
|
|
224
|
+
</TooltipTrigger>
|
|
225
|
+
<TooltipContent>Tokens served from cache, reducing API cost</TooltipContent>
|
|
226
|
+
</Tooltip>
|
|
227
|
+
</TooltipProvider>
|
|
237
228
|
)}
|
|
238
229
|
|
|
239
230
|
{/* Message count */}
|
|
240
231
|
{messageCount !== null && (
|
|
241
|
-
<
|
|
242
|
-
<
|
|
243
|
-
|
|
244
|
-
|
|
232
|
+
<TooltipProvider>
|
|
233
|
+
<Tooltip>
|
|
234
|
+
<TooltipTrigger asChild>
|
|
235
|
+
<span className="flex items-center gap-1 text-muted-foreground text-xs shrink-0">
|
|
236
|
+
<MessageSquare className="size-3" />
|
|
237
|
+
<span className="font-mono tabular-nums">{messageCount}</span>
|
|
238
|
+
</span>
|
|
239
|
+
</TooltipTrigger>
|
|
240
|
+
<TooltipContent>Number of messages in the conversation</TooltipContent>
|
|
241
|
+
</Tooltip>
|
|
242
|
+
</TooltipProvider>
|
|
245
243
|
)}
|
|
246
244
|
|
|
247
245
|
{/* Tool count */}
|
|
248
246
|
{toolCount !== null && (
|
|
249
|
-
<
|
|
250
|
-
<
|
|
251
|
-
|
|
252
|
-
|
|
247
|
+
<TooltipProvider>
|
|
248
|
+
<Tooltip>
|
|
249
|
+
<TooltipTrigger asChild>
|
|
250
|
+
<span className="flex items-center gap-1 text-muted-foreground text-xs shrink-0">
|
|
251
|
+
<Wrench className="size-3" />
|
|
252
|
+
<span className="font-mono tabular-nums">{toolCount}</span>
|
|
253
|
+
</span>
|
|
254
|
+
</TooltipTrigger>
|
|
255
|
+
<TooltipContent>Number of tools defined in the request</TooltipContent>
|
|
256
|
+
</Tooltip>
|
|
257
|
+
</TooltipProvider>
|
|
253
258
|
)}
|
|
254
259
|
|
|
255
260
|
{/* Origin */}
|
|
@@ -280,25 +285,38 @@ export const LogEntryHeader = memo(function ({
|
|
|
280
285
|
|
|
281
286
|
{/* Client info (PID + project folder) */}
|
|
282
287
|
{(log.clientPid !== null || log.clientProjectFolder !== null) && (
|
|
283
|
-
<
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
<
|
|
296
|
-
|
|
297
|
-
|
|
288
|
+
<TooltipProvider>
|
|
289
|
+
<Tooltip>
|
|
290
|
+
<TooltipTrigger asChild>
|
|
291
|
+
<span className="flex items-center gap-1 text-purple-400/80 text-xs shrink-0">
|
|
292
|
+
<FileTerminal className="size-3" />
|
|
293
|
+
{log.clientProjectFolder !== null ? (
|
|
294
|
+
<span className="font-mono tabular-nums">{log.clientProjectFolder}</span>
|
|
295
|
+
) : (
|
|
296
|
+
<span className="font-mono tabular-nums">PID {log.clientPid}</span>
|
|
297
|
+
)}
|
|
298
|
+
</span>
|
|
299
|
+
</TooltipTrigger>
|
|
300
|
+
<TooltipContent>
|
|
301
|
+
{log.clientCwd !== null
|
|
302
|
+
? `PID: ${log.clientPid ?? "?"} CWD: ${log.clientCwd}`
|
|
303
|
+
: `Process ID: ${log.clientPid ?? "?"}`}
|
|
304
|
+
</TooltipContent>
|
|
305
|
+
</Tooltip>
|
|
306
|
+
</TooltipProvider>
|
|
298
307
|
)}
|
|
299
308
|
|
|
300
309
|
{/* Streaming indicator */}
|
|
301
|
-
{log.streaming &&
|
|
310
|
+
{log.streaming && (
|
|
311
|
+
<TooltipProvider>
|
|
312
|
+
<Tooltip>
|
|
313
|
+
<TooltipTrigger asChild>
|
|
314
|
+
<Radio className="size-3 text-muted-foreground/60 shrink-0" />
|
|
315
|
+
</TooltipTrigger>
|
|
316
|
+
<TooltipContent>Request used SSE streaming</TooltipContent>
|
|
317
|
+
</Tooltip>
|
|
318
|
+
</TooltipProvider>
|
|
319
|
+
)}
|
|
302
320
|
|
|
303
321
|
{/* Spacer */}
|
|
304
322
|
<span className="flex-1 min-w-0" />
|
|
@@ -3,6 +3,7 @@ import type { JSX } from "react";
|
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { Button } from "../ui/button";
|
|
6
|
+
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "../ui/tooltip";
|
|
6
7
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog";
|
|
7
8
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
|
|
8
9
|
import { ResponseView } from "./ResponseView";
|
|
@@ -92,12 +93,21 @@ export function ReplayDialog({ log, open, onOpenChange }: ReplayDialogProps): JS
|
|
|
92
93
|
<TabsContent value="modified" className="space-y-4">
|
|
93
94
|
<div>
|
|
94
95
|
<label className="text-sm font-medium mb-2 block">Request Body (JSON)</label>
|
|
95
|
-
<
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
96
|
+
<TooltipProvider>
|
|
97
|
+
<Tooltip>
|
|
98
|
+
<TooltipTrigger asChild>
|
|
99
|
+
<textarea
|
|
100
|
+
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"
|
|
101
|
+
value={modifiedBody}
|
|
102
|
+
onChange={(e) => setModifiedBody(e.target.value)}
|
|
103
|
+
spellCheck={false}
|
|
104
|
+
/>
|
|
105
|
+
</TooltipTrigger>
|
|
106
|
+
<TooltipContent>
|
|
107
|
+
Edit the request body before re-sending to the provider
|
|
108
|
+
</TooltipContent>
|
|
109
|
+
</Tooltip>
|
|
110
|
+
</TooltipProvider>
|
|
101
111
|
</div>
|
|
102
112
|
|
|
103
113
|
{error !== null && error !== "" && (
|
|
@@ -2,6 +2,7 @@ import { useState, useEffect, useMemo, type JSX } from "react";
|
|
|
2
2
|
import { ChevronDown, ChevronRight, Loader2 } from "lucide-react";
|
|
3
3
|
import { Badge } from "../ui/badge";
|
|
4
4
|
import { JsonViewer } from "../ui/json-viewer";
|
|
5
|
+
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "../ui/tooltip";
|
|
5
6
|
import type { StreamingChunk } from "../../proxy/schemas";
|
|
6
7
|
|
|
7
8
|
export type StreamingChunkSequenceProps = {
|
|
@@ -153,22 +154,29 @@ export function StreamingChunkSequence({
|
|
|
153
154
|
|
|
154
155
|
return (
|
|
155
156
|
<div className="space-y-1">
|
|
156
|
-
<
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
157
|
+
<TooltipProvider>
|
|
158
|
+
<Tooltip>
|
|
159
|
+
<TooltipTrigger asChild>
|
|
160
|
+
<button
|
|
161
|
+
type="button"
|
|
162
|
+
className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer"
|
|
163
|
+
onClick={() => setContainerExpanded((v) => !v)}
|
|
164
|
+
>
|
|
165
|
+
{containerExpanded ? (
|
|
166
|
+
<ChevronDown className="size-3" />
|
|
167
|
+
) : (
|
|
168
|
+
<ChevronRight className="size-3" />
|
|
169
|
+
)}
|
|
170
|
+
<span>Raw SSE Events</span>
|
|
171
|
+
<Badge variant="outline" className="text-[9px] px-1 py-0 h-4 font-mono ml-1">
|
|
172
|
+
{logId}
|
|
173
|
+
{truncated === true ? "+" : ""}
|
|
174
|
+
</Badge>
|
|
175
|
+
</button>
|
|
176
|
+
</TooltipTrigger>
|
|
177
|
+
<TooltipContent>Server-Sent Events streaming chunks from the provider</TooltipContent>
|
|
178
|
+
</Tooltip>
|
|
179
|
+
</TooltipProvider>
|
|
172
180
|
|
|
173
181
|
{containerExpanded === true ? (
|
|
174
182
|
<div className="rounded-md border border-border bg-muted/20 overflow-auto max-h-64">
|