@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
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
Loader2,
|
|
8
8
|
MessageSquare,
|
|
9
9
|
Trash2,
|
|
10
|
-
User,
|
|
11
10
|
Zap,
|
|
12
11
|
} from "lucide-react";
|
|
13
12
|
import type { JSX } from "react";
|
|
@@ -17,8 +16,9 @@ import { formatTimestampRange } from "../../lib/timeDisplay";
|
|
|
17
16
|
import { cn, formatTokens } from "../../lib/utils";
|
|
18
17
|
import type { CapturedLog } from "../../contracts";
|
|
19
18
|
import { ConfirmDialog } from "../ui/confirm-dialog";
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
19
|
+
import type { Provider } from "../providers/ProviderLogo";
|
|
20
|
+
import { ClientLogo, clientAppLabel, detectClientApp } from "../clients/ClientLogo";
|
|
21
|
+
import { ProviderLogoStack } from "./ProviderLogoStack";
|
|
22
22
|
import { resolveLogFormat } from "./log-formats";
|
|
23
23
|
|
|
24
24
|
export type ConversationHeaderProps = {
|
|
@@ -31,8 +31,8 @@ export type ConversationHeaderProps = {
|
|
|
31
31
|
expanded: boolean;
|
|
32
32
|
onToggle: () => void;
|
|
33
33
|
/** Unique providers whose models appear in this group, with their distinct
|
|
34
|
-
* model names. Rendered as a row of provider logos next to the header
|
|
35
|
-
*
|
|
34
|
+
* model names. Rendered as a row of provider logos next to the header, one
|
|
35
|
+
* per provider so model-switching inside a single session is visible.
|
|
36
36
|
* at a glance. `undefined` hides the strip (default). */
|
|
37
37
|
providers?: { provider: Provider; models: string[] }[];
|
|
38
38
|
/** When true and the group is collapsed, show a spinner instead of the
|
|
@@ -50,6 +50,10 @@ export type ConversationHeaderProps = {
|
|
|
50
50
|
* Only shown as a secondary label when the session lacks a real
|
|
51
51
|
* `sessionId` (the same fallback path used for `clientPid`). */
|
|
52
52
|
clientProjectFolder?: string | null;
|
|
53
|
+
/** Show IDE identity in the header. Parent IDE rows can disable this to avoid repetition. */
|
|
54
|
+
showClientIdentity?: boolean;
|
|
55
|
+
/** Show process/project metadata in the header. Parent process rows can disable this. */
|
|
56
|
+
showProcessMetadata?: boolean;
|
|
53
57
|
/** Controls whether timestamps render as compact local time or full ISO strings. */
|
|
54
58
|
timeDisplayFormat: TimeDisplayFormat;
|
|
55
59
|
/** Clear all logs in this group. After confirmation the parent removes them
|
|
@@ -71,6 +75,8 @@ export function ConversationHeader({
|
|
|
71
75
|
userAgent,
|
|
72
76
|
clientPid,
|
|
73
77
|
clientProjectFolder,
|
|
78
|
+
showClientIdentity = true,
|
|
79
|
+
showProcessMetadata = true,
|
|
74
80
|
timeDisplayFormat,
|
|
75
81
|
onClear,
|
|
76
82
|
}: ConversationHeaderProps): JSX.Element {
|
|
@@ -89,6 +95,22 @@ export function ConversationHeader({
|
|
|
89
95
|
},
|
|
90
96
|
[conversationId],
|
|
91
97
|
);
|
|
98
|
+
const providerLogoItems =
|
|
99
|
+
providers?.map(({ provider, models }) => ({
|
|
100
|
+
provider,
|
|
101
|
+
tooltip: models.length === 1 ? (models[0] ?? provider) : models.join(", "),
|
|
102
|
+
})) ?? [];
|
|
103
|
+
const clientApp = detectClientApp({ userAgent, conversationId, clientProjectFolder });
|
|
104
|
+
const detectedClientLabel = clientAppLabel(clientApp);
|
|
105
|
+
const hasUserAgent = userAgent !== null && userAgent !== undefined && userAgent !== "";
|
|
106
|
+
const clientDisplayLabel = detectedClientLabel ?? userAgent ?? null;
|
|
107
|
+
const clientTooltip =
|
|
108
|
+
detectedClientLabel !== null && hasUserAgent ? `${detectedClientLabel} - ${userAgent}` : null;
|
|
109
|
+
const isFallbackConversation = conversationId.startsWith("PID:") || conversationId.includes("|");
|
|
110
|
+
const compactSessionLabel =
|
|
111
|
+
conversationId.length > 24
|
|
112
|
+
? conversationId.slice(0, 12) + "..." + conversationId.slice(-12)
|
|
113
|
+
: conversationId;
|
|
92
114
|
|
|
93
115
|
return (
|
|
94
116
|
<div
|
|
@@ -97,10 +119,10 @@ export function ConversationHeader({
|
|
|
97
119
|
data-nav-id={`conv-${conversationId}`}
|
|
98
120
|
data-nav-action={expanded ? "collapse" : "expand"}
|
|
99
121
|
className={cn(
|
|
100
|
-
"flex items-center gap-3 px-3 py-2
|
|
101
|
-
"hover:
|
|
122
|
+
"border border-border bg-card shadow-sm flex cursor-pointer items-center gap-3 rounded-[8px] px-3 py-2 transition-colors",
|
|
123
|
+
"hover:border-cyan-200/20",
|
|
102
124
|
"select-none",
|
|
103
|
-
"
|
|
125
|
+
"sticky top-0 z-10 mb-2",
|
|
104
126
|
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:outline-none",
|
|
105
127
|
)}
|
|
106
128
|
onClick={onToggle}
|
|
@@ -112,34 +134,28 @@ export function ConversationHeader({
|
|
|
112
134
|
}
|
|
113
135
|
}}
|
|
114
136
|
>
|
|
115
|
-
{/*
|
|
116
|
-
{
|
|
117
|
-
<ChevronDown className="size-4 text-muted-foreground shrink-0" />
|
|
118
|
-
) : isLoading ? (
|
|
119
|
-
<Loader2 className="size-4 animate-spin text-muted-foreground shrink-0" />
|
|
120
|
-
) : (
|
|
121
|
-
<ChevronRight className="size-4 text-muted-foreground shrink-0" />
|
|
122
|
-
)}
|
|
137
|
+
{/* Provider logos: first visual element whenever model/provider logos are available. */}
|
|
138
|
+
<ProviderLogoStack items={providerLogoItems} ariaLabel="Models used in this session" />
|
|
123
139
|
|
|
124
140
|
{/* Session identifier.
|
|
125
|
-
- Real session id (e.g. `sess_abc123...`)
|
|
141
|
+
- Real session id (e.g. `sess_abc123...`) - render the id with the
|
|
126
142
|
standard ellipsis truncation; show `PID <n>` as a secondary pill
|
|
127
143
|
when we know the originating process.
|
|
128
|
-
- Fallback id (no sessionId was emitted)
|
|
144
|
+
- Fallback id (no sessionId was emitted) - the conversationId is
|
|
129
145
|
already a synthesized `PID:<n>|<folder>` marker; render the PID
|
|
130
146
|
as the primary identifier (purple, same weight as a real id)
|
|
131
147
|
and the project folder as a secondary muted label. */}
|
|
132
|
-
{
|
|
148
|
+
{isFallbackConversation && showProcessMetadata ? (
|
|
133
149
|
<>
|
|
134
150
|
{clientPid !== null && clientPid !== undefined ? (
|
|
135
151
|
<span
|
|
136
|
-
className="
|
|
152
|
+
className="shrink-0 font-mono text-xs font-semibold text-violet-200"
|
|
137
153
|
title={`PID ${clientPid}`}
|
|
138
154
|
>
|
|
139
155
|
PID {clientPid}
|
|
140
156
|
</span>
|
|
141
157
|
) : (
|
|
142
|
-
<span className="
|
|
158
|
+
<span className="shrink-0 font-mono text-xs font-semibold text-violet-200">
|
|
143
159
|
{conversationId}
|
|
144
160
|
</span>
|
|
145
161
|
)}
|
|
@@ -157,16 +173,14 @@ export function ConversationHeader({
|
|
|
157
173
|
) : (
|
|
158
174
|
<>
|
|
159
175
|
<span
|
|
160
|
-
className="
|
|
176
|
+
className="shrink-0 font-mono text-xs font-semibold text-violet-200"
|
|
161
177
|
title={conversationId}
|
|
162
178
|
>
|
|
163
|
-
{
|
|
164
|
-
? conversationId.slice(0, 12) + "..." + conversationId.slice(-12)
|
|
165
|
-
: conversationId}
|
|
179
|
+
{isFallbackConversation ? "sessionless" : compactSessionLabel}
|
|
166
180
|
</span>
|
|
167
|
-
{clientPid !== null && clientPid !== undefined && (
|
|
181
|
+
{showProcessMetadata && clientPid !== null && clientPid !== undefined && (
|
|
168
182
|
<span
|
|
169
|
-
className="
|
|
183
|
+
className="border border-border bg-muted/40 shrink-0 rounded px-1.5 py-0.5 font-mono text-xs tabular-nums text-muted-foreground"
|
|
170
184
|
title={`Client process ID ${clientPid}`}
|
|
171
185
|
>
|
|
172
186
|
PID {clientPid}
|
|
@@ -175,41 +189,17 @@ export function ConversationHeader({
|
|
|
175
189
|
</>
|
|
176
190
|
)}
|
|
177
191
|
|
|
178
|
-
{/* User-Agent */}
|
|
179
|
-
{
|
|
192
|
+
{/* Client app / User-Agent */}
|
|
193
|
+
{showClientIdentity && clientDisplayLabel !== null && clientDisplayLabel !== "" && (
|
|
180
194
|
<span
|
|
181
195
|
className="flex items-center gap-1 text-muted-foreground text-xs shrink-0"
|
|
182
|
-
title={
|
|
196
|
+
title={clientTooltip ?? clientDisplayLabel}
|
|
183
197
|
>
|
|
184
|
-
<
|
|
185
|
-
<span className="font-mono tabular-nums truncate max-w-[120px]">
|
|
186
|
-
|
|
187
|
-
)}
|
|
188
|
-
|
|
189
|
-
{/* Provider logos — one per unique provider used in this session. When
|
|
190
|
-
a session switched models mid-stream (e.g. claude-3-5 → deepseek-v4)
|
|
191
|
-
multiple logos appear side-by-side; tooltip lists every distinct
|
|
192
|
-
model name. */}
|
|
193
|
-
{providers !== undefined && providers.length > 0 && (
|
|
194
|
-
<TooltipProvider delayDuration={150}>
|
|
195
|
-
<span
|
|
196
|
-
className="flex items-center gap-0.5 shrink-0"
|
|
197
|
-
aria-label="Models used in this session"
|
|
198
|
-
>
|
|
199
|
-
{providers.map(({ provider, models }) => (
|
|
200
|
-
<Tooltip key={provider}>
|
|
201
|
-
<TooltipTrigger asChild>
|
|
202
|
-
<span className="shrink-0">
|
|
203
|
-
<ProviderLogo provider={provider} className="size-4" />
|
|
204
|
-
</span>
|
|
205
|
-
</TooltipTrigger>
|
|
206
|
-
<TooltipContent>
|
|
207
|
-
{models.length === 1 ? models[0] : models.join(", ")}
|
|
208
|
-
</TooltipContent>
|
|
209
|
-
</Tooltip>
|
|
210
|
-
))}
|
|
198
|
+
<ClientLogo client={clientApp} className="size-5" />
|
|
199
|
+
<span className="font-mono tabular-nums truncate max-w-[120px]">
|
|
200
|
+
{clientDisplayLabel}
|
|
211
201
|
</span>
|
|
212
|
-
</
|
|
202
|
+
</span>
|
|
213
203
|
)}
|
|
214
204
|
|
|
215
205
|
{/* Time range */}
|
|
@@ -252,7 +242,7 @@ export function ConversationHeader({
|
|
|
252
242
|
}}
|
|
253
243
|
aria-label={`Open session ${conversationId} in a new tab`}
|
|
254
244
|
title="Open this session in a new tab"
|
|
255
|
-
className="
|
|
245
|
+
className="border border-input bg-background hover:bg-accent hover:text-accent-foreground inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md text-muted-foreground transition-colors hover:text-foreground"
|
|
256
246
|
>
|
|
257
247
|
<ExternalLink className="size-3.5" />
|
|
258
248
|
</button>
|
|
@@ -264,12 +254,26 @@ export function ConversationHeader({
|
|
|
264
254
|
onClick={handleClearClick}
|
|
265
255
|
aria-label={`Clear group (${totalCalls} request${totalCalls !== 1 ? "s" : ""})`}
|
|
266
256
|
title="Clear this group"
|
|
267
|
-
className="
|
|
257
|
+
className="border border-input bg-background hover:bg-accent hover:text-accent-foreground inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md text-muted-foreground transition-colors hover:text-foreground"
|
|
268
258
|
>
|
|
269
259
|
<Trash2 className="size-3.5" />
|
|
270
260
|
</button>
|
|
271
261
|
)}
|
|
272
262
|
|
|
263
|
+
{/* Expand chevron - shows spinner when collapsed and group has pending logs */}
|
|
264
|
+
<span
|
|
265
|
+
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"
|
|
266
|
+
aria-hidden="true"
|
|
267
|
+
>
|
|
268
|
+
{expanded ? (
|
|
269
|
+
<ChevronDown className="size-4 shrink-0" />
|
|
270
|
+
) : isLoading ? (
|
|
271
|
+
<Loader2 className="size-4 shrink-0 animate-spin" />
|
|
272
|
+
) : (
|
|
273
|
+
<ChevronRight className="size-4 shrink-0" />
|
|
274
|
+
)}
|
|
275
|
+
</span>
|
|
276
|
+
|
|
273
277
|
<ConfirmDialog
|
|
274
278
|
open={confirmOpen}
|
|
275
279
|
onOpenChange={setConfirmOpen}
|
|
@@ -320,10 +324,10 @@ export function hasMixedApiFormat(logs: CapturedLog[]): boolean {
|
|
|
320
324
|
}
|
|
321
325
|
|
|
322
326
|
export function getConversationId(log: CapturedLog): string {
|
|
323
|
-
if (log.isTest === true) return "provider-test";
|
|
324
327
|
if (log.sessionId !== null && log.sessionId !== "" && log.sessionId !== undefined) {
|
|
325
328
|
return log.sessionId;
|
|
326
329
|
}
|
|
330
|
+
if (log.isTest === true) return "provider-test";
|
|
327
331
|
const hasPid = log.clientPid !== null && log.clientPid !== undefined;
|
|
328
332
|
const hasFolder = log.clientProjectFolder !== null && log.clientProjectFolder !== undefined;
|
|
329
333
|
if (!hasPid && !hasFolder) return "default";
|
|
@@ -75,7 +75,7 @@ const BODY_CHUNK_BYTES = 256 * 1024;
|
|
|
75
75
|
const CHUNKED_BODY_THRESHOLD_BYTES = 1024 * 1024;
|
|
76
76
|
|
|
77
77
|
const TAB_TRIGGER_CLASS =
|
|
78
|
-
"h-9 flex-none rounded-
|
|
78
|
+
"h-9 flex-none rounded-md border border-transparent bg-transparent px-3 text-xs font-semibold text-muted-foreground shadow-none transition-colors hover:border-white/10 hover:bg-white/[0.045] hover:text-foreground data-[state=active]:border-cyan-200/25 data-[state=active]:bg-cyan-300/[0.08] data-[state=active]:text-cyan-100 data-[state=active]:shadow-none";
|
|
79
79
|
|
|
80
80
|
function createEmptyBodyPreviewState(totalBytes: number | null = null): BodyPreviewState {
|
|
81
81
|
return {
|
|
@@ -140,7 +140,7 @@ function ExpandedPanel({
|
|
|
140
140
|
return (
|
|
141
141
|
<div
|
|
142
142
|
className={cn(
|
|
143
|
-
"inspector-scrollbar mx-3 mb-3 max-h-[72vh] overflow-auto rounded-
|
|
143
|
+
"border border-border bg-card shadow-sm inspector-scrollbar mx-3 mb-3 max-h-[72vh] overflow-auto rounded-[8px] p-3",
|
|
144
144
|
className,
|
|
145
145
|
)}
|
|
146
146
|
>
|
|
@@ -151,7 +151,7 @@ function ExpandedPanel({
|
|
|
151
151
|
|
|
152
152
|
function EmptyState({ children }: { children: ReactNode }): JSX.Element {
|
|
153
153
|
return (
|
|
154
|
-
<div className="
|
|
154
|
+
<div className="border border-border bg-muted/40 rounded-md border-dashed px-3 py-5 text-center text-xs italic text-muted-foreground">
|
|
155
155
|
{children}
|
|
156
156
|
</div>
|
|
157
157
|
);
|
|
@@ -159,7 +159,7 @@ function EmptyState({ children }: { children: ReactNode }): JSX.Element {
|
|
|
159
159
|
|
|
160
160
|
function RawTextBlock({ children }: { children: ReactNode }): JSX.Element {
|
|
161
161
|
return (
|
|
162
|
-
<pre className="inspector-scrollbar max-h-[64vh] overflow-auto whitespace-pre-wrap break-words rounded-
|
|
162
|
+
<pre className="border border-border bg-card shadow-sm inspector-scrollbar max-h-[64vh] overflow-auto whitespace-pre-wrap break-words rounded-[8px] p-3 font-mono text-xs leading-relaxed text-muted-foreground">
|
|
163
163
|
{children}
|
|
164
164
|
</pre>
|
|
165
165
|
);
|
|
@@ -231,7 +231,7 @@ function BodyPreviewNotice({
|
|
|
231
231
|
type="button"
|
|
232
232
|
variant="outline"
|
|
233
233
|
size="sm"
|
|
234
|
-
className="
|
|
234
|
+
className="border border-input bg-background hover:bg-accent hover:text-accent-foreground h-7 text-xs text-cyan-100"
|
|
235
235
|
onClick={onLoadMore}
|
|
236
236
|
>
|
|
237
237
|
{actionLabel}
|
|
@@ -257,13 +257,13 @@ function HeaderRows({
|
|
|
257
257
|
if (entries.length === 0) return <EmptyState>{emptyLabel}</EmptyState>;
|
|
258
258
|
|
|
259
259
|
return (
|
|
260
|
-
<div className="
|
|
260
|
+
<div className="border border-border bg-card shadow-sm overflow-hidden rounded-[8px] font-mono text-xs">
|
|
261
261
|
{entries.map(([key, value]) => (
|
|
262
262
|
<div
|
|
263
263
|
key={key}
|
|
264
|
-
className="grid min-w-0 grid-cols-[minmax(140px,0.32fr)_minmax(0,1fr)] border-b border-white/
|
|
264
|
+
className="grid min-w-0 grid-cols-[minmax(140px,0.32fr)_minmax(0,1fr)] border-b border-white/10 last:border-b-0"
|
|
265
265
|
>
|
|
266
|
-
<div className="min-w-0 truncate border-r border-white/
|
|
266
|
+
<div className="min-w-0 truncate border-r border-white/10 bg-white/[0.035] px-2.5 py-2 font-semibold text-cyan-200">
|
|
267
267
|
{key}
|
|
268
268
|
</div>
|
|
269
269
|
<div className="min-w-0 truncate px-2.5 py-2 text-muted-foreground" title={value}>
|
|
@@ -277,6 +277,8 @@ function HeaderRows({
|
|
|
277
277
|
|
|
278
278
|
export type LogEntryProps = {
|
|
279
279
|
log: CapturedLog;
|
|
280
|
+
/** Human-facing ordinal inside the current session. Internal lookups still use `log.id`. */
|
|
281
|
+
displayNumber: number;
|
|
280
282
|
viewMode?: "simple" | "full";
|
|
281
283
|
/** Live "strip Claude Code billing header" flag, sourced once at the viewer
|
|
282
284
|
* container. Hoisted out of `LogEntry` so a single SWR subscription serves
|
|
@@ -295,6 +297,8 @@ export type LogEntryProps = {
|
|
|
295
297
|
cacheTrend?: CacheTrendEntry | null;
|
|
296
298
|
/** Callback to open CompareDrawer with this log and its immediate predecessor. */
|
|
297
299
|
onCompareWithPrevious?: (log: CapturedLog) => void;
|
|
300
|
+
/** Show client PID/project metadata on the log header row. */
|
|
301
|
+
showClientMetadata?: boolean;
|
|
298
302
|
};
|
|
299
303
|
|
|
300
304
|
function DiffToggleButton({
|
|
@@ -314,7 +318,7 @@ function DiffToggleButton({
|
|
|
314
318
|
"h-8 border-white/10 text-xs shadow-none",
|
|
315
319
|
active
|
|
316
320
|
? "bg-cyan-300 text-black hover:bg-cyan-200"
|
|
317
|
-
: "
|
|
321
|
+
: "border border-input bg-background hover:bg-accent hover:text-accent-foreground text-muted-foreground hover:text-cyan-100",
|
|
318
322
|
)}
|
|
319
323
|
onClick={onClick}
|
|
320
324
|
aria-pressed={active}
|
|
@@ -361,12 +365,14 @@ const HeadersDiffContent = memo(function ({
|
|
|
361
365
|
|
|
362
366
|
export const LogEntry = memo(function ({
|
|
363
367
|
log,
|
|
368
|
+
displayNumber,
|
|
364
369
|
viewMode = "simple",
|
|
365
370
|
strip,
|
|
366
371
|
slowResponseThresholdSeconds,
|
|
367
372
|
timeDisplayFormat,
|
|
368
373
|
cacheTrend = null,
|
|
369
374
|
onCompareWithPrevious,
|
|
375
|
+
showClientMetadata = true,
|
|
370
376
|
}: LogEntryProps): JSX.Element {
|
|
371
377
|
const [expanded, setExpanded] = useState<boolean>(false);
|
|
372
378
|
const [replayOpen, setReplayOpen] = useState<boolean>(false);
|
|
@@ -429,7 +435,7 @@ export const LogEntry = memo(function ({
|
|
|
429
435
|
);
|
|
430
436
|
|
|
431
437
|
// Headers are rendered as a flat list, so we copy them as pretty-printed JSON.
|
|
432
|
-
// Only build the string when there's at least one entry
|
|
438
|
+
// Only build the string when there's at least one entry; empty headers would
|
|
433
439
|
// otherwise copy "{}", which is misleading.
|
|
434
440
|
const headersText = useMemo(
|
|
435
441
|
() =>
|
|
@@ -715,16 +721,16 @@ export const LogEntry = memo(function ({
|
|
|
715
721
|
<TooltipProvider>
|
|
716
722
|
<div
|
|
717
723
|
className={cn(
|
|
718
|
-
"relative mb-2 overflow-hidden rounded-
|
|
724
|
+
"border border-border bg-card shadow-sm relative mb-2 overflow-hidden rounded-[8px] transition-[background-color,border-color,box-shadow]",
|
|
719
725
|
expanded
|
|
720
|
-
? "border-cyan-
|
|
721
|
-
: "
|
|
726
|
+
? "border-cyan-200/25 shadow-[0_24px_80px_rgba(0,0,0,0.36),0_0_0_1px_rgba(103,232,249,0.05),inset_0_1px_0_rgba(255,255,255,0.14)]"
|
|
727
|
+
: "hover:border-cyan-200/18",
|
|
722
728
|
)}
|
|
723
729
|
>
|
|
724
730
|
<div
|
|
725
731
|
className={cn(
|
|
726
732
|
"pointer-events-none absolute inset-y-3 left-1 w-px rounded-full",
|
|
727
|
-
expanded ? "bg-cyan-
|
|
733
|
+
expanded ? "bg-cyan-200/55 shadow-[0_0_20px_rgba(103,232,249,0.26)]" : "bg-white/12",
|
|
728
734
|
)}
|
|
729
735
|
aria-hidden="true"
|
|
730
736
|
/>
|
|
@@ -734,12 +740,14 @@ export const LogEntry = memo(function ({
|
|
|
734
740
|
/>
|
|
735
741
|
<LogEntryHeader
|
|
736
742
|
log={displayLog}
|
|
743
|
+
displayNumber={displayNumber}
|
|
737
744
|
messageCount={requestAnalysis.messageCount}
|
|
738
745
|
toolCount={requestAnalysis.toolCount}
|
|
739
746
|
expanded={expanded}
|
|
740
747
|
onToggle={handleToggleExpanded}
|
|
741
748
|
cacheTrend={cacheTrend}
|
|
742
749
|
slowResponseThresholdSeconds={slowResponseThresholdSeconds}
|
|
750
|
+
showClientMetadata={showClientMetadata}
|
|
743
751
|
timeDisplayFormat={timeDisplayFormat}
|
|
744
752
|
activeTab={activeTab}
|
|
745
753
|
tabActions={tabActions}
|
|
@@ -754,18 +762,18 @@ export const LogEntry = memo(function ({
|
|
|
754
762
|
|
|
755
763
|
{expanded && (
|
|
756
764
|
<div
|
|
757
|
-
className="relative border-t border-
|
|
765
|
+
className="relative border-t border-white/10 bg-black/[0.18] pt-2"
|
|
758
766
|
onClick={(e) => e.stopPropagation()}
|
|
759
767
|
onKeyDown={(e) => e.stopPropagation()}
|
|
760
768
|
>
|
|
761
769
|
<div
|
|
762
|
-
className="pointer-events-none absolute inset-x-0 top-0 h-12 bg-gradient-to-b from-
|
|
770
|
+
className="pointer-events-none absolute inset-x-0 top-0 h-12 bg-gradient-to-b from-white/[0.05] to-transparent"
|
|
763
771
|
aria-hidden="true"
|
|
764
772
|
/>
|
|
765
773
|
<Tabs value={activeTab} onValueChange={setActiveTab} className="gap-2">
|
|
766
774
|
<TabsList
|
|
767
775
|
variant="line"
|
|
768
|
-
className="inspector-scrollbar relative mx-3 h-auto w-[calc(100%-1.5rem)] justify-start overflow-x-auto rounded-
|
|
776
|
+
className="border border-border bg-muted/40 inspector-scrollbar relative mx-3 h-auto w-[calc(100%-1.5rem)] justify-start overflow-x-auto rounded-[8px] p-1"
|
|
769
777
|
>
|
|
770
778
|
{viewMode === "full" && (
|
|
771
779
|
<TabsTrigger className={TAB_TRIGGER_CLASS} value="raw-headers">
|
|
@@ -1051,7 +1059,12 @@ export const LogEntry = memo(function ({
|
|
|
1051
1059
|
)}
|
|
1052
1060
|
</div>
|
|
1053
1061
|
<Suspense fallback={null}>
|
|
1054
|
-
<LazyReplayDialog
|
|
1062
|
+
<LazyReplayDialog
|
|
1063
|
+
log={displayLog}
|
|
1064
|
+
displayNumber={displayNumber}
|
|
1065
|
+
open={replayOpen}
|
|
1066
|
+
onOpenChange={setReplayOpen}
|
|
1067
|
+
/>
|
|
1055
1068
|
</Suspense>
|
|
1056
1069
|
</TooltipProvider>
|
|
1057
1070
|
);
|