@tonyclaw/agent-inspector 2.1.14 → 2.1.16
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-Ccrrcx1j.js +1 -0
- package/.output/public/assets/ProxyViewerContainer-DLqClc9A.js +106 -0
- package/.output/public/assets/ReplayDialog-C7axhr-l.js +1 -0
- package/.output/public/assets/{RequestAnatomy-BNahe83D.js → RequestAnatomy-WOptg8j_.js} +1 -1
- package/.output/public/assets/{ResponseView-DSOnGqi6.js → ResponseView-CSAcxh_M.js} +2 -2
- package/.output/public/assets/{StreamingChunkSequence-BEKTDklB.js → StreamingChunkSequence-DJS5KhPx.js} +1 -1
- package/.output/public/assets/_sessionId-DUSalzKH.js +1 -0
- package/.output/public/assets/index-BPpA21dY.css +1 -0
- package/.output/public/assets/index-Bt0Az2I2.js +1 -0
- package/.output/public/assets/{index-DWOkqdCa.js → index-DMPNh46t.js} +1 -1
- package/.output/public/assets/{json-viewer-C2JpgcW0.js → json-viewer-inX5QSa3.js} +1 -1
- package/.output/public/assets/{main-CSONBwwn.js → main-aCAKYGSD.js} +2 -2
- package/.output/server/_libs/lucide-react.mjs +224 -194
- package/.output/server/{_sessionId-CPAa37n5.mjs → _sessionId-C0fhvQnn.mjs} +2 -2
- package/.output/server/_ssr/{CompareDrawer-ceW5VxMo.mjs → CompareDrawer-CO-Ti5sg.mjs} +36 -15
- package/.output/server/_ssr/{ProxyViewerContainer-CDfEE_w-.mjs → ProxyViewerContainer-N4J7uBCX.mjs} +1153 -494
- package/.output/server/_ssr/{ReplayDialog-V0s_eEbR.mjs → ReplayDialog-J1VxC0In.mjs} +14 -7
- package/.output/server/_ssr/{RequestAnatomy-f1ccwR9d.mjs → RequestAnatomy-DBH-F3rq.mjs} +3 -3
- package/.output/server/_ssr/{ResponseView-BIRrqG4H.mjs → ResponseView-DLFwV7Hd.mjs} +3 -3
- package/.output/server/_ssr/{StreamingChunkSequence-V3JFjCgX.mjs → StreamingChunkSequence-BywmuND0.mjs} +3 -3
- package/.output/server/_ssr/{index-DsykulzS.mjs → index-CStymIc0.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-Dcnm0Ivf.mjs → json-viewer-DlQu0N1x.mjs} +3 -3
- package/.output/server/_ssr/{router-4bdm6Mt2.mjs → router-CQ4Zxhmr.mjs} +118 -47
- package/.output/server/{_tanstack-start-manifest_v-Xp4CO64V.mjs → _tanstack-start-manifest_v-BHeJBGfo.mjs} +1 -1
- package/.output/server/index.mjs +75 -75
- package/package.json +1 -1
- package/src/components/ProxyViewer.tsx +174 -107
- package/src/components/ProxyViewerContainer.tsx +77 -67
- package/src/components/alerts/AlertsDialog.tsx +2 -1
- package/src/components/clients/ClientLogo.tsx +132 -0
- package/src/components/groups/GroupsDialog.tsx +37 -14
- package/src/components/providers/ProviderCard.tsx +1 -1
- package/src/components/providers/ProviderForm.tsx +18 -18
- package/src/components/providers/ProviderLogo.tsx +6 -1
- package/src/components/providers/ProvidersPanel.tsx +6 -6
- package/src/components/providers/SettingsDialog.tsx +13 -6
- package/src/components/proxy-viewer/AgentTraceSummary.tsx +62 -29
- package/src/components/proxy-viewer/CompareDrawer.tsx +38 -8
- package/src/components/proxy-viewer/ConversationGroup.tsx +43 -32
- package/src/components/proxy-viewer/ConversationGroupList.tsx +452 -89
- package/src/components/proxy-viewer/ConversationHeader.tsx +65 -61
- package/src/components/proxy-viewer/LogEntry.tsx +31 -18
- package/src/components/proxy-viewer/LogEntryHeader.tsx +42 -49
- package/src/components/proxy-viewer/ProviderLogoStack.tsx +60 -0
- package/src/components/proxy-viewer/ReplayDialog.tsx +8 -2
- package/src/components/proxy-viewer/RequestToolsPanel.tsx +9 -9
- package/src/components/proxy-viewer/ThreadConnector.tsx +22 -11
- package/src/components/proxy-viewer/TurnGroup.tsx +30 -36
- package/src/components/proxy-viewer/TurnGroupList.tsx +3 -0
- package/src/components/proxy-viewer/viewerState.ts +5 -6
- package/src/components/ui/icon-trigger.ts +2 -0
- package/src/components/ui/transient-toast.tsx +104 -0
- package/src/lib/sessionInfoContract.ts +5 -0
- package/src/lib/stopReason.ts +48 -16
- package/src/mcp/server.ts +71 -27
- package/src/mcp/toolHandlers.ts +23 -4
- package/src/proxy/formats/openai/alibabaProvider.ts +0 -4
- package/src/proxy/sessionInfo.ts +36 -3
- package/src/proxy/sessionSupervisor.ts +20 -1
- package/src/proxy/store.ts +1 -1
- package/src/routes/api/providers.$providerId.test.log.ts +8 -4
- package/styles/globals.css +180 -180
- package/.output/public/assets/CompareDrawer-DjgjIFx7.js +0 -1
- package/.output/public/assets/ProxyViewerContainer-CWUQZLYy.js +0 -106
- package/.output/public/assets/ReplayDialog-CU0Tbb2c.js +0 -1
- package/.output/public/assets/_sessionId-Cif8JZdn.js +0 -1
- package/.output/public/assets/index-D_WfwzUi.js +0 -1
- package/.output/public/assets/index-DtLuQrs0.css +0 -1
|
@@ -34,7 +34,7 @@ import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "../ui/
|
|
|
34
34
|
import type { CacheTrend } from "./cacheTrend";
|
|
35
35
|
|
|
36
36
|
const STATUS_BADGE_CLASSES: Record<StatusCategory, string> = {
|
|
37
|
-
success: "bg-
|
|
37
|
+
success: "bg-slate-300/[0.08] text-slate-100 border-slate-300/20",
|
|
38
38
|
client_error: "bg-amber-300/[0.10] text-amber-100 border-amber-300/30",
|
|
39
39
|
server_error: "bg-rose-400/[0.10] text-rose-100 border-rose-300/30",
|
|
40
40
|
pending: "bg-cyan-300/[0.08] text-cyan-100 border-cyan-300/25",
|
|
@@ -45,36 +45,31 @@ const STATUS_CUE_CLASSES: Record<
|
|
|
45
45
|
{
|
|
46
46
|
frame: string;
|
|
47
47
|
dot: string;
|
|
48
|
-
rail: string;
|
|
49
48
|
}
|
|
50
49
|
> = {
|
|
51
50
|
success: {
|
|
52
|
-
frame: "border-
|
|
53
|
-
dot: "bg-
|
|
54
|
-
rail: "bg-emerald-300/45 shadow-[0_0_22px_rgba(110,231,183,0.28)]",
|
|
51
|
+
frame: "border-cyan-200/15 bg-white/[0.04] text-slate-100",
|
|
52
|
+
dot: "bg-cyan-200/80 shadow-[0_0_14px_rgba(125,211,252,0.65)]",
|
|
55
53
|
},
|
|
56
54
|
client_error: {
|
|
57
55
|
frame: "border-amber-300/25 bg-amber-300/[0.08] text-amber-100",
|
|
58
56
|
dot: "bg-amber-200 shadow-[0_0_14px_rgba(252,211,77,0.8)]",
|
|
59
|
-
rail: "bg-amber-300/50 shadow-[0_0_22px_rgba(252,211,77,0.28)]",
|
|
60
57
|
},
|
|
61
58
|
server_error: {
|
|
62
59
|
frame: "border-rose-300/25 bg-rose-300/[0.08] text-rose-100",
|
|
63
60
|
dot: "bg-rose-300 shadow-[0_0_14px_rgba(253,164,175,0.8)]",
|
|
64
|
-
rail: "bg-rose-300/50 shadow-[0_0_22px_rgba(253,164,175,0.28)]",
|
|
65
61
|
},
|
|
66
62
|
pending: {
|
|
67
63
|
frame: "border-cyan-300/25 bg-cyan-300/[0.08] text-cyan-100",
|
|
68
64
|
dot: "bg-cyan-200 shadow-[0_0_14px_rgba(103,232,249,0.8)]",
|
|
69
|
-
rail: "bg-cyan-300/50 shadow-[0_0_22px_rgba(103,232,249,0.28)]",
|
|
70
65
|
},
|
|
71
66
|
};
|
|
72
67
|
|
|
73
68
|
const HEADER_ACTION_BUTTON_CLASS =
|
|
74
|
-
"
|
|
69
|
+
"border border-input bg-background hover:bg-accent hover:text-accent-foreground size-8 text-muted-foreground shadow-none hover:text-cyan-100";
|
|
75
70
|
|
|
76
71
|
const TELEMETRY_CHIP_CLASS =
|
|
77
|
-
"flex h-7 items-center gap-1 rounded-md
|
|
72
|
+
"border border-border bg-muted/40 flex h-7 items-center gap-1 rounded-md px-2 text-xs";
|
|
78
73
|
|
|
79
74
|
function formatElapsed(ms: number): string {
|
|
80
75
|
if (ms < 1000) return `${ms}ms`;
|
|
@@ -100,7 +95,7 @@ function statusCueLabel(category: StatusCategory, status: number | null): string
|
|
|
100
95
|
}
|
|
101
96
|
|
|
102
97
|
/**
|
|
103
|
-
* Inline trend indicator: small arrow (
|
|
98
|
+
* Inline trend indicator: small arrow (up / down) plus the absolute
|
|
104
99
|
* delta in compact form. Returns `null` when there is no trend to display.
|
|
105
100
|
*/
|
|
106
101
|
function CacheTrendIndicator({ trend }: { trend: CacheTrend | null }): JSX.Element | null {
|
|
@@ -110,7 +105,7 @@ function CacheTrendIndicator({ trend }: { trend: CacheTrend | null }): JSX.Eleme
|
|
|
110
105
|
const sign = isUp ? "+" : "-";
|
|
111
106
|
return (
|
|
112
107
|
<span className="flex items-center gap-0.5 text-muted-foreground tabular-nums">
|
|
113
|
-
<Icon className={isUp ? "size-3 text-
|
|
108
|
+
<Icon className={isUp ? "size-3 text-cyan-300" : "size-3 text-rose-400"} />
|
|
114
109
|
<span className="font-mono">
|
|
115
110
|
{sign}
|
|
116
111
|
{formatTokens(trend.delta)}
|
|
@@ -121,12 +116,12 @@ function CacheTrendIndicator({ trend }: { trend: CacheTrend | null }): JSX.Eleme
|
|
|
121
116
|
|
|
122
117
|
/**
|
|
123
118
|
* Per-tab action bundle surfaced in the header when the tab is active.
|
|
124
|
-
* - `copyText === null`
|
|
125
|
-
* - `expansion === null`
|
|
119
|
+
* - `copyText === null` - Copy button hidden (e.g. tab has no body to copy)
|
|
120
|
+
* - `expansion === null` - Expand-all button hidden (e.g. flat dict, or
|
|
126
121
|
* not valid JSON). When present, the button reflects the JSON viewer's
|
|
127
122
|
* bulk-expansion state and the caller is responsible for forwarding the
|
|
128
123
|
* matching `bulkDepth` / `bulkRevision` to the underlying viewer.
|
|
129
|
-
* - `diffWithRaw` / `diffWithPrevious`
|
|
124
|
+
* - `diffWithRaw` / `diffWithPrevious` - optional Diff buttons. Undefined
|
|
130
125
|
* means "not applicable" (hidden). Today only the Request tab sets them.
|
|
131
126
|
*/
|
|
132
127
|
export type HeaderTabAction = {
|
|
@@ -155,6 +150,8 @@ export type HeaderTabActions = Record<string, HeaderTabAction | undefined>;
|
|
|
155
150
|
|
|
156
151
|
export type LogEntryHeaderProps = {
|
|
157
152
|
log: CapturedLog;
|
|
153
|
+
/** Human-facing ordinal inside the current session. Internal actions still use `log.id`. */
|
|
154
|
+
displayNumber: number;
|
|
158
155
|
/** Number of messages in the request (supports both Anthropic and OpenAI formats). */
|
|
159
156
|
messageCount?: number | null;
|
|
160
157
|
/** Number of tools defined in the request (supports both Anthropic and OpenAI formats). */
|
|
@@ -178,12 +175,15 @@ export type LogEntryHeaderProps = {
|
|
|
178
175
|
onReplay?: () => void;
|
|
179
176
|
/** Slow-response threshold in seconds. `0` disables the warning indicator. */
|
|
180
177
|
slowResponseThresholdSeconds?: number;
|
|
178
|
+
/** Show client PID/project metadata on each log row. Parent process rows can disable this. */
|
|
179
|
+
showClientMetadata?: boolean;
|
|
181
180
|
/** Controls whether timestamps render as compact local time or full ISO strings. */
|
|
182
181
|
timeDisplayFormat: TimeDisplayFormat;
|
|
183
182
|
};
|
|
184
183
|
|
|
185
184
|
export const LogEntryHeader = memo(function ({
|
|
186
185
|
log,
|
|
186
|
+
displayNumber,
|
|
187
187
|
messageCount = null,
|
|
188
188
|
toolCount = null,
|
|
189
189
|
expanded,
|
|
@@ -193,6 +193,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
193
193
|
tabActions,
|
|
194
194
|
onReplay,
|
|
195
195
|
slowResponseThresholdSeconds = 0,
|
|
196
|
+
showClientMetadata = true,
|
|
196
197
|
timeDisplayFormat,
|
|
197
198
|
}: LogEntryHeaderProps): JSX.Element {
|
|
198
199
|
const statusCategory = getStatusCategory(log.responseStatus);
|
|
@@ -229,9 +230,9 @@ export const LogEntryHeader = memo(function ({
|
|
|
229
230
|
className={cn(
|
|
230
231
|
"group/header relative isolate flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 overflow-hidden px-3 py-2 cursor-pointer transition-[background-color,border-color,box-shadow]",
|
|
231
232
|
expanded
|
|
232
|
-
? "bg-[
|
|
233
|
-
: "bg-
|
|
234
|
-
"select-none before:pointer-events-none before:absolute before:inset-x-
|
|
233
|
+
? "bg-white/[0.035] shadow-[inset_0_1px_0_rgba(255,255,255,0.10)]"
|
|
234
|
+
: "bg-transparent hover:bg-white/[0.035]",
|
|
235
|
+
"select-none before:pointer-events-none before:absolute before:inset-x-3 before:top-0 before:h-px before:bg-white/12",
|
|
235
236
|
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:outline-none",
|
|
236
237
|
)}
|
|
237
238
|
onClick={onToggle}
|
|
@@ -242,40 +243,28 @@ export const LogEntryHeader = memo(function ({
|
|
|
242
243
|
}
|
|
243
244
|
}}
|
|
244
245
|
>
|
|
245
|
-
<span
|
|
246
|
-
className={cn(
|
|
247
|
-
"absolute left-0 top-2 bottom-2 w-0.5 rounded-r-full transition-colors",
|
|
248
|
-
statusCue.rail,
|
|
249
|
-
)}
|
|
250
|
-
aria-hidden="true"
|
|
251
|
-
/>
|
|
252
246
|
{/* Request ID */}
|
|
253
247
|
<span
|
|
254
248
|
className={cn(
|
|
255
|
-
"inline-flex h-8 shrink-0 items-center gap-2 rounded-md
|
|
249
|
+
"border border-border bg-muted/40 inline-flex h-8 shrink-0 items-center gap-2 rounded-md px-2 font-mono",
|
|
256
250
|
statusCue.frame,
|
|
257
251
|
)}
|
|
258
252
|
>
|
|
259
253
|
<span className={cn("size-1.5 rounded-full", statusCue.dot)} aria-hidden="true" />
|
|
260
|
-
<span className="text-
|
|
261
|
-
|
|
254
|
+
<span className="text-xs font-semibold tabular-nums" title={`Log ID ${String(log.id)}`}>
|
|
255
|
+
#{displayNumber}
|
|
262
256
|
</span>
|
|
263
|
-
<span className="text-xs font-semibold tabular-nums">#{log.id}</span>
|
|
264
257
|
</span>
|
|
265
258
|
|
|
266
259
|
{/* Request start time */}
|
|
267
|
-
<span className="flex h-7 items-center gap-1 rounded-md
|
|
260
|
+
<span className="border border-border bg-muted/40 flex h-7 shrink-0 items-center gap-1 rounded-md px-2 text-xs text-muted-foreground">
|
|
268
261
|
<Clock className="size-3" />
|
|
269
262
|
<span className="font-mono tabular-nums" title={log.timestamp}>
|
|
270
263
|
{formatTimestamp(log.timestamp, timeDisplayFormat)}
|
|
271
264
|
</span>
|
|
272
265
|
</span>
|
|
273
266
|
|
|
274
|
-
{/*
|
|
275
|
-
no longer render the logo (sessions with many logs would show the
|
|
276
|
-
same provider logo dozens of times). */}
|
|
277
|
-
|
|
278
|
-
{/* Response Status — only shown for non-200 or pending. Each category
|
|
267
|
+
{/* Response Status - only shown for non-200 or pending. Each category
|
|
279
268
|
carries a distinct icon in addition to color so the meaning is
|
|
280
269
|
legible without color perception. */}
|
|
281
270
|
<Badge
|
|
@@ -317,7 +306,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
317
306
|
)}
|
|
318
307
|
|
|
319
308
|
{/* Elapsed time. Tooltip is only shown when this log exceeded the slow-
|
|
320
|
-
response threshold
|
|
309
|
+
response threshold - for normal-speed logs the elapsed value is
|
|
321
310
|
self-explanatory, so we skip the tooltip wrapper to keep the row
|
|
322
311
|
quiet. */}
|
|
323
312
|
{log.elapsedMs !== null &&
|
|
@@ -362,7 +351,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
362
351
|
{log.streaming && tokenRateLabel !== null && (
|
|
363
352
|
<Tooltip>
|
|
364
353
|
<TooltipTrigger asChild>
|
|
365
|
-
<span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0 text-
|
|
354
|
+
<span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0 text-cyan-300")}>
|
|
366
355
|
<Gauge className="size-3" />
|
|
367
356
|
<span className="font-mono tabular-nums">{tokenRateLabel}</span>
|
|
368
357
|
</span>
|
|
@@ -382,7 +371,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
382
371
|
log.inputTokens !== null ? "text-blue-400" : "text-muted-foreground",
|
|
383
372
|
)}
|
|
384
373
|
>
|
|
385
|
-
IN {log.inputTokens !== null ? formatTokens(log.inputTokens) : "
|
|
374
|
+
IN {log.inputTokens !== null ? formatTokens(log.inputTokens) : "n/a"}
|
|
386
375
|
</span>
|
|
387
376
|
<span className="text-muted-foreground">/</span>
|
|
388
377
|
<span
|
|
@@ -391,7 +380,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
391
380
|
log.outputTokens !== null ? "text-amber-400" : "text-muted-foreground",
|
|
392
381
|
)}
|
|
393
382
|
>
|
|
394
|
-
OUT {log.outputTokens !== null ? formatTokens(log.outputTokens) : "
|
|
383
|
+
OUT {log.outputTokens !== null ? formatTokens(log.outputTokens) : "n/a"}
|
|
395
384
|
</span>
|
|
396
385
|
</span>
|
|
397
386
|
</span>
|
|
@@ -400,7 +389,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
400
389
|
{log.cacheCreationInputTokens !== null && log.cacheCreationInputTokens > 0 && (
|
|
401
390
|
<span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0")}>
|
|
402
391
|
<CacheTrendIndicator trend={cacheTrend?.creation ?? null} />
|
|
403
|
-
<span className="font-mono tabular-nums text-
|
|
392
|
+
<span className="font-mono tabular-nums text-cyan-300">
|
|
404
393
|
KV Cache +{formatTokens(log.cacheCreationInputTokens)}
|
|
405
394
|
</span>
|
|
406
395
|
</span>
|
|
@@ -444,7 +433,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
444
433
|
)}
|
|
445
434
|
|
|
446
435
|
{/* Client info (PID + project folder) */}
|
|
447
|
-
{(hasClientPid || hasClientProjectFolder) && (
|
|
436
|
+
{showClientMetadata && (hasClientPid || hasClientProjectFolder) && (
|
|
448
437
|
<Tooltip>
|
|
449
438
|
<TooltipTrigger asChild>
|
|
450
439
|
<span className={cn(TELEMETRY_CHIP_CLASS, "hidden shrink-0 text-purple-300 xl:flex")}>
|
|
@@ -477,7 +466,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
477
466
|
{/* Spacer */}
|
|
478
467
|
<span className="flex-1 min-w-0" />
|
|
479
468
|
|
|
480
|
-
{/* Header actions
|
|
469
|
+
{/* Header actions - only when expanded, so the collapsed view stays
|
|
481
470
|
compact. Buttons stop propagation so they don't toggle the log. */}
|
|
482
471
|
{expanded && (
|
|
483
472
|
<span
|
|
@@ -576,7 +565,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
576
565
|
aria-label={action.copyCopied ? "Copied" : action.copyLabel}
|
|
577
566
|
>
|
|
578
567
|
{action.copyCopied ? (
|
|
579
|
-
<Check className="size-3.5 text-
|
|
568
|
+
<Check className="size-3.5 text-cyan-300" />
|
|
580
569
|
) : (
|
|
581
570
|
<Copy className="size-3.5" />
|
|
582
571
|
)}
|
|
@@ -609,12 +598,16 @@ export const LogEntryHeader = memo(function ({
|
|
|
609
598
|
</span>
|
|
610
599
|
)}
|
|
611
600
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
601
|
+
<span
|
|
602
|
+
className="border border-input bg-background hover:bg-accent hover:text-accent-foreground inline-flex size-8 shrink-0 items-center justify-center rounded-md text-slate-200"
|
|
603
|
+
aria-hidden="true"
|
|
604
|
+
>
|
|
605
|
+
{expanded ? (
|
|
606
|
+
<ChevronDown className="size-4 shrink-0" />
|
|
607
|
+
) : (
|
|
608
|
+
<ChevronRight className="size-4 shrink-0" />
|
|
609
|
+
)}
|
|
610
|
+
</span>
|
|
618
611
|
</div>
|
|
619
612
|
</TooltipProvider>
|
|
620
613
|
);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { JSX } from "react";
|
|
2
|
+
import { cn } from "../../lib/utils";
|
|
3
|
+
import { ProviderLogo, type Provider } from "../providers/ProviderLogo";
|
|
4
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip";
|
|
5
|
+
|
|
6
|
+
const MAX_VISIBLE_LOGOS = 3;
|
|
7
|
+
|
|
8
|
+
export const ROW_CHEVRON_SLOT_CLASS =
|
|
9
|
+
"flex h-8 w-5 shrink-0 items-center justify-center text-muted-foreground";
|
|
10
|
+
|
|
11
|
+
export type ProviderLogoStackItem = {
|
|
12
|
+
provider: Provider;
|
|
13
|
+
tooltip: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function ProviderLogoStack({
|
|
17
|
+
items,
|
|
18
|
+
ariaLabel,
|
|
19
|
+
className,
|
|
20
|
+
}: {
|
|
21
|
+
items: readonly ProviderLogoStackItem[];
|
|
22
|
+
ariaLabel: string;
|
|
23
|
+
className?: string;
|
|
24
|
+
}): JSX.Element {
|
|
25
|
+
if (items.length === 0) {
|
|
26
|
+
return (
|
|
27
|
+
<span className={cn("flex h-8 w-12 shrink-0 items-center", className)} aria-hidden="true" />
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const visibleItems = items.slice(0, MAX_VISIBLE_LOGOS);
|
|
32
|
+
const overflowCount = items.length - visibleItems.length;
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<TooltipProvider delayDuration={150}>
|
|
36
|
+
<span className={cn("flex h-8 w-12 shrink-0 items-center", className)} aria-label={ariaLabel}>
|
|
37
|
+
{visibleItems.map((item, index) => (
|
|
38
|
+
<Tooltip key={`${item.provider}-${String(index)}`}>
|
|
39
|
+
<TooltipTrigger asChild>
|
|
40
|
+
<span
|
|
41
|
+
className={cn(
|
|
42
|
+
"border border-border bg-muted/40 inline-flex size-6 shrink-0 items-center justify-center rounded-md",
|
|
43
|
+
index > 0 && "-ml-2",
|
|
44
|
+
)}
|
|
45
|
+
>
|
|
46
|
+
<ProviderLogo provider={item.provider} className="size-4" />
|
|
47
|
+
</span>
|
|
48
|
+
</TooltipTrigger>
|
|
49
|
+
<TooltipContent>{item.tooltip}</TooltipContent>
|
|
50
|
+
</Tooltip>
|
|
51
|
+
))}
|
|
52
|
+
{overflowCount > 0 && (
|
|
53
|
+
<span className="border border-border bg-muted/40 -ml-2 inline-flex h-6 min-w-6 shrink-0 items-center justify-center rounded-md px-1 font-mono text-[10px] text-muted-foreground">
|
|
54
|
+
+{overflowCount}
|
|
55
|
+
</span>
|
|
56
|
+
)}
|
|
57
|
+
</span>
|
|
58
|
+
</TooltipProvider>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -35,6 +35,7 @@ type ReplayResult = z.infer<typeof ReplayResultSchema>;
|
|
|
35
35
|
|
|
36
36
|
type ReplayDialogProps = {
|
|
37
37
|
log: CapturedLog;
|
|
38
|
+
displayNumber: number;
|
|
38
39
|
open: boolean;
|
|
39
40
|
onOpenChange: (open: boolean) => void;
|
|
40
41
|
};
|
|
@@ -159,7 +160,12 @@ function ReplayComparisonTable({
|
|
|
159
160
|
);
|
|
160
161
|
}
|
|
161
162
|
|
|
162
|
-
export function ReplayDialog({
|
|
163
|
+
export function ReplayDialog({
|
|
164
|
+
log,
|
|
165
|
+
displayNumber,
|
|
166
|
+
open,
|
|
167
|
+
onOpenChange,
|
|
168
|
+
}: ReplayDialogProps): JSX.Element {
|
|
163
169
|
const { providers } = useProviders();
|
|
164
170
|
const originalBody = log.rawRequestBody ?? "{}";
|
|
165
171
|
const [modifiedBody, setModifiedBody] = useState<string>(() => {
|
|
@@ -310,7 +316,7 @@ export function ReplayDialog({ log, open, onOpenChange }: ReplayDialogProps): JS
|
|
|
310
316
|
<DialogHeader>
|
|
311
317
|
<DialogTitle className="flex items-center gap-2">
|
|
312
318
|
<RotateCcw className="size-4" />
|
|
313
|
-
|
|
319
|
+
<span title={`Log ID ${String(log.id)}`}>Replay Request #{displayNumber}</span>
|
|
314
320
|
</DialogTitle>
|
|
315
321
|
</DialogHeader>
|
|
316
322
|
|
|
@@ -101,7 +101,7 @@ function ToolRow({ tool, called }: { tool: RequestToolDefinition; called: boolea
|
|
|
101
101
|
|
|
102
102
|
return (
|
|
103
103
|
<Collapsible open={open} onOpenChange={setOpen}>
|
|
104
|
-
<div className="
|
|
104
|
+
<div className="border border-border bg-card shadow-sm rounded-[8px]">
|
|
105
105
|
<div className="flex min-w-0 items-center gap-2 px-2.5 py-2">
|
|
106
106
|
<button
|
|
107
107
|
type="button"
|
|
@@ -140,7 +140,7 @@ function ToolRow({ tool, called }: { tool: RequestToolDefinition; called: boolea
|
|
|
140
140
|
</button>
|
|
141
141
|
<button
|
|
142
142
|
type="button"
|
|
143
|
-
className="inline-flex size-7 shrink-0 items-center justify-center rounded text-muted-foreground transition-colors hover:
|
|
143
|
+
className="border border-input bg-background hover:bg-accent hover:text-accent-foreground inline-flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
144
144
|
onClick={nameCopy.copy}
|
|
145
145
|
aria-label={nameCopy.copied ? "Copied tool name" : "Copy tool name"}
|
|
146
146
|
>
|
|
@@ -153,13 +153,13 @@ function ToolRow({ tool, called }: { tool: RequestToolDefinition; called: boolea
|
|
|
153
153
|
</div>
|
|
154
154
|
<CollapsibleContent>
|
|
155
155
|
{open && (
|
|
156
|
-
<div className="space-y-2 border-t border-white/10 bg-white/[0.
|
|
156
|
+
<div className="space-y-2 border-t border-white/10 bg-white/[0.025] px-2.5 py-2">
|
|
157
157
|
{tool.description !== null && (
|
|
158
158
|
<p className="text-xs leading-relaxed text-muted-foreground">{tool.description}</p>
|
|
159
159
|
)}
|
|
160
160
|
<RequiredParameters names={tool.requiredParameters} />
|
|
161
161
|
{tool.schema !== null && (
|
|
162
|
-
<ScrollArea className="max-h-64 rounded-
|
|
162
|
+
<ScrollArea className="border border-border bg-card shadow-sm max-h-64 rounded-[8px] p-2">
|
|
163
163
|
<Suspense
|
|
164
164
|
fallback={<div className="text-xs text-muted-foreground">Loading JSON...</div>}
|
|
165
165
|
>
|
|
@@ -203,7 +203,7 @@ export const RequestTools = memo(function RequestTools({
|
|
|
203
203
|
const hiddenPreviewCount = summary.tools.length - previewTools.length;
|
|
204
204
|
|
|
205
205
|
return (
|
|
206
|
-
<section className="
|
|
206
|
+
<section className="border border-border bg-card shadow-sm mb-3 overflow-hidden rounded-[8px]">
|
|
207
207
|
<div className="flex min-w-0 flex-wrap items-center gap-2 px-3 py-2">
|
|
208
208
|
<div className="flex min-w-0 flex-1 items-center gap-2">
|
|
209
209
|
<Wrench className="size-4 shrink-0 text-sky-300/80" />
|
|
@@ -242,7 +242,7 @@ export const RequestTools = memo(function RequestTools({
|
|
|
242
242
|
type="button"
|
|
243
243
|
variant="ghost"
|
|
244
244
|
size="sm"
|
|
245
|
-
className="h-7
|
|
245
|
+
className="h-7 px-2 text-muted-foreground hover:text-cyan-100"
|
|
246
246
|
onClick={() => setExpanded((value) => !value)}
|
|
247
247
|
aria-expanded={expanded}
|
|
248
248
|
>
|
|
@@ -251,7 +251,7 @@ export const RequestTools = memo(function RequestTools({
|
|
|
251
251
|
</Button>
|
|
252
252
|
</div>
|
|
253
253
|
|
|
254
|
-
<div className="flex min-w-0 flex-wrap gap-1.5 border-t border-white/10 bg-
|
|
254
|
+
<div className="flex min-w-0 flex-wrap gap-1.5 border-t border-white/10 bg-white/[0.025] px-3 py-2">
|
|
255
255
|
{previewTools.map((tool) => (
|
|
256
256
|
<Badge
|
|
257
257
|
key={tool.name}
|
|
@@ -272,8 +272,8 @@ export const RequestTools = memo(function RequestTools({
|
|
|
272
272
|
</div>
|
|
273
273
|
|
|
274
274
|
{expanded && (
|
|
275
|
-
<div className="space-y-2 border-t border-white/10 bg-black/
|
|
276
|
-
<label className="flex h-8 items-center gap-2 rounded-md
|
|
275
|
+
<div className="space-y-2 border-t border-white/10 bg-black/10 px-3 py-3">
|
|
276
|
+
<label className="border border-input bg-background hover:bg-accent hover:text-accent-foreground flex h-8 items-center gap-2 rounded-md px-2 text-xs">
|
|
277
277
|
<Search className="size-3.5 shrink-0 text-muted-foreground" />
|
|
278
278
|
<input
|
|
279
279
|
value={query}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type JSX, useMemo } from "react";
|
|
2
2
|
import { cn } from "../../lib/utils";
|
|
3
|
-
import type
|
|
3
|
+
import { isTurnBoundary, type StopReason } from "../../lib/stopReason";
|
|
4
4
|
import { getCrabVariant, getInteriorCrabVariant } from "../ui/crab-variants";
|
|
5
5
|
|
|
6
6
|
export type ThreadConnectorProps = {
|
|
@@ -18,10 +18,24 @@ export type ThreadConnectorProps = {
|
|
|
18
18
|
onToggle?: () => void;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
function boundaryTitle(stopReason: StopReason): string {
|
|
22
|
+
switch (stopReason) {
|
|
23
|
+
case "end_turn":
|
|
24
|
+
return "End of Turn (Anthropic)";
|
|
25
|
+
case "stop":
|
|
26
|
+
return "End of Turn (OpenAI)";
|
|
27
|
+
case "length":
|
|
28
|
+
return "End of Turn (length limit)";
|
|
29
|
+
case "tool_use":
|
|
30
|
+
case null:
|
|
31
|
+
return "End of Turn";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
21
35
|
/**
|
|
22
36
|
* Vertical timeline connector. Top spacer uses a calc() that
|
|
23
37
|
* mixes rem (py-1 + half-line) and px (border) so the crab
|
|
24
|
-
* centre-line matches the LogEntry
|
|
38
|
+
* centre-line matches the LogEntry display number regardless of the
|
|
25
39
|
* root font-size. The bottom spacer is flex-1 so the outgoing
|
|
26
40
|
* line adapts to whatever height the LogEntry occupies.
|
|
27
41
|
*/
|
|
@@ -35,7 +49,7 @@ export function ThreadConnector({
|
|
|
35
49
|
collapsible = false,
|
|
36
50
|
onToggle,
|
|
37
51
|
}: ThreadConnectorProps): JSX.Element {
|
|
38
|
-
const isBoundary = stopReason
|
|
52
|
+
const isBoundary = isTurnBoundary(stopReason);
|
|
39
53
|
const isFusedBoundary = isOnlyEntry && isTurnStart && isBoundary;
|
|
40
54
|
const isRunning = isPending && !isBoundary;
|
|
41
55
|
const Crab = useMemo(() => getCrabVariant(crabIndex), [crabIndex]);
|
|
@@ -77,17 +91,14 @@ export function ThreadConnector({
|
|
|
77
91
|
<span title="Start and end of turn">
|
|
78
92
|
<FusedCrab
|
|
79
93
|
className={cn(
|
|
80
|
-
"size-3.5 text-
|
|
94
|
+
"size-3.5 text-cyan-300/90",
|
|
81
95
|
"animate-crab-settle",
|
|
82
|
-
"drop-shadow-[0_0_4px_rgba(
|
|
96
|
+
"drop-shadow-[0_0_4px_rgba(103,232,249,0.35)]",
|
|
83
97
|
)}
|
|
84
98
|
/>
|
|
85
99
|
</span>
|
|
86
100
|
) : isBoundary ? (
|
|
87
|
-
<span
|
|
88
|
-
title={stopReason === "end_turn" ? "End of Turn (Anthropic)" : "End of Turn (OpenAI)"}
|
|
89
|
-
{...interactiveProps}
|
|
90
|
-
>
|
|
101
|
+
<span title={boundaryTitle(stopReason)} {...interactiveProps}>
|
|
91
102
|
<Crab
|
|
92
103
|
className={cn(
|
|
93
104
|
"size-3.5 text-amber-400",
|
|
@@ -100,9 +111,9 @@ export function ThreadConnector({
|
|
|
100
111
|
<span title="Start of turn" {...interactiveProps}>
|
|
101
112
|
<Crab
|
|
102
113
|
className={cn(
|
|
103
|
-
"size-3.5 text-
|
|
114
|
+
"size-3.5 text-cyan-300/85",
|
|
104
115
|
"animate-crab-appear",
|
|
105
|
-
"drop-shadow-[0_0_4px_rgba(
|
|
116
|
+
"drop-shadow-[0_0_4px_rgba(103,232,249,0.32)]",
|
|
106
117
|
)}
|
|
107
118
|
/>
|
|
108
119
|
</span>
|