@tonyclaw/agent-inspector 3.0.23 → 3.0.25
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-CFedvZHU.js → CompareDrawer-BnvOPwpe.js} +1 -1
- package/.output/public/assets/ProxyViewerContainer-DcqFqo1m.js +127 -0
- package/.output/public/assets/{ReplayDialog-X-fRSKAu.js → ReplayDialog-BJgoQWdW.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-S8qB5nVg.js → RequestAnatomy-CpBZEHnM.js} +1 -1
- package/.output/public/assets/{ResponseView-_rcOWSwJ.js → ResponseView-CWmyRb2M.js} +2 -2
- package/.output/public/assets/{StreamingChunkSequence-Bc15AVpN.js → StreamingChunkSequence-BWhC8bwx.js} +1 -1
- package/.output/public/assets/{_sessionId-Cz-HW2Wf.js → _sessionId-Cq-2OqUU.js} +1 -1
- package/.output/public/assets/{index-CEgwWY3E.js → index-BKi9IRtV.js} +71 -7
- package/.output/public/assets/index-BMYuDL5X.css +1 -0
- package/.output/public/assets/{index-CXYBU0j8.js → index-Cf8O0Oos.js} +1 -1
- package/.output/public/assets/{index-ClKXooQd.js → index-DdgTJ4QJ.js} +1 -1
- package/.output/public/assets/{json-viewer-QfU4qGdd.js → json-viewer-Bqu_4hmm.js} +1 -1
- package/.output/server/_libs/lucide-react.mjs +306 -246
- package/.output/server/{_sessionId-9FAmYZVf.mjs → _sessionId-Ba7H3Gjw.mjs} +2 -2
- package/.output/server/_ssr/{CompareDrawer-BLKjOzwD.mjs → CompareDrawer-aH59Ab6x.mjs} +3 -3
- package/.output/server/_ssr/{ProxyViewerContainer-Dcs2Mq8l.mjs → ProxyViewerContainer-By-I0tJv.mjs} +1791 -1259
- package/.output/server/_ssr/{ReplayDialog-oyiF4LIo.mjs → ReplayDialog-BO4Vy0et.mjs} +4 -4
- package/.output/server/_ssr/{RequestAnatomy-DkyQXSuj.mjs → RequestAnatomy-FtOr7vSS.mjs} +3 -3
- package/.output/server/_ssr/{ResponseView-3NTDEiDh.mjs → ResponseView-cvw6ajue.mjs} +3 -3
- package/.output/server/_ssr/{StreamingChunkSequence-DRq-5Bgn.mjs → StreamingChunkSequence-Oc8S2kD6.mjs} +3 -3
- package/.output/server/_ssr/{index-dtMiNUa1.mjs → index-BDRlQheR.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-BhUwicvp.mjs → json-viewer-Cd6trhQr.mjs} +3 -3
- package/.output/server/_ssr/{router-DO0OntUK.mjs → router-bofP-Ekl.mjs} +296 -91
- package/.output/server/{_tanstack-start-manifest_v-DO5v3BHY.mjs → _tanstack-start-manifest_v-BPuf86qB.mjs} +1 -1
- package/.output/server/index.mjs +76 -76
- package/package.json +3 -2
- package/src/components/ProxyViewer.tsx +138 -740
- package/src/components/ProxyViewerContainer.tsx +5 -47
- package/src/components/alerts/AlertsDialog.tsx +6 -2
- package/src/components/inspector-pet/InspectorPet.tsx +390 -0
- package/src/components/inspector-pet/inspectorPetLogic.ts +273 -0
- package/src/components/providers/ProviderCard.tsx +20 -579
- package/src/components/providers/ProviderTestResultsView.tsx +732 -0
- package/src/components/proxy-viewer/LogEntry.tsx +5 -0
- package/src/components/proxy-viewer/LogEntryHeader.tsx +7 -0
- package/src/components/proxy-viewer/ToolTraceEvents.tsx +33 -36
- package/src/components/proxy-viewer/TurnGroup.tsx +1 -5
- package/src/components/proxy-viewer/proxyViewerContainerLogic.ts +46 -0
- package/src/components/proxy-viewer/proxyViewerLogic.ts +469 -0
- package/src/lib/themeMode.ts +45 -0
- package/src/lib/uiScale.ts +112 -0
- package/src/lib/useThemeMode.ts +40 -0
- package/src/lib/useUiScale.ts +52 -0
- package/src/routes/__root.tsx +78 -1
- package/styles/globals.css +403 -24
- package/.output/public/assets/ProxyViewerContainer-BlCrBvdm.js +0 -127
- package/.output/public/assets/index-B_SIQ47i.css +0 -1
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type ChangeEvent,
|
|
3
|
-
type CSSProperties,
|
|
4
3
|
type JSX,
|
|
5
|
-
type PointerEvent as ReactPointerEvent,
|
|
6
4
|
type ReactNode,
|
|
7
5
|
useCallback,
|
|
8
6
|
useEffect,
|
|
@@ -12,6 +10,7 @@ import {
|
|
|
12
10
|
Suspense,
|
|
13
11
|
} from "react";
|
|
14
12
|
import {
|
|
13
|
+
ALargeSmall,
|
|
15
14
|
ArrowDownRight,
|
|
16
15
|
ArrowLeft,
|
|
17
16
|
ArrowUpRight,
|
|
@@ -25,6 +24,7 @@ import {
|
|
|
25
24
|
ChevronsRight,
|
|
26
25
|
Copy,
|
|
27
26
|
Download,
|
|
27
|
+
Eye,
|
|
28
28
|
FileJson,
|
|
29
29
|
Gauge,
|
|
30
30
|
Flag,
|
|
@@ -34,6 +34,8 @@ import {
|
|
|
34
34
|
RefreshCw,
|
|
35
35
|
Search,
|
|
36
36
|
Siren,
|
|
37
|
+
Moon,
|
|
38
|
+
Sun,
|
|
37
39
|
Timer,
|
|
38
40
|
Trash2,
|
|
39
41
|
Upload,
|
|
@@ -47,11 +49,23 @@ import { copyTextToClipboard } from "../lib/clipboard";
|
|
|
47
49
|
import type { InspectorGroupEvidence, InspectorGroupMember } from "../lib/groupContract";
|
|
48
50
|
import type { CaptureMode, TimeDisplayFormat } from "../lib/runtimeConfig";
|
|
49
51
|
import { formatTimestampRange } from "../lib/timeDisplay";
|
|
50
|
-
import { extractStopReason, type StopReason } from "../lib/stopReason";
|
|
51
52
|
import { cn, formatContextWindowTokens, formatTokens } from "../lib/utils";
|
|
53
|
+
import { useThemeMode } from "../lib/useThemeMode";
|
|
54
|
+
import { formatUiScale, normalizeUiScalePreference, type UiScalePreference } from "../lib/uiScale";
|
|
55
|
+
import { useUiScale } from "../lib/useUiScale";
|
|
52
56
|
import { useProviders } from "../lib/useProviders";
|
|
53
57
|
import packageJson from "../../package.json";
|
|
54
58
|
import { groupLogsByConversation, type ConversationGroupData } from "./proxy-viewer";
|
|
59
|
+
import {
|
|
60
|
+
buildSessionSlateStats,
|
|
61
|
+
formatPaginationStatus,
|
|
62
|
+
getExportActionVisibility,
|
|
63
|
+
resolveSessionContextScope,
|
|
64
|
+
type SessionContextScope,
|
|
65
|
+
type SessionSlateMetricTone,
|
|
66
|
+
type SessionSlateStats,
|
|
67
|
+
type SessionSlateStatus,
|
|
68
|
+
} from "./proxy-viewer/proxyViewerLogic";
|
|
55
69
|
|
|
56
70
|
import { CrabLogo } from "./ui/crab-logo";
|
|
57
71
|
import { crabVariants } from "./ui/crab-variants";
|
|
@@ -65,12 +79,13 @@ import { AlertsDialog } from "./alerts/AlertsDialog";
|
|
|
65
79
|
import { GroupsDialog } from "./groups/GroupsDialog";
|
|
66
80
|
import { SettingsDialog } from "./providers/SettingsDialog";
|
|
67
81
|
import { AgentLabDialog } from "./ecosystem/AgentLabDialog";
|
|
82
|
+
import { InspectorPet } from "./inspector-pet/InspectorPet";
|
|
68
83
|
import { PiAgentPanel } from "./pi-agent/PiAgentPanel";
|
|
69
84
|
import { computeCacheTrends } from "./proxy-viewer/cacheTrend";
|
|
70
85
|
import { LazyCompareDrawer } from "./proxy-viewer/lazy";
|
|
71
86
|
import { dispatchLogFocusRequest } from "./proxy-viewer/logFocus";
|
|
72
87
|
import { ConversationGroupList } from "./proxy-viewer/ConversationGroupList";
|
|
73
|
-
import {
|
|
88
|
+
import { buildValidPredecessors } from "./proxy-viewer/viewerState";
|
|
74
89
|
import { useKeyboardNavigation } from "./proxy-viewer/useKeyboardNavigation";
|
|
75
90
|
import { SegmentBar } from "./proxy-viewer/anatomy/SegmentBar";
|
|
76
91
|
import type {
|
|
@@ -107,26 +122,6 @@ function displayNumberForLog(
|
|
|
107
122
|
return displayNumberByLogId.get(log.id) ?? log.id;
|
|
108
123
|
}
|
|
109
124
|
|
|
110
|
-
export function shouldShowRawExport(captureMode: CaptureMode): boolean {
|
|
111
|
-
return captureMode === "full";
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export type ExportActionVisibility = {
|
|
115
|
-
redacted: boolean;
|
|
116
|
-
raw: boolean;
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
export function getExportActionVisibility(
|
|
120
|
-
captureMode: CaptureMode,
|
|
121
|
-
logCount: number,
|
|
122
|
-
): ExportActionVisibility {
|
|
123
|
-
const hasLogs = logCount > 0;
|
|
124
|
-
return {
|
|
125
|
-
redacted: hasLogs,
|
|
126
|
-
raw: hasLogs && shouldShowRawExport(captureMode),
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
125
|
function computeTokenSummary(logs: CapturedLog[]): { totalIn: number; totalOut: number } {
|
|
131
126
|
let totalIn = 0;
|
|
132
127
|
let totalOut = 0;
|
|
@@ -137,12 +132,6 @@ function computeTokenSummary(logs: CapturedLog[]): { totalIn: number; totalOut:
|
|
|
137
132
|
return { totalIn, totalOut };
|
|
138
133
|
}
|
|
139
134
|
|
|
140
|
-
export type SessionContextScope = {
|
|
141
|
-
label: string;
|
|
142
|
-
logs: CapturedLog[];
|
|
143
|
-
showBackLink: boolean;
|
|
144
|
-
};
|
|
145
|
-
|
|
146
135
|
export type SessionMembershipEvidence = {
|
|
147
136
|
groupId: string;
|
|
148
137
|
groupTitle: string;
|
|
@@ -177,13 +166,6 @@ export type SessionLoadProgress = {
|
|
|
177
166
|
loadedPages: number;
|
|
178
167
|
};
|
|
179
168
|
|
|
180
|
-
function formatTimeRange(logs: CapturedLog[], timeDisplayFormat: TimeDisplayFormat): string | null {
|
|
181
|
-
const first = logs[0];
|
|
182
|
-
const last = logs[logs.length - 1];
|
|
183
|
-
if (first === undefined || last === undefined) return null;
|
|
184
|
-
return formatTimestampRange(first.timestamp, last.timestamp, timeDisplayFormat);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
169
|
function getFirstUserAgent(logs: CapturedLog[]): string | null {
|
|
188
170
|
for (const log of logs) {
|
|
189
171
|
if (log.userAgent !== null && log.userAgent !== undefined && log.userAgent !== "") {
|
|
@@ -193,366 +175,11 @@ function getFirstUserAgent(logs: CapturedLog[]): string | null {
|
|
|
193
175
|
return null;
|
|
194
176
|
}
|
|
195
177
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
type SessionSlateStats = {
|
|
204
|
-
status: SessionSlateStatus;
|
|
205
|
-
statusLabel: string;
|
|
206
|
-
modelLabel: string;
|
|
207
|
-
providerLabel: string;
|
|
208
|
-
requests: number;
|
|
209
|
-
turns: number;
|
|
210
|
-
failures: number;
|
|
211
|
-
pending: number;
|
|
212
|
-
slow: number;
|
|
213
|
-
lengthStops: number;
|
|
214
|
-
timeRange: string | null;
|
|
215
|
-
tokenLabel: string;
|
|
216
|
-
finishLabel: string;
|
|
217
|
-
finishDetail: string;
|
|
218
|
-
finishTone: SessionSlateMetricTone;
|
|
219
|
-
outcomeLabel: string;
|
|
220
|
-
outcomeDetail: string;
|
|
221
|
-
outcomeTone: SessionSlateMetricTone;
|
|
222
|
-
slowestLabel: string;
|
|
223
|
-
slowestDetail: string;
|
|
224
|
-
heaviestDetail: string;
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
function hasLogFailure(log: CapturedLog): boolean {
|
|
228
|
-
if (log.error !== null && log.error !== undefined && log.error.length > 0) return true;
|
|
229
|
-
return log.responseStatus !== null && log.responseStatus >= 400;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function getModelLabel(logs: readonly CapturedLog[]): string {
|
|
233
|
-
const models = new Set<string>();
|
|
234
|
-
for (const log of logs) {
|
|
235
|
-
if (log.model !== null && log.model.length > 0) {
|
|
236
|
-
models.add(log.model);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
const sorted = [...models].sort();
|
|
240
|
-
if (sorted.length === 0) return "Model unknown";
|
|
241
|
-
if (sorted.length === 1) return sorted[0] ?? "Model unknown";
|
|
242
|
-
return `${sorted.length} models`;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function getProviderLabel(logs: readonly CapturedLog[]): string {
|
|
246
|
-
const providers = new Set<string>();
|
|
247
|
-
for (const log of logs) {
|
|
248
|
-
if (
|
|
249
|
-
log.providerName !== null &&
|
|
250
|
-
log.providerName !== undefined &&
|
|
251
|
-
log.providerName.length > 0
|
|
252
|
-
) {
|
|
253
|
-
providers.add(log.providerName);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
const sorted = [...providers].sort();
|
|
257
|
-
if (sorted.length === 0) return "Provider unknown";
|
|
258
|
-
if (sorted.length === 1) return sorted[0] ?? "Provider unknown";
|
|
259
|
-
return `${sorted.length} providers`;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
function formatElapsedMs(ms: number): string {
|
|
263
|
-
if (ms < 1000) return `${ms}ms`;
|
|
264
|
-
if (ms < 60_000) {
|
|
265
|
-
const seconds = ms / 1000;
|
|
266
|
-
return `${seconds < 10 ? seconds.toFixed(1) : seconds.toFixed(0)}s`;
|
|
267
|
-
}
|
|
268
|
-
const totalSeconds = Math.round(ms / 1000);
|
|
269
|
-
const minutes = Math.floor(totalSeconds / 60);
|
|
270
|
-
const seconds = totalSeconds % 60;
|
|
271
|
-
return `${minutes}m ${seconds}s`;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
function formatTokenPair(inputTokens: number, outputTokens: number): string {
|
|
275
|
-
return `${formatTokens(inputTokens)} in / ${formatTokens(outputTokens)} out`;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
function countSessionTurns(
|
|
279
|
-
logs: CapturedLog[],
|
|
280
|
-
conversationGroups: readonly ConversationLogs[],
|
|
281
|
-
): number {
|
|
282
|
-
if (logs.length === 0) return 0;
|
|
283
|
-
if (conversationGroups.length === 0) return buildTurnGroups(logs).length;
|
|
284
|
-
|
|
285
|
-
let turns = 0;
|
|
286
|
-
for (const group of conversationGroups) {
|
|
287
|
-
turns += buildTurnGroups(group.logs).length;
|
|
288
|
-
}
|
|
289
|
-
return turns;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
type FinishSummary = {
|
|
293
|
-
label: string;
|
|
294
|
-
detail: string;
|
|
295
|
-
tone: SessionSlateMetricTone;
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
type SessionOutcomeSummary = {
|
|
299
|
-
label: string;
|
|
300
|
-
detail: string;
|
|
301
|
-
tone: SessionSlateMetricTone;
|
|
302
|
-
};
|
|
303
|
-
|
|
304
|
-
type FailureSummary = {
|
|
305
|
-
logNumber: number;
|
|
306
|
-
status: number | null;
|
|
307
|
-
error: string | null;
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
function summarizeFinishReason({
|
|
311
|
-
stopReason,
|
|
312
|
-
pending,
|
|
313
|
-
lastFailureLogNumber,
|
|
314
|
-
}: {
|
|
315
|
-
stopReason: StopReason;
|
|
316
|
-
pending: number;
|
|
317
|
-
lastFailureLogNumber: number | null;
|
|
318
|
-
}): FinishSummary {
|
|
319
|
-
if (pending > 0) {
|
|
320
|
-
return {
|
|
321
|
-
label: "In flight",
|
|
322
|
-
detail: `${pending} request${pending === 1 ? "" : "s"} pending`,
|
|
323
|
-
tone: "cool",
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
if (lastFailureLogNumber !== null && stopReason === null) {
|
|
328
|
-
return {
|
|
329
|
-
label: "Error",
|
|
330
|
-
detail: `Last failed request #${lastFailureLogNumber}`,
|
|
331
|
-
tone: "danger",
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
switch (stopReason) {
|
|
336
|
-
case "end_turn":
|
|
337
|
-
return { label: "End turn", detail: "Assistant completed the turn", tone: "neutral" };
|
|
338
|
-
case "stop":
|
|
339
|
-
return { label: "Stop", detail: "Model reached a stop condition", tone: "neutral" };
|
|
340
|
-
case "length":
|
|
341
|
-
return { label: "Output limit", detail: "Turn ended at max output tokens", tone: "watch" };
|
|
342
|
-
case "tool_use":
|
|
343
|
-
return { label: "Tool call", detail: "Assistant requested a tool", tone: "cool" };
|
|
344
|
-
case null:
|
|
345
|
-
return { label: "Unknown", detail: "No finish reason captured", tone: "neutral" };
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
function isClientAbortError(error: string | null): boolean {
|
|
350
|
-
if (error === null) return false;
|
|
351
|
-
const lower = error.toLowerCase();
|
|
352
|
-
return (
|
|
353
|
-
lower.includes("abort") ||
|
|
354
|
-
lower.includes("cancel") ||
|
|
355
|
-
lower.includes("client disconnected") ||
|
|
356
|
-
lower.includes("stream disconnected") ||
|
|
357
|
-
lower.includes("connection closed")
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
function summarizeSessionOutcome({
|
|
362
|
-
pending,
|
|
363
|
-
failures,
|
|
364
|
-
lengthStops,
|
|
365
|
-
stopReason,
|
|
366
|
-
lastFailure,
|
|
367
|
-
}: {
|
|
368
|
-
pending: number;
|
|
369
|
-
failures: number;
|
|
370
|
-
lengthStops: number;
|
|
371
|
-
stopReason: StopReason;
|
|
372
|
-
lastFailure: FailureSummary | null;
|
|
373
|
-
}): SessionOutcomeSummary {
|
|
374
|
-
if (
|
|
375
|
-
lastFailure !== null &&
|
|
376
|
-
(lastFailure.status === 499 || isClientAbortError(lastFailure.error))
|
|
377
|
-
) {
|
|
378
|
-
return {
|
|
379
|
-
label: "Client aborted",
|
|
380
|
-
detail: `Request #${lastFailure.logNumber} ended before completion`,
|
|
381
|
-
tone: "watch",
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
if (pending > 0) {
|
|
386
|
-
return {
|
|
387
|
-
label: "In flight",
|
|
388
|
-
detail: `${pending} request${pending === 1 ? "" : "s"} still running`,
|
|
389
|
-
tone: "cool",
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
if (lastFailure !== null && lastFailure.status !== null && lastFailure.status >= 500) {
|
|
394
|
-
return {
|
|
395
|
-
label: "Provider error",
|
|
396
|
-
detail: `Request #${lastFailure.logNumber} returned HTTP ${lastFailure.status}`,
|
|
397
|
-
tone: "danger",
|
|
398
|
-
};
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
if (failures > 0 && lastFailure !== null) {
|
|
402
|
-
return {
|
|
403
|
-
label: "Failed",
|
|
404
|
-
detail: `Request #${lastFailure.logNumber} needs attention`,
|
|
405
|
-
tone: "danger",
|
|
406
|
-
};
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
if (lengthStops > 0 || stopReason === "length") {
|
|
410
|
-
return {
|
|
411
|
-
label: "Output limited",
|
|
412
|
-
detail: "Model stopped at the output token limit",
|
|
413
|
-
tone: "watch",
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
switch (stopReason) {
|
|
418
|
-
case "end_turn":
|
|
419
|
-
case "stop":
|
|
420
|
-
return { label: "Completed", detail: "Assistant completed normally", tone: "neutral" };
|
|
421
|
-
case "tool_use":
|
|
422
|
-
return { label: "Tool requested", detail: "Assistant handed off to a tool", tone: "cool" };
|
|
423
|
-
case null:
|
|
424
|
-
return { label: "Unknown", detail: "No terminal signal captured", tone: "neutral" };
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
export function buildSessionSlateStats({
|
|
429
|
-
logs,
|
|
430
|
-
conversationGroups,
|
|
431
|
-
isLoading,
|
|
432
|
-
timeDisplayFormat,
|
|
433
|
-
slowResponseThresholdSeconds,
|
|
434
|
-
}: {
|
|
435
|
-
logs: CapturedLog[];
|
|
436
|
-
conversationGroups: readonly ConversationLogs[];
|
|
437
|
-
isLoading: boolean;
|
|
438
|
-
timeDisplayFormat: TimeDisplayFormat;
|
|
439
|
-
slowResponseThresholdSeconds: number;
|
|
440
|
-
}): SessionSlateStats {
|
|
441
|
-
let failures = 0;
|
|
442
|
-
let pending = 0;
|
|
443
|
-
let slow = 0;
|
|
444
|
-
let lengthStops = 0;
|
|
445
|
-
let totalIn = 0;
|
|
446
|
-
let totalOut = 0;
|
|
447
|
-
let terminalStopReason: StopReason = null;
|
|
448
|
-
let lastFailureLogNumber: number | null = null;
|
|
449
|
-
let lastFailure: FailureSummary | null = null;
|
|
450
|
-
let slowestMs: number | null = null;
|
|
451
|
-
let slowestLogNumber: number | null = null;
|
|
452
|
-
let heaviestTokens = 0;
|
|
453
|
-
let heaviestInputTokens = 0;
|
|
454
|
-
let heaviestOutputTokens = 0;
|
|
455
|
-
let heaviestLogNumber: number | null = null;
|
|
456
|
-
const slowThresholdMs = slowResponseThresholdSeconds * 1000;
|
|
457
|
-
|
|
458
|
-
for (let index = 0; index < logs.length; index += 1) {
|
|
459
|
-
const log = logs[index];
|
|
460
|
-
if (log === undefined) continue;
|
|
461
|
-
const logNumber = index + 1;
|
|
462
|
-
const stopReason = extractStopReason(log);
|
|
463
|
-
if (stopReason !== null) terminalStopReason = stopReason;
|
|
464
|
-
if (stopReason === "length") lengthStops += 1;
|
|
465
|
-
|
|
466
|
-
if (hasLogFailure(log)) {
|
|
467
|
-
failures += 1;
|
|
468
|
-
lastFailureLogNumber = logNumber;
|
|
469
|
-
lastFailure = {
|
|
470
|
-
logNumber,
|
|
471
|
-
status: log.responseStatus,
|
|
472
|
-
error: log.error ?? null,
|
|
473
|
-
};
|
|
474
|
-
}
|
|
475
|
-
if (log.responseStatus === null) pending += 1;
|
|
476
|
-
if (
|
|
477
|
-
slowResponseThresholdSeconds > 0 &&
|
|
478
|
-
log.elapsedMs !== null &&
|
|
479
|
-
log.elapsedMs >= slowThresholdMs
|
|
480
|
-
) {
|
|
481
|
-
slow += 1;
|
|
482
|
-
}
|
|
483
|
-
if (log.inputTokens !== null) totalIn += log.inputTokens;
|
|
484
|
-
if (log.outputTokens !== null) totalOut += log.outputTokens;
|
|
485
|
-
if (log.elapsedMs !== null && (slowestMs === null || log.elapsedMs > slowestMs)) {
|
|
486
|
-
slowestMs = log.elapsedMs;
|
|
487
|
-
slowestLogNumber = logNumber;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
const inputTokens = log.inputTokens ?? 0;
|
|
491
|
-
const outputTokens = log.outputTokens ?? 0;
|
|
492
|
-
const requestTokens = inputTokens + outputTokens;
|
|
493
|
-
if (requestTokens > heaviestTokens) {
|
|
494
|
-
heaviestTokens = requestTokens;
|
|
495
|
-
heaviestInputTokens = inputTokens;
|
|
496
|
-
heaviestOutputTokens = outputTokens;
|
|
497
|
-
heaviestLogNumber = logNumber;
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
const status: SessionSlateStatus =
|
|
502
|
-
isLoading || (pending > 0 && failures === 0)
|
|
503
|
-
? "rolling"
|
|
504
|
-
: failures > 0
|
|
505
|
-
? "failed"
|
|
506
|
-
: slow > 0 || lengthStops > 0
|
|
507
|
-
? "watch"
|
|
508
|
-
: "clear";
|
|
509
|
-
const statusLabel =
|
|
510
|
-
status === "rolling"
|
|
511
|
-
? "Rolling"
|
|
512
|
-
: status === "failed"
|
|
513
|
-
? "Failure"
|
|
514
|
-
: status === "watch"
|
|
515
|
-
? "Watch"
|
|
516
|
-
: "Clear";
|
|
517
|
-
const finish = summarizeFinishReason({
|
|
518
|
-
stopReason: terminalStopReason,
|
|
519
|
-
pending,
|
|
520
|
-
lastFailureLogNumber,
|
|
521
|
-
});
|
|
522
|
-
const outcome = summarizeSessionOutcome({
|
|
523
|
-
pending,
|
|
524
|
-
failures,
|
|
525
|
-
lengthStops,
|
|
526
|
-
stopReason: terminalStopReason,
|
|
527
|
-
lastFailure,
|
|
528
|
-
});
|
|
529
|
-
|
|
530
|
-
return {
|
|
531
|
-
status,
|
|
532
|
-
statusLabel,
|
|
533
|
-
modelLabel: getModelLabel(logs),
|
|
534
|
-
providerLabel: getProviderLabel(logs),
|
|
535
|
-
requests: logs.length,
|
|
536
|
-
turns: countSessionTurns(logs, conversationGroups),
|
|
537
|
-
failures,
|
|
538
|
-
pending,
|
|
539
|
-
slow,
|
|
540
|
-
lengthStops,
|
|
541
|
-
timeRange: formatTimeRange(logs, timeDisplayFormat),
|
|
542
|
-
tokenLabel: `${formatTokens(totalIn)} in / ${formatTokens(totalOut)} out`,
|
|
543
|
-
finishLabel: finish.label,
|
|
544
|
-
finishDetail: finish.detail,
|
|
545
|
-
finishTone: finish.tone,
|
|
546
|
-
outcomeLabel: outcome.label,
|
|
547
|
-
outcomeDetail: outcome.detail,
|
|
548
|
-
outcomeTone: outcome.tone,
|
|
549
|
-
slowestLabel: slowestMs === null ? "n/a" : formatElapsedMs(slowestMs),
|
|
550
|
-
slowestDetail: slowestLogNumber === null ? "Latency pending" : `Request #${slowestLogNumber}`,
|
|
551
|
-
heaviestDetail:
|
|
552
|
-
heaviestLogNumber === null
|
|
553
|
-
? "Tokens pending"
|
|
554
|
-
: `#${heaviestLogNumber} ${formatTokenPair(heaviestInputTokens, heaviestOutputTokens)}`,
|
|
555
|
-
};
|
|
178
|
+
function formatTimeRange(logs: CapturedLog[], timeDisplayFormat: TimeDisplayFormat): string | null {
|
|
179
|
+
const first = logs[0];
|
|
180
|
+
const last = logs[logs.length - 1];
|
|
181
|
+
if (first === undefined || last === undefined) return null;
|
|
182
|
+
return formatTimestampRange(first.timestamp, last.timestamp, timeDisplayFormat);
|
|
556
183
|
}
|
|
557
184
|
|
|
558
185
|
function slateMetricToneClass(tone: SessionSlateMetricTone): string {
|
|
@@ -681,46 +308,6 @@ function SessionOpeningSlate({ stats }: { stats: SessionSlateStats }): JSX.Eleme
|
|
|
681
308
|
);
|
|
682
309
|
}
|
|
683
310
|
|
|
684
|
-
export function resolveSessionContextScope({
|
|
685
|
-
pinnedSessionId,
|
|
686
|
-
selectedSession,
|
|
687
|
-
sessions,
|
|
688
|
-
logs,
|
|
689
|
-
}: {
|
|
690
|
-
pinnedSessionId: string | undefined;
|
|
691
|
-
selectedSession: string;
|
|
692
|
-
sessions: readonly string[];
|
|
693
|
-
logs: readonly CapturedLog[];
|
|
694
|
-
}): SessionContextScope | null {
|
|
695
|
-
if (pinnedSessionId !== undefined) {
|
|
696
|
-
const scopedLogs = logs.filter((log) => log.sessionId === pinnedSessionId);
|
|
697
|
-
if (scopedLogs.length === 0) return null;
|
|
698
|
-
return { label: pinnedSessionId, logs: scopedLogs, showBackLink: true };
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
if (selectedSession !== "__all__") {
|
|
702
|
-
const scopedLogs = logs.filter((log) => log.sessionId === selectedSession);
|
|
703
|
-
if (scopedLogs.length === 0) return null;
|
|
704
|
-
return { label: selectedSession, logs: scopedLogs, showBackLink: true };
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
const singleSessionId = sessions.length === 1 ? (sessions[0] ?? null) : null;
|
|
708
|
-
if (singleSessionId !== null) {
|
|
709
|
-
const allVisibleLogsBelongToSession = logs.every((log) => log.sessionId === singleSessionId);
|
|
710
|
-
if (allVisibleLogsBelongToSession) {
|
|
711
|
-
return { label: singleSessionId, logs: [...logs], showBackLink: false };
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
const allVisibleLogsAreSessionless =
|
|
716
|
-
sessions.length === 0 && logs.length > 0 && logs.every((log) => log.sessionId === null);
|
|
717
|
-
if (allVisibleLogsAreSessionless) {
|
|
718
|
-
return { label: "Visible requests", logs: [...logs], showBackLink: false };
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
return null;
|
|
722
|
-
}
|
|
723
|
-
|
|
724
311
|
function formatPercent(value: number | null): string {
|
|
725
312
|
if (value === null) return "Unknown";
|
|
726
313
|
const percent = value * 100;
|
|
@@ -969,29 +556,6 @@ function SessionMembershipPanel({
|
|
|
969
556
|
);
|
|
970
557
|
}
|
|
971
558
|
|
|
972
|
-
export function formatPaginationStatus({
|
|
973
|
-
logs,
|
|
974
|
-
pagination,
|
|
975
|
-
}: {
|
|
976
|
-
logs: readonly CapturedLog[];
|
|
977
|
-
pagination: LogPaginationControls;
|
|
978
|
-
}): string {
|
|
979
|
-
if (pagination.isLoading && logs.length === 0) return "Loading logs...";
|
|
980
|
-
if (logs.length === 0) return "No logs on this page";
|
|
981
|
-
|
|
982
|
-
const first = logs[0];
|
|
983
|
-
const last = logs[logs.length - 1];
|
|
984
|
-
const range =
|
|
985
|
-
first === undefined || last === undefined ? "Current page" : `#${first.id}-#${last.id}`;
|
|
986
|
-
if (pagination.total === null) {
|
|
987
|
-
return `Showing ${logs.length} logs (${range})`;
|
|
988
|
-
}
|
|
989
|
-
if (pagination.total <= logs.length) {
|
|
990
|
-
return `Showing all ${pagination.total} logs`;
|
|
991
|
-
}
|
|
992
|
-
return `Showing ${logs.length} of ${pagination.total} logs (${range})`;
|
|
993
|
-
}
|
|
994
|
-
|
|
995
559
|
function LogPaginationButton({
|
|
996
560
|
label,
|
|
997
561
|
title,
|
|
@@ -1317,16 +881,109 @@ function CrabFamily({
|
|
|
1317
881
|
);
|
|
1318
882
|
}
|
|
1319
883
|
|
|
884
|
+
function ThemeModeToggle(): JSX.Element {
|
|
885
|
+
const { themeMode, setThemeMode } = useThemeMode();
|
|
886
|
+
|
|
887
|
+
return (
|
|
888
|
+
<div
|
|
889
|
+
className="border-border/70 bg-muted/70 inline-flex h-8 items-center rounded-md border p-0.5 shadow-sm"
|
|
890
|
+
aria-label="Theme mode"
|
|
891
|
+
role="group"
|
|
892
|
+
>
|
|
893
|
+
<button
|
|
894
|
+
type="button"
|
|
895
|
+
onClick={() => setThemeMode("light")}
|
|
896
|
+
className={cn(
|
|
897
|
+
"inline-flex size-7 items-center justify-center rounded-[6px] text-muted-foreground transition-colors hover:text-foreground",
|
|
898
|
+
themeMode === "light" && "bg-background text-foreground shadow-sm",
|
|
899
|
+
)}
|
|
900
|
+
aria-label="Use light mode"
|
|
901
|
+
aria-pressed={themeMode === "light"}
|
|
902
|
+
title="Light mode"
|
|
903
|
+
>
|
|
904
|
+
<Sun className="size-3.5" />
|
|
905
|
+
</button>
|
|
906
|
+
<button
|
|
907
|
+
type="button"
|
|
908
|
+
onClick={() => setThemeMode("eye-care")}
|
|
909
|
+
className={cn(
|
|
910
|
+
"inline-flex size-7 items-center justify-center rounded-[6px] text-muted-foreground transition-colors hover:text-foreground",
|
|
911
|
+
themeMode === "eye-care" && "bg-background text-foreground shadow-sm",
|
|
912
|
+
)}
|
|
913
|
+
aria-label="Use eye care mode"
|
|
914
|
+
aria-pressed={themeMode === "eye-care"}
|
|
915
|
+
title="Eye care mode"
|
|
916
|
+
>
|
|
917
|
+
<Eye className="size-3.5" />
|
|
918
|
+
</button>
|
|
919
|
+
<button
|
|
920
|
+
type="button"
|
|
921
|
+
onClick={() => setThemeMode("dark")}
|
|
922
|
+
className={cn(
|
|
923
|
+
"inline-flex size-7 items-center justify-center rounded-[6px] text-muted-foreground transition-colors hover:text-foreground",
|
|
924
|
+
themeMode === "dark" && "bg-background text-foreground shadow-sm",
|
|
925
|
+
)}
|
|
926
|
+
aria-label="Use dark mode"
|
|
927
|
+
aria-pressed={themeMode === "dark"}
|
|
928
|
+
title="Dark mode"
|
|
929
|
+
>
|
|
930
|
+
<Moon className="size-3.5" />
|
|
931
|
+
</button>
|
|
932
|
+
</div>
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
function UiScaleControl({
|
|
937
|
+
preference,
|
|
938
|
+
resolvedScale,
|
|
939
|
+
onChange,
|
|
940
|
+
}: {
|
|
941
|
+
preference: UiScalePreference;
|
|
942
|
+
resolvedScale: number;
|
|
943
|
+
onChange: (preference: UiScalePreference) => void;
|
|
944
|
+
}): JSX.Element {
|
|
945
|
+
const resolvedLabel = formatUiScale(resolvedScale);
|
|
946
|
+
const currentLabel = preference === "auto" ? `Auto (${resolvedLabel})` : `${preference}%`;
|
|
947
|
+
|
|
948
|
+
return (
|
|
949
|
+
<Select
|
|
950
|
+
value={preference}
|
|
951
|
+
onValueChange={(value) => onChange(normalizeUiScalePreference(value))}
|
|
952
|
+
>
|
|
953
|
+
<SelectTrigger
|
|
954
|
+
size="sm"
|
|
955
|
+
className="bg-white/[0.035] hover:bg-white/[0.065] h-8 min-w-[4rem] justify-center gap-1 border-0 px-2 py-0 text-muted-foreground shadow-[inset_0_0_0_1px_rgba(255,255,255,0.06)] [&>svg:last-child]:hidden"
|
|
956
|
+
aria-label={`Interface size: ${currentLabel}`}
|
|
957
|
+
>
|
|
958
|
+
<ALargeSmall className="size-3.5" />
|
|
959
|
+
<span className="font-mono text-[10px] font-semibold tabular-nums">{resolvedLabel}</span>
|
|
960
|
+
</SelectTrigger>
|
|
961
|
+
<SelectContent align="end">
|
|
962
|
+
<SelectItem value="auto">Auto</SelectItem>
|
|
963
|
+
<SelectItem value="100">100%</SelectItem>
|
|
964
|
+
<SelectItem value="125">125%</SelectItem>
|
|
965
|
+
<SelectItem value="150">150%</SelectItem>
|
|
966
|
+
</SelectContent>
|
|
967
|
+
</Select>
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
|
|
1320
971
|
function BrandHeader({
|
|
1321
972
|
compact,
|
|
1322
973
|
crabEntrancePhase,
|
|
1323
974
|
currentSessionId,
|
|
1324
975
|
logCount,
|
|
976
|
+
uiScalePreference,
|
|
977
|
+
resolvedUiScale,
|
|
978
|
+
onUiScaleChange,
|
|
1325
979
|
}: {
|
|
1326
980
|
compact: boolean;
|
|
1327
981
|
crabEntrancePhase: "hidden" | "playing" | "done";
|
|
1328
982
|
currentSessionId: string | null;
|
|
1329
983
|
logCount: number;
|
|
984
|
+
uiScalePreference: UiScalePreference;
|
|
985
|
+
resolvedUiScale: number;
|
|
986
|
+
onUiScaleChange: (preference: UiScalePreference) => void;
|
|
1330
987
|
}): JSX.Element {
|
|
1331
988
|
if (compact) {
|
|
1332
989
|
return (
|
|
@@ -1347,6 +1004,12 @@ function BrandHeader({
|
|
|
1347
1004
|
</div>
|
|
1348
1005
|
</div>
|
|
1349
1006
|
<div className="flex shrink-0 items-center gap-1">
|
|
1007
|
+
<ThemeModeToggle />
|
|
1008
|
+
<UiScaleControl
|
|
1009
|
+
preference={uiScalePreference}
|
|
1010
|
+
resolvedScale={resolvedUiScale}
|
|
1011
|
+
onChange={onUiScaleChange}
|
|
1012
|
+
/>
|
|
1350
1013
|
<AlertsDialog />
|
|
1351
1014
|
<GroupsDialog />
|
|
1352
1015
|
<AgentLabDialog currentSessionId={currentSessionId} logCount={logCount} />
|
|
@@ -1377,6 +1040,12 @@ function BrandHeader({
|
|
|
1377
1040
|
</span>
|
|
1378
1041
|
</h1>
|
|
1379
1042
|
<div className="flex flex-1 justify-end gap-1">
|
|
1043
|
+
<ThemeModeToggle />
|
|
1044
|
+
<UiScaleControl
|
|
1045
|
+
preference={uiScalePreference}
|
|
1046
|
+
resolvedScale={resolvedUiScale}
|
|
1047
|
+
onChange={onUiScaleChange}
|
|
1048
|
+
/>
|
|
1380
1049
|
<AlertsDialog />
|
|
1381
1050
|
<GroupsDialog />
|
|
1382
1051
|
<AgentLabDialog currentSessionId={currentSessionId} logCount={logCount} />
|
|
@@ -1386,25 +1055,6 @@ function BrandHeader({
|
|
|
1386
1055
|
);
|
|
1387
1056
|
}
|
|
1388
1057
|
|
|
1389
|
-
type FloatingLogoPosition = {
|
|
1390
|
-
x: number;
|
|
1391
|
-
y: number;
|
|
1392
|
-
};
|
|
1393
|
-
|
|
1394
|
-
type FloatingLogoDragState = {
|
|
1395
|
-
pointerId: number;
|
|
1396
|
-
offsetX: number;
|
|
1397
|
-
offsetY: number;
|
|
1398
|
-
};
|
|
1399
|
-
|
|
1400
|
-
type FloatingLogoEdge = "left" | "right" | "top" | "bottom";
|
|
1401
|
-
|
|
1402
|
-
const FLOATING_LOGO_SIZE = 56;
|
|
1403
|
-
const FLOATING_LOGO_MARGIN = 14;
|
|
1404
|
-
const FLOATING_LOGO_MIN_DRIFT_MS = 5200;
|
|
1405
|
-
const FLOATING_LOGO_DRIFT_WINDOW_MS = 2400;
|
|
1406
|
-
const FLOATING_LOGO_MIN_STEP = 72;
|
|
1407
|
-
const FLOATING_LOGO_MAX_STEP = 156;
|
|
1408
1058
|
const DETAILS_PANEL_COLLAPSED_STORAGE_KEY = "agent-inspector.detailsPanelCollapsed";
|
|
1409
1059
|
|
|
1410
1060
|
function readStoredDetailsPanelCollapsed(): boolean {
|
|
@@ -1413,264 +1063,6 @@ function readStoredDetailsPanelCollapsed(): boolean {
|
|
|
1413
1063
|
return stored === null || stored === "1";
|
|
1414
1064
|
}
|
|
1415
1065
|
|
|
1416
|
-
function clampFloatingLogoPosition(position: FloatingLogoPosition): FloatingLogoPosition {
|
|
1417
|
-
const maxX = Math.max(
|
|
1418
|
-
FLOATING_LOGO_MARGIN,
|
|
1419
|
-
window.innerWidth - FLOATING_LOGO_SIZE - FLOATING_LOGO_MARGIN,
|
|
1420
|
-
);
|
|
1421
|
-
const maxY = Math.max(
|
|
1422
|
-
FLOATING_LOGO_MARGIN,
|
|
1423
|
-
window.innerHeight - FLOATING_LOGO_SIZE - FLOATING_LOGO_MARGIN,
|
|
1424
|
-
);
|
|
1425
|
-
return {
|
|
1426
|
-
x: Math.min(Math.max(position.x, FLOATING_LOGO_MARGIN), maxX),
|
|
1427
|
-
y: Math.min(Math.max(position.y, FLOATING_LOGO_MARGIN), maxY),
|
|
1428
|
-
};
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1431
|
-
function snapFloatingLogoToEdge(position: FloatingLogoPosition): FloatingLogoPosition {
|
|
1432
|
-
const clamped = clampFloatingLogoPosition(position);
|
|
1433
|
-
const maxX = Math.max(
|
|
1434
|
-
FLOATING_LOGO_MARGIN,
|
|
1435
|
-
window.innerWidth - FLOATING_LOGO_SIZE - FLOATING_LOGO_MARGIN,
|
|
1436
|
-
);
|
|
1437
|
-
const maxY = Math.max(
|
|
1438
|
-
FLOATING_LOGO_MARGIN,
|
|
1439
|
-
window.innerHeight - FLOATING_LOGO_SIZE - FLOATING_LOGO_MARGIN,
|
|
1440
|
-
);
|
|
1441
|
-
const leftDistance = clamped.x - FLOATING_LOGO_MARGIN;
|
|
1442
|
-
const rightDistance = maxX - clamped.x;
|
|
1443
|
-
const topDistance = clamped.y - FLOATING_LOGO_MARGIN;
|
|
1444
|
-
const bottomDistance = maxY - clamped.y;
|
|
1445
|
-
const nearestDistance = Math.min(leftDistance, rightDistance, topDistance, bottomDistance);
|
|
1446
|
-
|
|
1447
|
-
if (nearestDistance === leftDistance) return { x: FLOATING_LOGO_MARGIN, y: clamped.y };
|
|
1448
|
-
if (nearestDistance === rightDistance) return { x: maxX, y: clamped.y };
|
|
1449
|
-
if (nearestDistance === topDistance) return { x: clamped.x, y: FLOATING_LOGO_MARGIN };
|
|
1450
|
-
return { x: clamped.x, y: maxY };
|
|
1451
|
-
}
|
|
1452
|
-
|
|
1453
|
-
function randomFloatingLogoEdgePosition(): FloatingLogoPosition {
|
|
1454
|
-
const maxX = Math.max(
|
|
1455
|
-
FLOATING_LOGO_MARGIN,
|
|
1456
|
-
window.innerWidth - FLOATING_LOGO_SIZE - FLOATING_LOGO_MARGIN,
|
|
1457
|
-
);
|
|
1458
|
-
const maxY = Math.max(
|
|
1459
|
-
FLOATING_LOGO_MARGIN,
|
|
1460
|
-
window.innerHeight - FLOATING_LOGO_SIZE - FLOATING_LOGO_MARGIN,
|
|
1461
|
-
);
|
|
1462
|
-
const randomX = FLOATING_LOGO_MARGIN + Math.random() * (maxX - FLOATING_LOGO_MARGIN);
|
|
1463
|
-
const randomY = FLOATING_LOGO_MARGIN + Math.random() * (maxY - FLOATING_LOGO_MARGIN);
|
|
1464
|
-
const edgeIndex = Math.floor(Math.random() * 4);
|
|
1465
|
-
|
|
1466
|
-
switch (edgeIndex) {
|
|
1467
|
-
case 0:
|
|
1468
|
-
return { x: FLOATING_LOGO_MARGIN, y: randomY };
|
|
1469
|
-
case 1:
|
|
1470
|
-
return { x: maxX, y: randomY };
|
|
1471
|
-
case 2:
|
|
1472
|
-
return { x: randomX, y: FLOATING_LOGO_MARGIN };
|
|
1473
|
-
case 3:
|
|
1474
|
-
return { x: randomX, y: maxY };
|
|
1475
|
-
default:
|
|
1476
|
-
return { x: maxX, y: FLOATING_LOGO_MARGIN };
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
|
|
1480
|
-
function nearestFloatingLogoEdge(position: FloatingLogoPosition): FloatingLogoEdge {
|
|
1481
|
-
const clamped = clampFloatingLogoPosition(position);
|
|
1482
|
-
const maxX = Math.max(
|
|
1483
|
-
FLOATING_LOGO_MARGIN,
|
|
1484
|
-
window.innerWidth - FLOATING_LOGO_SIZE - FLOATING_LOGO_MARGIN,
|
|
1485
|
-
);
|
|
1486
|
-
const maxY = Math.max(
|
|
1487
|
-
FLOATING_LOGO_MARGIN,
|
|
1488
|
-
window.innerHeight - FLOATING_LOGO_SIZE - FLOATING_LOGO_MARGIN,
|
|
1489
|
-
);
|
|
1490
|
-
const distances: { edge: FloatingLogoEdge; distance: number }[] = [
|
|
1491
|
-
{ edge: "left", distance: clamped.x - FLOATING_LOGO_MARGIN },
|
|
1492
|
-
{ edge: "right", distance: maxX - clamped.x },
|
|
1493
|
-
{ edge: "top", distance: clamped.y - FLOATING_LOGO_MARGIN },
|
|
1494
|
-
{ edge: "bottom", distance: maxY - clamped.y },
|
|
1495
|
-
];
|
|
1496
|
-
return distances.reduce((best, current) => (current.distance < best.distance ? current : best))
|
|
1497
|
-
.edge;
|
|
1498
|
-
}
|
|
1499
|
-
|
|
1500
|
-
function nextFloatingLogoWalkingPosition(position: FloatingLogoPosition): FloatingLogoPosition {
|
|
1501
|
-
const clamped = snapFloatingLogoToEdge(position);
|
|
1502
|
-
const maxX = Math.max(
|
|
1503
|
-
FLOATING_LOGO_MARGIN,
|
|
1504
|
-
window.innerWidth - FLOATING_LOGO_SIZE - FLOATING_LOGO_MARGIN,
|
|
1505
|
-
);
|
|
1506
|
-
const maxY = Math.max(
|
|
1507
|
-
FLOATING_LOGO_MARGIN,
|
|
1508
|
-
window.innerHeight - FLOATING_LOGO_SIZE - FLOATING_LOGO_MARGIN,
|
|
1509
|
-
);
|
|
1510
|
-
const step =
|
|
1511
|
-
FLOATING_LOGO_MIN_STEP + Math.random() * (FLOATING_LOGO_MAX_STEP - FLOATING_LOGO_MIN_STEP);
|
|
1512
|
-
const direction = Math.random() > 0.5 ? 1 : -1;
|
|
1513
|
-
const edge = nearestFloatingLogoEdge(clamped);
|
|
1514
|
-
|
|
1515
|
-
switch (edge) {
|
|
1516
|
-
case "left": {
|
|
1517
|
-
const nextY = clamped.y + step * direction;
|
|
1518
|
-
if (nextY <= FLOATING_LOGO_MARGIN)
|
|
1519
|
-
return { x: FLOATING_LOGO_MARGIN, y: FLOATING_LOGO_MARGIN };
|
|
1520
|
-
if (nextY >= maxY) return { x: FLOATING_LOGO_MARGIN, y: maxY };
|
|
1521
|
-
return { x: FLOATING_LOGO_MARGIN, y: nextY };
|
|
1522
|
-
}
|
|
1523
|
-
case "right": {
|
|
1524
|
-
const nextY = clamped.y + step * direction;
|
|
1525
|
-
if (nextY <= FLOATING_LOGO_MARGIN) return { x: maxX, y: FLOATING_LOGO_MARGIN };
|
|
1526
|
-
if (nextY >= maxY) return { x: maxX, y: maxY };
|
|
1527
|
-
return { x: maxX, y: nextY };
|
|
1528
|
-
}
|
|
1529
|
-
case "top": {
|
|
1530
|
-
const nextX = clamped.x + step * direction;
|
|
1531
|
-
if (nextX <= FLOATING_LOGO_MARGIN)
|
|
1532
|
-
return { x: FLOATING_LOGO_MARGIN, y: FLOATING_LOGO_MARGIN };
|
|
1533
|
-
if (nextX >= maxX) return { x: maxX, y: FLOATING_LOGO_MARGIN };
|
|
1534
|
-
return { x: nextX, y: FLOATING_LOGO_MARGIN };
|
|
1535
|
-
}
|
|
1536
|
-
case "bottom": {
|
|
1537
|
-
const nextX = clamped.x + step * direction;
|
|
1538
|
-
if (nextX <= FLOATING_LOGO_MARGIN) return { x: FLOATING_LOGO_MARGIN, y: maxY };
|
|
1539
|
-
if (nextX >= maxX) return { x: maxX, y: maxY };
|
|
1540
|
-
return { x: nextX, y: maxY };
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
|
|
1545
|
-
function FloatingInspectorLogo(): JSX.Element {
|
|
1546
|
-
const [position, setPosition] = useState<FloatingLogoPosition>({
|
|
1547
|
-
x: FLOATING_LOGO_MARGIN,
|
|
1548
|
-
y: FLOATING_LOGO_MARGIN,
|
|
1549
|
-
});
|
|
1550
|
-
const [motionTick, setMotionTick] = useState(0);
|
|
1551
|
-
const [dragging, setDragging] = useState(false);
|
|
1552
|
-
const dragStateRef = useRef<FloatingLogoDragState | null>(null);
|
|
1553
|
-
|
|
1554
|
-
useEffect(() => {
|
|
1555
|
-
setPosition(randomFloatingLogoEdgePosition());
|
|
1556
|
-
}, []);
|
|
1557
|
-
|
|
1558
|
-
useEffect(() => {
|
|
1559
|
-
if (dragging) return undefined;
|
|
1560
|
-
let timeoutId: number | null = null;
|
|
1561
|
-
|
|
1562
|
-
const scheduleDrift = () => {
|
|
1563
|
-
const delay = FLOATING_LOGO_MIN_DRIFT_MS + Math.random() * FLOATING_LOGO_DRIFT_WINDOW_MS;
|
|
1564
|
-
timeoutId = window.setTimeout(() => {
|
|
1565
|
-
setMotionTick((current) => current + 1);
|
|
1566
|
-
setPosition((current) => nextFloatingLogoWalkingPosition(current));
|
|
1567
|
-
scheduleDrift();
|
|
1568
|
-
}, delay);
|
|
1569
|
-
};
|
|
1570
|
-
|
|
1571
|
-
scheduleDrift();
|
|
1572
|
-
return () => {
|
|
1573
|
-
if (timeoutId !== null) {
|
|
1574
|
-
window.clearTimeout(timeoutId);
|
|
1575
|
-
}
|
|
1576
|
-
};
|
|
1577
|
-
}, [dragging]);
|
|
1578
|
-
|
|
1579
|
-
useEffect(() => {
|
|
1580
|
-
const handleResize = () => {
|
|
1581
|
-
setPosition((current) => snapFloatingLogoToEdge(current));
|
|
1582
|
-
};
|
|
1583
|
-
window.addEventListener("resize", handleResize);
|
|
1584
|
-
return () => window.removeEventListener("resize", handleResize);
|
|
1585
|
-
}, []);
|
|
1586
|
-
|
|
1587
|
-
const handlePointerDown = useCallback((event: ReactPointerEvent<HTMLButtonElement>) => {
|
|
1588
|
-
const rect = event.currentTarget.getBoundingClientRect();
|
|
1589
|
-
dragStateRef.current = {
|
|
1590
|
-
pointerId: event.pointerId,
|
|
1591
|
-
offsetX: event.clientX - rect.left,
|
|
1592
|
-
offsetY: event.clientY - rect.top,
|
|
1593
|
-
};
|
|
1594
|
-
event.currentTarget.setPointerCapture(event.pointerId);
|
|
1595
|
-
setDragging(true);
|
|
1596
|
-
}, []);
|
|
1597
|
-
|
|
1598
|
-
const handlePointerMove = useCallback((event: ReactPointerEvent<HTMLButtonElement>) => {
|
|
1599
|
-
const dragState = dragStateRef.current;
|
|
1600
|
-
if (dragState === null || dragState.pointerId !== event.pointerId) return;
|
|
1601
|
-
setPosition(
|
|
1602
|
-
clampFloatingLogoPosition({
|
|
1603
|
-
x: event.clientX - dragState.offsetX,
|
|
1604
|
-
y: event.clientY - dragState.offsetY,
|
|
1605
|
-
}),
|
|
1606
|
-
);
|
|
1607
|
-
}, []);
|
|
1608
|
-
|
|
1609
|
-
const handlePointerUp = useCallback((event: ReactPointerEvent<HTMLButtonElement>) => {
|
|
1610
|
-
const dragState = dragStateRef.current;
|
|
1611
|
-
if (dragState === null || dragState.pointerId !== event.pointerId) return;
|
|
1612
|
-
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
|
|
1613
|
-
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
1614
|
-
}
|
|
1615
|
-
dragStateRef.current = null;
|
|
1616
|
-
setDragging(false);
|
|
1617
|
-
setPosition((current) => snapFloatingLogoToEdge(current));
|
|
1618
|
-
}, []);
|
|
1619
|
-
|
|
1620
|
-
const style = useMemo<CSSProperties>(
|
|
1621
|
-
() => ({
|
|
1622
|
-
transform: `translate3d(${position.x}px, ${position.y}px, 0) rotate(${
|
|
1623
|
-
motionTick % 2 === 0 ? "-4deg" : "5deg"
|
|
1624
|
-
}) scale(${dragging ? "1.08" : "1"})`,
|
|
1625
|
-
transition: dragging ? "none" : "transform 4800ms cubic-bezier(0.33, 1, 0.68, 1)",
|
|
1626
|
-
touchAction: "none",
|
|
1627
|
-
}),
|
|
1628
|
-
[dragging, motionTick, position.x, position.y],
|
|
1629
|
-
);
|
|
1630
|
-
|
|
1631
|
-
return (
|
|
1632
|
-
<button
|
|
1633
|
-
type="button"
|
|
1634
|
-
className={cn(
|
|
1635
|
-
"fixed left-0 top-0 z-40 flex size-14 items-center justify-center rounded-full bg-transparent text-foreground outline-none transition-[filter,opacity] duration-200 hover:opacity-95 hover:drop-shadow-[0_0_24px_rgba(245,158,11,0.36)] focus-visible:ring-1 focus-visible:ring-amber-300/55",
|
|
1636
|
-
dragging ? "cursor-grabbing" : "cursor-grab",
|
|
1637
|
-
)}
|
|
1638
|
-
style={style}
|
|
1639
|
-
onPointerDown={handlePointerDown}
|
|
1640
|
-
onPointerMove={handlePointerMove}
|
|
1641
|
-
onPointerUp={handlePointerUp}
|
|
1642
|
-
onPointerCancel={handlePointerUp}
|
|
1643
|
-
aria-label="Floating Inspector logo"
|
|
1644
|
-
title="Drag Inspector logo"
|
|
1645
|
-
>
|
|
1646
|
-
<span
|
|
1647
|
-
className={cn(
|
|
1648
|
-
"relative flex size-12 items-center justify-center",
|
|
1649
|
-
!dragging && "floating-crab-body",
|
|
1650
|
-
)}
|
|
1651
|
-
aria-hidden="true"
|
|
1652
|
-
>
|
|
1653
|
-
<CrabLogo className="relative z-10 size-11 text-amber-500 drop-shadow-[0_0_15px_rgba(245,158,11,0.30)]" />
|
|
1654
|
-
<span className="pointer-events-none absolute inset-x-0 bottom-0 z-0 flex items-end justify-center gap-1 text-amber-400/80">
|
|
1655
|
-
{[0, 1, 2, 3, 4, 5].map((leg) => (
|
|
1656
|
-
<span
|
|
1657
|
-
key={leg}
|
|
1658
|
-
className={cn(
|
|
1659
|
-
"floating-crab-leg h-2 w-0.5 rounded-full bg-current shadow-[0_0_7px_rgba(245,158,11,0.24)]",
|
|
1660
|
-
leg < 3 ? "floating-crab-leg-left" : "floating-crab-leg-right",
|
|
1661
|
-
dragging && "animate-none",
|
|
1662
|
-
)}
|
|
1663
|
-
style={{
|
|
1664
|
-
animationDelay: `${leg % 2 === 0 ? 0 : 180}ms`,
|
|
1665
|
-
}}
|
|
1666
|
-
/>
|
|
1667
|
-
))}
|
|
1668
|
-
</span>
|
|
1669
|
-
</span>
|
|
1670
|
-
</button>
|
|
1671
|
-
);
|
|
1672
|
-
}
|
|
1673
|
-
|
|
1674
1066
|
function SessionContextLogButton({ label, logId }: { label: string; logId: number }): JSX.Element {
|
|
1675
1067
|
return (
|
|
1676
1068
|
<button
|
|
@@ -2049,6 +1441,7 @@ export function ProxyViewer({
|
|
|
2049
1441
|
}: ProxyViewerProps): JSX.Element {
|
|
2050
1442
|
const { totalIn, totalOut } = useMemo(() => computeTokenSummary(logs), [logs]);
|
|
2051
1443
|
const { providers } = useProviders();
|
|
1444
|
+
const { uiScalePreference, resolvedUiScale, setUiScalePreference } = useUiScale();
|
|
2052
1445
|
const [exporting, setExporting] = useState<ExportMode | null>(null);
|
|
2053
1446
|
const [exportError, setExportError] = useState<string | null>(null);
|
|
2054
1447
|
const [importing, setImporting] = useState(false);
|
|
@@ -2334,7 +1727,9 @@ export function ProxyViewer({
|
|
|
2334
1727
|
onClick={toggleDetailsPanelCollapsed}
|
|
2335
1728
|
className={cn(
|
|
2336
1729
|
"group fixed top-1/2 right-3 z-40 inline-flex h-14 w-7 -translate-y-1/2 items-center justify-center rounded-l-md border border-white/[0.08] bg-background/90 text-muted-foreground shadow-[0_12px_34px_rgba(0,0,0,0.26),inset_0_1px_0_rgba(255,255,255,0.06)] backdrop-blur transition-[right,background-color,color,border-color] duration-200 hover:border-white/[0.16] hover:bg-white/[0.07] hover:text-foreground focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none",
|
|
2337
|
-
detailsPanelCollapsed
|
|
1730
|
+
detailsPanelCollapsed
|
|
1731
|
+
? "right-3"
|
|
1732
|
+
: "lg:right-[calc(21.25rem-0.875rem)] xl:right-[calc(22.5rem-0.875rem)]",
|
|
2338
1733
|
)}
|
|
2339
1734
|
aria-label={detailsPanelCollapsed ? "Show details panel" : "Hide details panel"}
|
|
2340
1735
|
title={detailsPanelCollapsed ? "Show details panel" : "Hide details panel"}
|
|
@@ -2346,13 +1741,13 @@ export function ProxyViewer({
|
|
|
2346
1741
|
<PanelRightClose className="size-3.5" />
|
|
2347
1742
|
)}
|
|
2348
1743
|
</button>
|
|
2349
|
-
{detailsPanelCollapsed && <
|
|
1744
|
+
{detailsPanelCollapsed && <InspectorPet logs={logs} />}
|
|
2350
1745
|
<div
|
|
2351
1746
|
className={cn(
|
|
2352
1747
|
"grid w-full min-w-0 grid-cols-1 lg:min-h-0 lg:flex-1 lg:overflow-hidden",
|
|
2353
1748
|
detailsPanelCollapsed
|
|
2354
1749
|
? "lg:grid-cols-[minmax(0,1fr)]"
|
|
2355
|
-
: "lg:grid-cols-[minmax(0,1fr)
|
|
1750
|
+
: "lg:grid-cols-[minmax(0,1fr)_21.25rem] xl:grid-cols-[minmax(0,1fr)_22.5rem]",
|
|
2356
1751
|
)}
|
|
2357
1752
|
>
|
|
2358
1753
|
{!detailsPanelCollapsed && (
|
|
@@ -2363,6 +1758,9 @@ export function ProxyViewer({
|
|
|
2363
1758
|
crabEntrancePhase={crabEntrancePhase}
|
|
2364
1759
|
currentSessionId={labSessionId}
|
|
2365
1760
|
logCount={logs.length}
|
|
1761
|
+
uiScalePreference={uiScalePreference}
|
|
1762
|
+
resolvedUiScale={resolvedUiScale}
|
|
1763
|
+
onUiScaleChange={setUiScalePreference}
|
|
2366
1764
|
/>
|
|
2367
1765
|
|
|
2368
1766
|
<SidebarPanel label="Session">
|