@tonyclaw/agent-inspector 2.1.15 → 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-BcEd6V-V.js → CompareDrawer-Ccrrcx1j.js} +1 -1
- package/.output/public/assets/ProxyViewerContainer-DLqClc9A.js +106 -0
- package/.output/public/assets/{ReplayDialog-BNpC0548.js → ReplayDialog-C7axhr-l.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-Ds1uRLVB.js → RequestAnatomy-WOptg8j_.js} +1 -1
- package/.output/public/assets/{ResponseView-7KPVqKl5.js → ResponseView-CSAcxh_M.js} +2 -2
- package/.output/public/assets/{StreamingChunkSequence-BHQT261s.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-DjKt8XKe.js → index-DMPNh46t.js} +1 -1
- package/.output/public/assets/{json-viewer-CkCu-rka.js → json-viewer-inX5QSa3.js} +1 -1
- package/.output/public/assets/{main-DpD1N0S8.js → main-aCAKYGSD.js} +2 -2
- package/.output/server/_libs/lucide-react.mjs +207 -171
- package/.output/server/{_sessionId-DF9Sy8cP.mjs → _sessionId-C0fhvQnn.mjs} +2 -2
- package/.output/server/_ssr/{CompareDrawer-BoxztaO7.mjs → CompareDrawer-CO-Ti5sg.mjs} +3 -3
- package/.output/server/_ssr/{ProxyViewerContainer-CRBkqFlJ.mjs → ProxyViewerContainer-N4J7uBCX.mjs} +807 -386
- package/.output/server/_ssr/{ReplayDialog-Cc1dyDuK.mjs → ReplayDialog-J1VxC0In.mjs} +4 -4
- package/.output/server/_ssr/{RequestAnatomy-CMGSsz5Z.mjs → RequestAnatomy-DBH-F3rq.mjs} +3 -3
- package/.output/server/_ssr/{ResponseView-Cp10DM1D.mjs → ResponseView-DLFwV7Hd.mjs} +3 -3
- package/.output/server/_ssr/{StreamingChunkSequence-B1VGxy3A.mjs → StreamingChunkSequence-BywmuND0.mjs} +3 -3
- package/.output/server/_ssr/{index-47XVPghS.mjs → index-CStymIc0.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-zDE2rrmJ.mjs → json-viewer-DlQu0N1x.mjs} +3 -3
- package/.output/server/_ssr/{router-DVeuZFqI.mjs → router-CQ4Zxhmr.mjs} +112 -46
- package/.output/server/{_tanstack-start-manifest_v-Bp8JxtPW.mjs → _tanstack-start-manifest_v-BHeJBGfo.mjs} +1 -1
- package/.output/server/index.mjs +72 -72
- package/package.json +1 -1
- package/src/components/ProxyViewer.tsx +146 -105
- package/src/components/ProxyViewerContainer.tsx +77 -67
- package/src/components/alerts/AlertsDialog.tsx +2 -1
- package/src/components/clients/ClientLogo.tsx +1 -1
- package/src/components/groups/GroupsDialog.tsx +19 -1
- 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 +6 -6
- 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 +44 -32
- package/src/components/proxy-viewer/LogEntry.tsx +21 -17
- package/src/components/proxy-viewer/LogEntryHeader.tsx +38 -45
- package/src/components/proxy-viewer/ProviderLogoStack.tsx +2 -2
- package/src/components/proxy-viewer/RequestToolsPanel.tsx +9 -9
- package/src/components/proxy-viewer/ThreadConnector.tsx +4 -4
- package/src/components/proxy-viewer/TurnGroup.tsx +16 -11
- package/src/components/proxy-viewer/TurnGroupList.tsx +3 -0
- package/src/components/proxy-viewer/viewerState.ts +0 -4
- package/src/components/ui/icon-trigger.ts +2 -0
- package/src/components/ui/transient-toast.tsx +104 -0
- package/src/lib/sessionInfoContract.ts +4 -0
- 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 +31 -2
- 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/ProxyViewerContainer-h851qWNp.js +0 -106
- package/.output/public/assets/_sessionId-DWePGjnS.js +0 -1
- package/.output/public/assets/index-CI1-G8ua.js +0 -1
- package/.output/public/assets/index-DdhFqPsI.css +0 -1
|
@@ -18,7 +18,7 @@ import type { CapturedLog } from "../../contracts";
|
|
|
18
18
|
import { ConfirmDialog } from "../ui/confirm-dialog";
|
|
19
19
|
import type { Provider } from "../providers/ProviderLogo";
|
|
20
20
|
import { ClientLogo, clientAppLabel, detectClientApp } from "../clients/ClientLogo";
|
|
21
|
-
import { ProviderLogoStack
|
|
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 {
|
|
@@ -100,6 +106,11 @@ export function ConversationHeader({
|
|
|
100
106
|
const clientDisplayLabel = detectedClientLabel ?? userAgent ?? null;
|
|
101
107
|
const clientTooltip =
|
|
102
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;
|
|
103
114
|
|
|
104
115
|
return (
|
|
105
116
|
<div
|
|
@@ -108,10 +119,10 @@ export function ConversationHeader({
|
|
|
108
119
|
data-nav-id={`conv-${conversationId}`}
|
|
109
120
|
data-nav-action={expanded ? "collapse" : "expand"}
|
|
110
121
|
className={cn(
|
|
111
|
-
"flex items-center gap-3 px-3 py-2
|
|
112
|
-
"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",
|
|
113
124
|
"select-none",
|
|
114
|
-
"
|
|
125
|
+
"sticky top-0 z-10 mb-2",
|
|
115
126
|
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:outline-none",
|
|
116
127
|
)}
|
|
117
128
|
onClick={onToggle}
|
|
@@ -126,36 +137,25 @@ export function ConversationHeader({
|
|
|
126
137
|
{/* Provider logos: first visual element whenever model/provider logos are available. */}
|
|
127
138
|
<ProviderLogoStack items={providerLogoItems} ariaLabel="Models used in this session" />
|
|
128
139
|
|
|
129
|
-
{/* Expand chevron - shows spinner when collapsed and group has pending logs */}
|
|
130
|
-
<span className={ROW_CHEVRON_SLOT_CLASS}>
|
|
131
|
-
{expanded ? (
|
|
132
|
-
<ChevronDown className="size-4 shrink-0" />
|
|
133
|
-
) : isLoading ? (
|
|
134
|
-
<Loader2 className="size-4 shrink-0 animate-spin" />
|
|
135
|
-
) : (
|
|
136
|
-
<ChevronRight className="size-4 shrink-0" />
|
|
137
|
-
)}
|
|
138
|
-
</span>
|
|
139
|
-
|
|
140
140
|
{/* Session identifier.
|
|
141
|
-
- Real session id (e.g. `sess_abc123...`)
|
|
141
|
+
- Real session id (e.g. `sess_abc123...`) - render the id with the
|
|
142
142
|
standard ellipsis truncation; show `PID <n>` as a secondary pill
|
|
143
143
|
when we know the originating process.
|
|
144
|
-
- Fallback id (no sessionId was emitted)
|
|
144
|
+
- Fallback id (no sessionId was emitted) - the conversationId is
|
|
145
145
|
already a synthesized `PID:<n>|<folder>` marker; render the PID
|
|
146
146
|
as the primary identifier (purple, same weight as a real id)
|
|
147
147
|
and the project folder as a secondary muted label. */}
|
|
148
|
-
{
|
|
148
|
+
{isFallbackConversation && showProcessMetadata ? (
|
|
149
149
|
<>
|
|
150
150
|
{clientPid !== null && clientPid !== undefined ? (
|
|
151
151
|
<span
|
|
152
|
-
className="
|
|
152
|
+
className="shrink-0 font-mono text-xs font-semibold text-violet-200"
|
|
153
153
|
title={`PID ${clientPid}`}
|
|
154
154
|
>
|
|
155
155
|
PID {clientPid}
|
|
156
156
|
</span>
|
|
157
157
|
) : (
|
|
158
|
-
<span className="
|
|
158
|
+
<span className="shrink-0 font-mono text-xs font-semibold text-violet-200">
|
|
159
159
|
{conversationId}
|
|
160
160
|
</span>
|
|
161
161
|
)}
|
|
@@ -173,16 +173,14 @@ export function ConversationHeader({
|
|
|
173
173
|
) : (
|
|
174
174
|
<>
|
|
175
175
|
<span
|
|
176
|
-
className="
|
|
176
|
+
className="shrink-0 font-mono text-xs font-semibold text-violet-200"
|
|
177
177
|
title={conversationId}
|
|
178
178
|
>
|
|
179
|
-
{
|
|
180
|
-
? conversationId.slice(0, 12) + "..." + conversationId.slice(-12)
|
|
181
|
-
: conversationId}
|
|
179
|
+
{isFallbackConversation ? "sessionless" : compactSessionLabel}
|
|
182
180
|
</span>
|
|
183
|
-
{clientPid !== null && clientPid !== undefined && (
|
|
181
|
+
{showProcessMetadata && clientPid !== null && clientPid !== undefined && (
|
|
184
182
|
<span
|
|
185
|
-
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"
|
|
186
184
|
title={`Client process ID ${clientPid}`}
|
|
187
185
|
>
|
|
188
186
|
PID {clientPid}
|
|
@@ -192,7 +190,7 @@ export function ConversationHeader({
|
|
|
192
190
|
)}
|
|
193
191
|
|
|
194
192
|
{/* Client app / User-Agent */}
|
|
195
|
-
{clientDisplayLabel !== null && clientDisplayLabel !== "" && (
|
|
193
|
+
{showClientIdentity && clientDisplayLabel !== null && clientDisplayLabel !== "" && (
|
|
196
194
|
<span
|
|
197
195
|
className="flex items-center gap-1 text-muted-foreground text-xs shrink-0"
|
|
198
196
|
title={clientTooltip ?? clientDisplayLabel}
|
|
@@ -244,7 +242,7 @@ export function ConversationHeader({
|
|
|
244
242
|
}}
|
|
245
243
|
aria-label={`Open session ${conversationId} in a new tab`}
|
|
246
244
|
title="Open this session in a new tab"
|
|
247
|
-
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"
|
|
248
246
|
>
|
|
249
247
|
<ExternalLink className="size-3.5" />
|
|
250
248
|
</button>
|
|
@@ -256,12 +254,26 @@ export function ConversationHeader({
|
|
|
256
254
|
onClick={handleClearClick}
|
|
257
255
|
aria-label={`Clear group (${totalCalls} request${totalCalls !== 1 ? "s" : ""})`}
|
|
258
256
|
title="Clear this group"
|
|
259
|
-
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"
|
|
260
258
|
>
|
|
261
259
|
<Trash2 className="size-3.5" />
|
|
262
260
|
</button>
|
|
263
261
|
)}
|
|
264
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
|
+
|
|
265
277
|
<ConfirmDialog
|
|
266
278
|
open={confirmOpen}
|
|
267
279
|
onOpenChange={setConfirmOpen}
|
|
@@ -312,10 +324,10 @@ export function hasMixedApiFormat(logs: CapturedLog[]): boolean {
|
|
|
312
324
|
}
|
|
313
325
|
|
|
314
326
|
export function getConversationId(log: CapturedLog): string {
|
|
315
|
-
if (log.isTest === true) return "provider-test";
|
|
316
327
|
if (log.sessionId !== null && log.sessionId !== "" && log.sessionId !== undefined) {
|
|
317
328
|
return log.sessionId;
|
|
318
329
|
}
|
|
330
|
+
if (log.isTest === true) return "provider-test";
|
|
319
331
|
const hasPid = log.clientPid !== null && log.clientPid !== undefined;
|
|
320
332
|
const hasFolder = log.clientProjectFolder !== null && log.clientProjectFolder !== undefined;
|
|
321
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}>
|
|
@@ -297,6 +297,8 @@ export type LogEntryProps = {
|
|
|
297
297
|
cacheTrend?: CacheTrendEntry | null;
|
|
298
298
|
/** Callback to open CompareDrawer with this log and its immediate predecessor. */
|
|
299
299
|
onCompareWithPrevious?: (log: CapturedLog) => void;
|
|
300
|
+
/** Show client PID/project metadata on the log header row. */
|
|
301
|
+
showClientMetadata?: boolean;
|
|
300
302
|
};
|
|
301
303
|
|
|
302
304
|
function DiffToggleButton({
|
|
@@ -316,7 +318,7 @@ function DiffToggleButton({
|
|
|
316
318
|
"h-8 border-white/10 text-xs shadow-none",
|
|
317
319
|
active
|
|
318
320
|
? "bg-cyan-300 text-black hover:bg-cyan-200"
|
|
319
|
-
: "
|
|
321
|
+
: "border border-input bg-background hover:bg-accent hover:text-accent-foreground text-muted-foreground hover:text-cyan-100",
|
|
320
322
|
)}
|
|
321
323
|
onClick={onClick}
|
|
322
324
|
aria-pressed={active}
|
|
@@ -370,6 +372,7 @@ export const LogEntry = memo(function ({
|
|
|
370
372
|
timeDisplayFormat,
|
|
371
373
|
cacheTrend = null,
|
|
372
374
|
onCompareWithPrevious,
|
|
375
|
+
showClientMetadata = true,
|
|
373
376
|
}: LogEntryProps): JSX.Element {
|
|
374
377
|
const [expanded, setExpanded] = useState<boolean>(false);
|
|
375
378
|
const [replayOpen, setReplayOpen] = useState<boolean>(false);
|
|
@@ -432,7 +435,7 @@ export const LogEntry = memo(function ({
|
|
|
432
435
|
);
|
|
433
436
|
|
|
434
437
|
// Headers are rendered as a flat list, so we copy them as pretty-printed JSON.
|
|
435
|
-
// 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
|
|
436
439
|
// otherwise copy "{}", which is misleading.
|
|
437
440
|
const headersText = useMemo(
|
|
438
441
|
() =>
|
|
@@ -718,16 +721,16 @@ export const LogEntry = memo(function ({
|
|
|
718
721
|
<TooltipProvider>
|
|
719
722
|
<div
|
|
720
723
|
className={cn(
|
|
721
|
-
"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]",
|
|
722
725
|
expanded
|
|
723
|
-
? "border-cyan-
|
|
724
|
-
: "
|
|
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",
|
|
725
728
|
)}
|
|
726
729
|
>
|
|
727
730
|
<div
|
|
728
731
|
className={cn(
|
|
729
732
|
"pointer-events-none absolute inset-y-3 left-1 w-px rounded-full",
|
|
730
|
-
expanded ? "bg-cyan-
|
|
733
|
+
expanded ? "bg-cyan-200/55 shadow-[0_0_20px_rgba(103,232,249,0.26)]" : "bg-white/12",
|
|
731
734
|
)}
|
|
732
735
|
aria-hidden="true"
|
|
733
736
|
/>
|
|
@@ -744,6 +747,7 @@ export const LogEntry = memo(function ({
|
|
|
744
747
|
onToggle={handleToggleExpanded}
|
|
745
748
|
cacheTrend={cacheTrend}
|
|
746
749
|
slowResponseThresholdSeconds={slowResponseThresholdSeconds}
|
|
750
|
+
showClientMetadata={showClientMetadata}
|
|
747
751
|
timeDisplayFormat={timeDisplayFormat}
|
|
748
752
|
activeTab={activeTab}
|
|
749
753
|
tabActions={tabActions}
|
|
@@ -758,18 +762,18 @@ export const LogEntry = memo(function ({
|
|
|
758
762
|
|
|
759
763
|
{expanded && (
|
|
760
764
|
<div
|
|
761
|
-
className="relative border-t border-
|
|
765
|
+
className="relative border-t border-white/10 bg-black/[0.18] pt-2"
|
|
762
766
|
onClick={(e) => e.stopPropagation()}
|
|
763
767
|
onKeyDown={(e) => e.stopPropagation()}
|
|
764
768
|
>
|
|
765
769
|
<div
|
|
766
|
-
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"
|
|
767
771
|
aria-hidden="true"
|
|
768
772
|
/>
|
|
769
773
|
<Tabs value={activeTab} onValueChange={setActiveTab} className="gap-2">
|
|
770
774
|
<TabsList
|
|
771
775
|
variant="line"
|
|
772
|
-
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"
|
|
773
777
|
>
|
|
774
778
|
{viewMode === "full" && (
|
|
775
779
|
<TabsTrigger className={TAB_TRIGGER_CLASS} value="raw-headers">
|
|
@@ -32,10 +32,9 @@ import { Badge } from "../ui/badge";
|
|
|
32
32
|
import { Button } from "../ui/button";
|
|
33
33
|
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "../ui/tooltip";
|
|
34
34
|
import type { CacheTrend } from "./cacheTrend";
|
|
35
|
-
import { ROW_CHEVRON_SLOT_CLASS } from "./ProviderLogoStack";
|
|
36
35
|
|
|
37
36
|
const STATUS_BADGE_CLASSES: Record<StatusCategory, string> = {
|
|
38
|
-
success: "bg-
|
|
37
|
+
success: "bg-slate-300/[0.08] text-slate-100 border-slate-300/20",
|
|
39
38
|
client_error: "bg-amber-300/[0.10] text-amber-100 border-amber-300/30",
|
|
40
39
|
server_error: "bg-rose-400/[0.10] text-rose-100 border-rose-300/30",
|
|
41
40
|
pending: "bg-cyan-300/[0.08] text-cyan-100 border-cyan-300/25",
|
|
@@ -46,36 +45,31 @@ const STATUS_CUE_CLASSES: Record<
|
|
|
46
45
|
{
|
|
47
46
|
frame: string;
|
|
48
47
|
dot: string;
|
|
49
|
-
rail: string;
|
|
50
48
|
}
|
|
51
49
|
> = {
|
|
52
50
|
success: {
|
|
53
|
-
frame: "border-
|
|
54
|
-
dot: "bg-
|
|
55
|
-
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)]",
|
|
56
53
|
},
|
|
57
54
|
client_error: {
|
|
58
55
|
frame: "border-amber-300/25 bg-amber-300/[0.08] text-amber-100",
|
|
59
56
|
dot: "bg-amber-200 shadow-[0_0_14px_rgba(252,211,77,0.8)]",
|
|
60
|
-
rail: "bg-amber-300/50 shadow-[0_0_22px_rgba(252,211,77,0.28)]",
|
|
61
57
|
},
|
|
62
58
|
server_error: {
|
|
63
59
|
frame: "border-rose-300/25 bg-rose-300/[0.08] text-rose-100",
|
|
64
60
|
dot: "bg-rose-300 shadow-[0_0_14px_rgba(253,164,175,0.8)]",
|
|
65
|
-
rail: "bg-rose-300/50 shadow-[0_0_22px_rgba(253,164,175,0.28)]",
|
|
66
61
|
},
|
|
67
62
|
pending: {
|
|
68
63
|
frame: "border-cyan-300/25 bg-cyan-300/[0.08] text-cyan-100",
|
|
69
64
|
dot: "bg-cyan-200 shadow-[0_0_14px_rgba(103,232,249,0.8)]",
|
|
70
|
-
rail: "bg-cyan-300/50 shadow-[0_0_22px_rgba(103,232,249,0.28)]",
|
|
71
65
|
},
|
|
72
66
|
};
|
|
73
67
|
|
|
74
68
|
const HEADER_ACTION_BUTTON_CLASS =
|
|
75
|
-
"
|
|
69
|
+
"border border-input bg-background hover:bg-accent hover:text-accent-foreground size-8 text-muted-foreground shadow-none hover:text-cyan-100";
|
|
76
70
|
|
|
77
71
|
const TELEMETRY_CHIP_CLASS =
|
|
78
|
-
"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";
|
|
79
73
|
|
|
80
74
|
function formatElapsed(ms: number): string {
|
|
81
75
|
if (ms < 1000) return `${ms}ms`;
|
|
@@ -101,7 +95,7 @@ function statusCueLabel(category: StatusCategory, status: number | null): string
|
|
|
101
95
|
}
|
|
102
96
|
|
|
103
97
|
/**
|
|
104
|
-
* Inline trend indicator: small arrow (
|
|
98
|
+
* Inline trend indicator: small arrow (up / down) plus the absolute
|
|
105
99
|
* delta in compact form. Returns `null` when there is no trend to display.
|
|
106
100
|
*/
|
|
107
101
|
function CacheTrendIndicator({ trend }: { trend: CacheTrend | null }): JSX.Element | null {
|
|
@@ -111,7 +105,7 @@ function CacheTrendIndicator({ trend }: { trend: CacheTrend | null }): JSX.Eleme
|
|
|
111
105
|
const sign = isUp ? "+" : "-";
|
|
112
106
|
return (
|
|
113
107
|
<span className="flex items-center gap-0.5 text-muted-foreground tabular-nums">
|
|
114
|
-
<Icon className={isUp ? "size-3 text-
|
|
108
|
+
<Icon className={isUp ? "size-3 text-cyan-300" : "size-3 text-rose-400"} />
|
|
115
109
|
<span className="font-mono">
|
|
116
110
|
{sign}
|
|
117
111
|
{formatTokens(trend.delta)}
|
|
@@ -122,12 +116,12 @@ function CacheTrendIndicator({ trend }: { trend: CacheTrend | null }): JSX.Eleme
|
|
|
122
116
|
|
|
123
117
|
/**
|
|
124
118
|
* Per-tab action bundle surfaced in the header when the tab is active.
|
|
125
|
-
* - `copyText === null`
|
|
126
|
-
* - `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
|
|
127
121
|
* not valid JSON). When present, the button reflects the JSON viewer's
|
|
128
122
|
* bulk-expansion state and the caller is responsible for forwarding the
|
|
129
123
|
* matching `bulkDepth` / `bulkRevision` to the underlying viewer.
|
|
130
|
-
* - `diffWithRaw` / `diffWithPrevious`
|
|
124
|
+
* - `diffWithRaw` / `diffWithPrevious` - optional Diff buttons. Undefined
|
|
131
125
|
* means "not applicable" (hidden). Today only the Request tab sets them.
|
|
132
126
|
*/
|
|
133
127
|
export type HeaderTabAction = {
|
|
@@ -181,6 +175,8 @@ export type LogEntryHeaderProps = {
|
|
|
181
175
|
onReplay?: () => void;
|
|
182
176
|
/** Slow-response threshold in seconds. `0` disables the warning indicator. */
|
|
183
177
|
slowResponseThresholdSeconds?: number;
|
|
178
|
+
/** Show client PID/project metadata on each log row. Parent process rows can disable this. */
|
|
179
|
+
showClientMetadata?: boolean;
|
|
184
180
|
/** Controls whether timestamps render as compact local time or full ISO strings. */
|
|
185
181
|
timeDisplayFormat: TimeDisplayFormat;
|
|
186
182
|
};
|
|
@@ -197,6 +193,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
197
193
|
tabActions,
|
|
198
194
|
onReplay,
|
|
199
195
|
slowResponseThresholdSeconds = 0,
|
|
196
|
+
showClientMetadata = true,
|
|
200
197
|
timeDisplayFormat,
|
|
201
198
|
}: LogEntryHeaderProps): JSX.Element {
|
|
202
199
|
const statusCategory = getStatusCategory(log.responseStatus);
|
|
@@ -233,9 +230,9 @@ export const LogEntryHeader = memo(function ({
|
|
|
233
230
|
className={cn(
|
|
234
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]",
|
|
235
232
|
expanded
|
|
236
|
-
? "bg-[
|
|
237
|
-
: "bg-
|
|
238
|
-
"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",
|
|
239
236
|
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:outline-none",
|
|
240
237
|
)}
|
|
241
238
|
onClick={onToggle}
|
|
@@ -246,25 +243,10 @@ export const LogEntryHeader = memo(function ({
|
|
|
246
243
|
}
|
|
247
244
|
}}
|
|
248
245
|
>
|
|
249
|
-
<span
|
|
250
|
-
className={cn(
|
|
251
|
-
"absolute left-0 top-2 bottom-2 w-0.5 rounded-r-full transition-colors",
|
|
252
|
-
statusCue.rail,
|
|
253
|
-
)}
|
|
254
|
-
aria-hidden="true"
|
|
255
|
-
/>
|
|
256
|
-
<span className={ROW_CHEVRON_SLOT_CLASS}>
|
|
257
|
-
{expanded ? (
|
|
258
|
-
<ChevronDown className="size-4 shrink-0" />
|
|
259
|
-
) : (
|
|
260
|
-
<ChevronRight className="size-4 shrink-0" />
|
|
261
|
-
)}
|
|
262
|
-
</span>
|
|
263
|
-
|
|
264
246
|
{/* Request ID */}
|
|
265
247
|
<span
|
|
266
248
|
className={cn(
|
|
267
|
-
"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",
|
|
268
250
|
statusCue.frame,
|
|
269
251
|
)}
|
|
270
252
|
>
|
|
@@ -275,14 +257,14 @@ export const LogEntryHeader = memo(function ({
|
|
|
275
257
|
</span>
|
|
276
258
|
|
|
277
259
|
{/* Request start time */}
|
|
278
|
-
<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">
|
|
279
261
|
<Clock className="size-3" />
|
|
280
262
|
<span className="font-mono tabular-nums" title={log.timestamp}>
|
|
281
263
|
{formatTimestamp(log.timestamp, timeDisplayFormat)}
|
|
282
264
|
</span>
|
|
283
265
|
</span>
|
|
284
266
|
|
|
285
|
-
{/* Response Status
|
|
267
|
+
{/* Response Status - only shown for non-200 or pending. Each category
|
|
286
268
|
carries a distinct icon in addition to color so the meaning is
|
|
287
269
|
legible without color perception. */}
|
|
288
270
|
<Badge
|
|
@@ -324,7 +306,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
324
306
|
)}
|
|
325
307
|
|
|
326
308
|
{/* Elapsed time. Tooltip is only shown when this log exceeded the slow-
|
|
327
|
-
response threshold
|
|
309
|
+
response threshold - for normal-speed logs the elapsed value is
|
|
328
310
|
self-explanatory, so we skip the tooltip wrapper to keep the row
|
|
329
311
|
quiet. */}
|
|
330
312
|
{log.elapsedMs !== null &&
|
|
@@ -369,7 +351,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
369
351
|
{log.streaming && tokenRateLabel !== null && (
|
|
370
352
|
<Tooltip>
|
|
371
353
|
<TooltipTrigger asChild>
|
|
372
|
-
<span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0 text-
|
|
354
|
+
<span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0 text-cyan-300")}>
|
|
373
355
|
<Gauge className="size-3" />
|
|
374
356
|
<span className="font-mono tabular-nums">{tokenRateLabel}</span>
|
|
375
357
|
</span>
|
|
@@ -389,7 +371,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
389
371
|
log.inputTokens !== null ? "text-blue-400" : "text-muted-foreground",
|
|
390
372
|
)}
|
|
391
373
|
>
|
|
392
|
-
IN {log.inputTokens !== null ? formatTokens(log.inputTokens) : "
|
|
374
|
+
IN {log.inputTokens !== null ? formatTokens(log.inputTokens) : "n/a"}
|
|
393
375
|
</span>
|
|
394
376
|
<span className="text-muted-foreground">/</span>
|
|
395
377
|
<span
|
|
@@ -398,7 +380,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
398
380
|
log.outputTokens !== null ? "text-amber-400" : "text-muted-foreground",
|
|
399
381
|
)}
|
|
400
382
|
>
|
|
401
|
-
OUT {log.outputTokens !== null ? formatTokens(log.outputTokens) : "
|
|
383
|
+
OUT {log.outputTokens !== null ? formatTokens(log.outputTokens) : "n/a"}
|
|
402
384
|
</span>
|
|
403
385
|
</span>
|
|
404
386
|
</span>
|
|
@@ -407,7 +389,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
407
389
|
{log.cacheCreationInputTokens !== null && log.cacheCreationInputTokens > 0 && (
|
|
408
390
|
<span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0")}>
|
|
409
391
|
<CacheTrendIndicator trend={cacheTrend?.creation ?? null} />
|
|
410
|
-
<span className="font-mono tabular-nums text-
|
|
392
|
+
<span className="font-mono tabular-nums text-cyan-300">
|
|
411
393
|
KV Cache +{formatTokens(log.cacheCreationInputTokens)}
|
|
412
394
|
</span>
|
|
413
395
|
</span>
|
|
@@ -451,7 +433,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
451
433
|
)}
|
|
452
434
|
|
|
453
435
|
{/* Client info (PID + project folder) */}
|
|
454
|
-
{(hasClientPid || hasClientProjectFolder) && (
|
|
436
|
+
{showClientMetadata && (hasClientPid || hasClientProjectFolder) && (
|
|
455
437
|
<Tooltip>
|
|
456
438
|
<TooltipTrigger asChild>
|
|
457
439
|
<span className={cn(TELEMETRY_CHIP_CLASS, "hidden shrink-0 text-purple-300 xl:flex")}>
|
|
@@ -484,7 +466,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
484
466
|
{/* Spacer */}
|
|
485
467
|
<span className="flex-1 min-w-0" />
|
|
486
468
|
|
|
487
|
-
{/* Header actions
|
|
469
|
+
{/* Header actions - only when expanded, so the collapsed view stays
|
|
488
470
|
compact. Buttons stop propagation so they don't toggle the log. */}
|
|
489
471
|
{expanded && (
|
|
490
472
|
<span
|
|
@@ -583,7 +565,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
583
565
|
aria-label={action.copyCopied ? "Copied" : action.copyLabel}
|
|
584
566
|
>
|
|
585
567
|
{action.copyCopied ? (
|
|
586
|
-
<Check className="size-3.5 text-
|
|
568
|
+
<Check className="size-3.5 text-cyan-300" />
|
|
587
569
|
) : (
|
|
588
570
|
<Copy className="size-3.5" />
|
|
589
571
|
)}
|
|
@@ -615,6 +597,17 @@ export const LogEntryHeader = memo(function ({
|
|
|
615
597
|
)}
|
|
616
598
|
</span>
|
|
617
599
|
)}
|
|
600
|
+
|
|
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
|
);
|
|
@@ -39,7 +39,7 @@ export function ProviderLogoStack({
|
|
|
39
39
|
<TooltipTrigger asChild>
|
|
40
40
|
<span
|
|
41
41
|
className={cn(
|
|
42
|
-
"inline-flex size-6 shrink-0 items-center justify-center rounded-md
|
|
42
|
+
"border border-border bg-muted/40 inline-flex size-6 shrink-0 items-center justify-center rounded-md",
|
|
43
43
|
index > 0 && "-ml-2",
|
|
44
44
|
)}
|
|
45
45
|
>
|
|
@@ -50,7 +50,7 @@ export function ProviderLogoStack({
|
|
|
50
50
|
</Tooltip>
|
|
51
51
|
))}
|
|
52
52
|
{overflowCount > 0 && (
|
|
53
|
-
<span className="-ml-2 inline-flex h-6 min-w-6 shrink-0 items-center justify-center rounded-md
|
|
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
54
|
+{overflowCount}
|
|
55
55
|
</span>
|
|
56
56
|
)}
|