@tonyclaw/agent-inspector 2.1.3 → 2.1.5
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-BLYcm6Dg.js → CompareDrawer-SAn2KFl2.js} +1 -1
- package/.output/public/assets/ProxyViewerContainer-DaiTFf7n.js +117 -0
- package/.output/public/assets/{ReplayDialog-_kCy9L9E.js → ReplayDialog-CCwz0YUV.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-DWp5pWsv.js → RequestAnatomy-DXcC4ydl.js} +1 -1
- package/.output/public/assets/{ResponseView-Ci92o6mt.js → ResponseView-Bh9t9aZF.js} +1 -1
- package/.output/public/assets/{StreamingChunkSequence-C-1ZQl_b.js → StreamingChunkSequence-B69M2rr-.js} +1 -1
- package/.output/public/assets/_sessionId-6fF5JpyQ.js +1 -0
- package/.output/public/assets/index-Bcq8bZoK.css +1 -0
- package/.output/public/assets/index-DBSbw8Gd.js +1 -0
- package/.output/public/assets/{main-RgLvmIQk.js → main-DZVKUeg6.js} +2 -2
- package/.output/server/_libs/lucide-react.mjs +204 -173
- package/.output/server/_libs/radix-ui__react-dialog.mjs +2 -0
- package/.output/server/{_sessionId-DnOYbKO7.mjs → _sessionId-COVUf_EG.mjs} +3 -3
- package/.output/server/_ssr/{CompareDrawer-D44_F13_.mjs → CompareDrawer-DqQHO4tk.mjs} +3 -3
- package/.output/server/_ssr/{ProxyViewerContainer-Cx4p_awW.mjs → ProxyViewerContainer-BEeF4V-C.mjs} +1006 -75
- package/.output/server/_ssr/{ReplayDialog-wScCF3Oc.mjs → ReplayDialog-DUZbsd5x.mjs} +4 -4
- package/.output/server/_ssr/{RequestAnatomy-Cf7R7PXS.mjs → RequestAnatomy-DKuIxoBj.mjs} +2 -2
- package/.output/server/_ssr/{ResponseView-7pVIZLL-.mjs → ResponseView-BB9CYmTJ.mjs} +3 -3
- package/.output/server/_ssr/{StreamingChunkSequence-Bs57_GbC.mjs → StreamingChunkSequence-CkY3uTjF.mjs} +3 -3
- package/.output/server/_ssr/{index-6eY__la9.mjs → index-CHTchkrO.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-DRZPhZB6.mjs → router-C8DJ5pQZ.mjs} +1972 -221
- package/.output/server/_tanstack-start-manifest_v-Z1ffcxDr.mjs +4 -0
- package/.output/server/index.mjs +68 -68
- package/package.json +2 -1
- package/src/components/ProxyViewer.tsx +87 -1
- package/src/components/ProxyViewerContainer.tsx +26 -0
- package/src/components/alerts/AlertsDialog.tsx +610 -0
- package/src/components/groups/GroupsDialog.tsx +41 -13
- package/src/components/proxy-viewer/LogEntry.tsx +324 -51
- package/src/components/ui/dialog.tsx +1 -0
- package/src/contracts/index.ts +15 -2
- package/src/contracts/log.ts +18 -0
- package/src/lib/alertContract.ts +79 -0
- package/src/lib/apiClient.ts +39 -0
- package/src/lib/export-logs.ts +47 -9
- package/src/lib/groupContract.ts +1 -0
- package/src/lib/logImportContract.ts +12 -0
- package/src/lib/useAlerts.ts +82 -0
- package/src/lib/useGroupEvidence.ts +6 -2
- package/src/lib/useGroups.ts +6 -2
- package/src/proxy/alerts.ts +664 -0
- package/src/proxy/logBodyChunks.ts +91 -0
- package/src/proxy/logImporter.ts +491 -0
- package/src/proxy/logIndex.ts +63 -2
- package/src/proxy/sqliteLogIndex.ts +612 -0
- package/src/proxy/store.ts +32 -7
- package/src/routes/api/alerts.summary.ts +24 -0
- package/src/routes/api/alerts.ts +52 -0
- package/src/routes/api/logs.$id.body.ts +44 -0
- package/src/routes/api/logs.import.ts +50 -0
- package/src/services/alerts.ts +12 -0
- package/src/services/groups.ts +1 -0
- package/.output/public/assets/ProxyViewerContainer-Ba5wFn00.js +0 -117
- package/.output/public/assets/_sessionId-DKzAmSNP.js +0 -1
- package/.output/public/assets/index-B-MZ9lQM.css +0 -1
- package/.output/public/assets/index-ZZHwgkWJ.js +0 -1
- package/.output/server/_tanstack-start-manifest_v-DfAVFkZ5.mjs +0 -4
package/.output/server/_ssr/{ProxyViewerContainer-Cx4p_awW.mjs → ProxyViewerContainer-BEeF4V-C.mjs}
RENAMED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { r as reactExports, j as jsxRuntimeExports, a as React } from "../_libs/react.mjs";
|
|
2
|
-
import { I as InspectorGroupsListResponseSchema, C as CapturedLogSchema, D as DEFAULT_CAPTURE_MODE, a as DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS, b as DEFAULT_PROVIDER_TEST_TIMEOUT_SECONDS, c as DEFAULT_TIME_DISPLAY_FORMAT, d as RuntimeConfigSchema,
|
|
2
|
+
import { I as InspectorGroupsListResponseSchema, C as CapturedLogSchema, D as DEFAULT_CAPTURE_MODE, a as DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS, b as DEFAULT_PROVIDER_TEST_TIMEOUT_SECONDS, c as DEFAULT_TIME_DISPLAY_FORMAT, d as RuntimeConfigSchema, p as packageJson, P as ProviderConfigSchema, g as apiFormatForPath, l as GroupEvidenceExportResultSchema, m as DeleteInspectorGroupResponseSchema, r as createPendingProviderTestResults, t as ProviderTestResultsSchema, u as createFailedProviderTestResults, M as MAX_SLOW_RESPONSE_THRESHOLD_SECONDS, v as MAX_PROVIDER_TEST_TIMEOUT_SECONDS, T as TimeDisplayFormatSchema, h as getSessionPath, f as parseOpenAIResponse, O as OpenAIRequestSchema, A as AnthropicResponseSchema$1, e as AnthropicRequestSchema, s as safeGetOwnProperty, w as resolveProviderContextWindow, j as AlertSummarySchema, k as AlertListResponseSchema, G as GroupEvidenceReadResponseSchema, q as maskApiKey, n as providerHasContextMetadata, o as findProviderModelMetadata, K as KnowledgeCandidateSchema, i as stripClaudeCodeBillingHeader, L as LogBodyChunkSchema, x as isPlainRecord } from "./router-C8DJ5pQZ.mjs";
|
|
3
3
|
import { u as useSWR, a as useSWRConfig } from "../_libs/swr.mjs";
|
|
4
4
|
import { J as JSZip } from "../_libs/jszip.mjs";
|
|
5
5
|
import { c as clsx } from "../_libs/clsx.mjs";
|
|
6
6
|
import { t as twMerge } from "../_libs/tailwind-merge.mjs";
|
|
7
7
|
import { c as cva } from "../_libs/class-variance-authority.mjs";
|
|
8
|
-
import { R as Root, T as Trigger$1, C as Content, a as Close, b as Title, P as Portal$2, O as Overlay } from "../_libs/radix-ui__react-dialog.mjs";
|
|
8
|
+
import { R as Root, T as Trigger$1, C as Content, a as Close, b as Title, D as Description, P as Portal$2, O as Overlay } from "../_libs/radix-ui__react-dialog.mjs";
|
|
9
9
|
import { d as diffJson, a as diffLines } from "../_libs/diff.mjs";
|
|
10
10
|
import { u as useWindowVirtualizer, a as useVirtualizer } from "../_libs/tanstack__react-virtual.mjs";
|
|
11
11
|
import { R as Root2, T as Trigger, I as Icon, V as Value, P as Portal, C as Content2, a as Viewport, b as Item, c as ItemIndicator, d as ItemText, S as ScrollUpButton, e as ScrollDownButton } from "../_libs/radix-ui__react-select.mjs";
|
|
12
|
-
import { I as Info, C as ChevronDown, a as Check, X, D as Download, F as FileBraces, T as Trash2, P as Plus, A as ArrowLeft, b as ChevronUp, c as Copy, d as ChevronsLeft, e as ChevronLeft, f as ChevronRight, g as ChevronsRight, h as Clapperboard, S as Siren, G as Gauge, L as
|
|
12
|
+
import { I as Info, C as ChevronDown, a as Check, X, U as Upload, D as Download, F as FileBraces, T as Trash2, P as Plus, A as ArrowLeft, b as ChevronUp, c as Copy, d as ChevronsLeft, e as ChevronLeft, f as ChevronRight, g as ChevronsRight, h as Clapperboard, S as Siren, G as Gauge, L as LoaderCircle, R as RefreshCw, i as Layers, j as Settings, k as Scan, l as CircleAlert, m as Terminal, n as ArrowUpRight, o as ArrowDownRight, p as User, q as Clock, M as MessageSquare, Z as Zap, E as ExternalLink, W as Wrench, r as TriangleAlert, B as Brain, s as Bell, t as Clock3, u as ShieldAlert, v as EyeOff, w as Eye, x as RotateCw, y as Pencil, z as CircleX, H as ShieldCheck, J as Save, K as FileSearch, N as CircleCheck, O as CloudUpload, Q as OctagonAlert, V as Radio, Y as Globe, _ as FileTerminal, $ as ChevronsUp, a0 as ChevronsDown, a1 as FileDiff, a2 as History, a3 as RotateCcw, a4 as Search, a5 as GitCompareArrows, a6 as Minus, a7 as CircleCheckBig, a8 as ArrowUp, a9 as ArrowDown, aa as CircleQuestionMark, ab as Server, ac as Lock, ad as Wifi, ae as WifiOff, af as Ellipsis, ag as CodeXml, ah as Blocks, ai as MousePointerClick, aj as FileText, ak as Rows3, al as Columns2 } from "../_libs/lucide-react.mjs";
|
|
13
13
|
import { M as Markdown } from "../_libs/react-markdown.mjs";
|
|
14
14
|
import { u as union, d as object, a as array, l as literal, b as string, n as number, c as boolean, _ as _enum } from "../_libs/zod.mjs";
|
|
15
15
|
import { S as Slot } from "../_libs/radix-ui__react-slot.mjs";
|
|
@@ -41,6 +41,37 @@ async function fetchJson(input, schema, init, errorFallback) {
|
|
|
41
41
|
}
|
|
42
42
|
return parseJsonResponse$1(response, schema);
|
|
43
43
|
}
|
|
44
|
+
function formatTimeout(timeoutMs) {
|
|
45
|
+
if (timeoutMs < 1e3) return `${String(timeoutMs)}ms`;
|
|
46
|
+
return `${String(Math.round(timeoutMs / 1e3))}s`;
|
|
47
|
+
}
|
|
48
|
+
class ApiTimeoutError extends Error {
|
|
49
|
+
constructor(timeoutMs) {
|
|
50
|
+
super(`Request timed out after ${formatTimeout(timeoutMs)}`);
|
|
51
|
+
this.name = "ApiTimeoutError";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function fetchJsonWithTimeout(input, schema, timeoutMs, init, errorFallback) {
|
|
55
|
+
const controller = new AbortController();
|
|
56
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
57
|
+
try {
|
|
58
|
+
return await fetchJson(input, schema, { ...init, signal: controller.signal }, errorFallback);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
61
|
+
throw new ApiTimeoutError(timeoutMs);
|
|
62
|
+
}
|
|
63
|
+
throw error;
|
|
64
|
+
} finally {
|
|
65
|
+
clearTimeout(timeout);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const ImportLogsResponseSchema = object({
|
|
69
|
+
sessionId: string(),
|
|
70
|
+
imported: number().int().nonnegative(),
|
|
71
|
+
skipped: number().int().nonnegative(),
|
|
72
|
+
warnings: array(string()),
|
|
73
|
+
logs: array(CapturedLogSchema)
|
|
74
|
+
});
|
|
44
75
|
const STRIP_CONFIG_SWR_KEY = "/api/config";
|
|
45
76
|
async function fetcher$2(url) {
|
|
46
77
|
return fetchJson(
|
|
@@ -356,6 +387,29 @@ function redactJsonText(text) {
|
|
|
356
387
|
function exportText(text, mode) {
|
|
357
388
|
return mode === "redacted" ? redactJsonText(text) : text;
|
|
358
389
|
}
|
|
390
|
+
function buildExportLogMetadata(log, mode) {
|
|
391
|
+
return {
|
|
392
|
+
...log,
|
|
393
|
+
rawRequestBody: null,
|
|
394
|
+
responseText: null,
|
|
395
|
+
rawHeaders: mode === "raw" ? log.rawHeaders : void 0,
|
|
396
|
+
headers: mode === "raw" ? log.headers : void 0,
|
|
397
|
+
streamingChunks: void 0,
|
|
398
|
+
bodyContentMode: "compact"
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
function buildExportManifest(logs, mode, streamingLogs, exportedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
402
|
+
return {
|
|
403
|
+
version: packageJson.version,
|
|
404
|
+
exportedAt,
|
|
405
|
+
mode,
|
|
406
|
+
redacted: mode === "redacted",
|
|
407
|
+
logCount: logs.length,
|
|
408
|
+
logIds: logs.map((log) => log.id),
|
|
409
|
+
streamingChunkExportLimit: MAX_STREAMING_CHUNK_EXPORTS,
|
|
410
|
+
streamingChunkLogIds: streamingLogs.map((log) => log.id)
|
|
411
|
+
};
|
|
412
|
+
}
|
|
359
413
|
async function exportLogsAsZip(logs, mode = "redacted") {
|
|
360
414
|
const limitMessage = exportLimitMessage(logs);
|
|
361
415
|
if (limitMessage !== null) return { ok: false, message: limitMessage };
|
|
@@ -364,16 +418,12 @@ async function exportLogsAsZip(logs, mode = "redacted") {
|
|
|
364
418
|
const streamingLogs = logs.filter((log) => log.streaming).slice(0, MAX_STREAMING_CHUNK_EXPORTS);
|
|
365
419
|
zip.file(
|
|
366
420
|
"manifest.json",
|
|
421
|
+
JSON.stringify(buildExportManifest(logs, mode, streamingLogs), null, 2)
|
|
422
|
+
);
|
|
423
|
+
zip.file(
|
|
424
|
+
"logs.json",
|
|
367
425
|
JSON.stringify(
|
|
368
|
-
|
|
369
|
-
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
370
|
-
mode,
|
|
371
|
-
redacted: mode === "redacted",
|
|
372
|
-
logCount: logs.length,
|
|
373
|
-
logIds: logs.map((log) => log.id),
|
|
374
|
-
streamingChunkExportLimit: MAX_STREAMING_CHUNK_EXPORTS,
|
|
375
|
-
streamingChunkLogIds: streamingLogs.map((log) => log.id)
|
|
376
|
-
},
|
|
426
|
+
logs.map((log) => buildExportLogMetadata(log, mode)),
|
|
377
427
|
null,
|
|
378
428
|
2
|
|
379
429
|
)
|
|
@@ -458,7 +508,7 @@ async function copyTextToClipboard(text) {
|
|
|
458
508
|
}
|
|
459
509
|
return copyWithTextareaFallback(text);
|
|
460
510
|
}
|
|
461
|
-
function formatTimestamp$
|
|
511
|
+
function formatTimestamp$2(iso, format) {
|
|
462
512
|
switch (format) {
|
|
463
513
|
case "full":
|
|
464
514
|
return iso;
|
|
@@ -471,7 +521,7 @@ function formatTimestamp$1(iso, format) {
|
|
|
471
521
|
}
|
|
472
522
|
}
|
|
473
523
|
function formatTimestampRange(startedAt, endedAt, format) {
|
|
474
|
-
return `${formatTimestamp$
|
|
524
|
+
return `${formatTimestamp$2(startedAt, format)} - ${formatTimestamp$2(endedAt, format)}`;
|
|
475
525
|
}
|
|
476
526
|
function cn(...inputs) {
|
|
477
527
|
return twMerge(clsx(inputs));
|
|
@@ -675,6 +725,19 @@ function DialogTitle({
|
|
|
675
725
|
}
|
|
676
726
|
);
|
|
677
727
|
}
|
|
728
|
+
function DialogDescription({
|
|
729
|
+
className,
|
|
730
|
+
...props
|
|
731
|
+
}) {
|
|
732
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
733
|
+
Description,
|
|
734
|
+
{
|
|
735
|
+
"data-slot": "dialog-description",
|
|
736
|
+
className: cn("text-sm text-muted-foreground", className),
|
|
737
|
+
...props
|
|
738
|
+
}
|
|
739
|
+
);
|
|
740
|
+
}
|
|
678
741
|
const buttonVariants = cva(
|
|
679
742
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
680
743
|
{
|
|
@@ -1926,19 +1989,19 @@ function useCopyFeedback(text) {
|
|
|
1926
1989
|
return { copied, copy };
|
|
1927
1990
|
}
|
|
1928
1991
|
const LazyCompareDrawer = reactExports.lazy(
|
|
1929
|
-
() => import("./CompareDrawer-
|
|
1992
|
+
() => import("./CompareDrawer-DqQHO4tk.mjs").then((m) => ({ default: m.CompareDrawer }))
|
|
1930
1993
|
);
|
|
1931
1994
|
const LazyReplayDialog = reactExports.lazy(
|
|
1932
|
-
() => import("./ReplayDialog-
|
|
1995
|
+
() => import("./ReplayDialog-DUZbsd5x.mjs").then((m) => ({ default: m.ReplayDialog }))
|
|
1933
1996
|
);
|
|
1934
1997
|
const LazyRequestAnatomy = reactExports.lazy(
|
|
1935
|
-
() => import("./RequestAnatomy-
|
|
1998
|
+
() => import("./RequestAnatomy-DKuIxoBj.mjs").then((m) => ({ default: m.RequestAnatomy }))
|
|
1936
1999
|
);
|
|
1937
2000
|
const LazyResponseView = reactExports.lazy(
|
|
1938
|
-
() => import("./ResponseView-
|
|
2001
|
+
() => import("./ResponseView-BB9CYmTJ.mjs").then((m) => ({ default: m.ResponseView }))
|
|
1939
2002
|
);
|
|
1940
2003
|
const LazyStreamingChunkSequence = reactExports.lazy(
|
|
1941
|
-
() => import("./StreamingChunkSequence-
|
|
2004
|
+
() => import("./StreamingChunkSequence-CkY3uTjF.mjs").then((m) => ({
|
|
1942
2005
|
default: m.StreamingChunkSequence
|
|
1943
2006
|
}))
|
|
1944
2007
|
);
|
|
@@ -2411,7 +2474,7 @@ const LogEntryHeader = reactExports.memo(function({
|
|
|
2411
2474
|
] }),
|
|
2412
2475
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0", children: [
|
|
2413
2476
|
/* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3" }),
|
|
2414
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", title: log.timestamp, children: formatTimestamp$
|
|
2477
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", title: log.timestamp, children: formatTimestamp$2(log.timestamp, timeDisplayFormat) })
|
|
2415
2478
|
] }),
|
|
2416
2479
|
log.model !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
|
|
2417
2480
|
/* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ProviderLogo, { provider: detectProvider(log.model), className: "size-4" }) }) }),
|
|
@@ -3505,10 +3568,53 @@ function resolveFocusedTab(tab, anatomySegments) {
|
|
|
3505
3568
|
return "request";
|
|
3506
3569
|
}
|
|
3507
3570
|
}
|
|
3571
|
+
const FULL_LOG_HYDRATION_TIMEOUT_MS = 15e3;
|
|
3572
|
+
const BODY_CHUNK_TIMEOUT_MS = 15e3;
|
|
3573
|
+
const BODY_CHUNK_BYTES = 256 * 1024;
|
|
3574
|
+
const CHUNKED_BODY_THRESHOLD_BYTES = 1024 * 1024;
|
|
3508
3575
|
const TAB_TRIGGER_CLASS = "h-9 flex-none rounded-none border-0 border-b-2 border-transparent bg-transparent px-3 text-xs font-semibold text-muted-foreground shadow-none hover:bg-cyan-400/[0.04] hover:text-foreground data-[state=active]:border-cyan-300 data-[state=active]:bg-transparent data-[state=active]:text-cyan-100 data-[state=active]:shadow-none";
|
|
3509
|
-
function
|
|
3576
|
+
function createEmptyBodyPreviewState(totalBytes = null) {
|
|
3577
|
+
return {
|
|
3578
|
+
status: "idle",
|
|
3579
|
+
text: "",
|
|
3580
|
+
offset: 0,
|
|
3581
|
+
totalBytes,
|
|
3582
|
+
hasMore: totalBytes !== null && totalBytes > 0,
|
|
3583
|
+
error: null
|
|
3584
|
+
};
|
|
3585
|
+
}
|
|
3586
|
+
function bodyByteCount(value) {
|
|
3587
|
+
return value ?? 0;
|
|
3588
|
+
}
|
|
3589
|
+
function isCompactBody(log) {
|
|
3510
3590
|
return log.bodyContentMode === "compact" || log.bodyContentMode === "truncated";
|
|
3511
3591
|
}
|
|
3592
|
+
function shouldUseChunkedBodyLoading(log) {
|
|
3593
|
+
if (!isCompactBody(log)) return false;
|
|
3594
|
+
return bodyByteCount(log.rawRequestBodyBytes) > CHUNKED_BODY_THRESHOLD_BYTES || bodyByteCount(log.responseTextBytes) > CHUNKED_BODY_THRESHOLD_BYTES;
|
|
3595
|
+
}
|
|
3596
|
+
function shouldHydrateLogBody(log) {
|
|
3597
|
+
return isCompactBody(log) && !shouldUseChunkedBodyLoading(log);
|
|
3598
|
+
}
|
|
3599
|
+
function formatBytes$1(bytes) {
|
|
3600
|
+
if (bytes < 1024) return `${bytes.toLocaleString()} B`;
|
|
3601
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KiB`;
|
|
3602
|
+
if (bytes < 1024 * 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MiB`;
|
|
3603
|
+
return `${(bytes / 1024 / 1024 / 1024).toFixed(1)} GiB`;
|
|
3604
|
+
}
|
|
3605
|
+
function bodyPreviewErrorMessage(error) {
|
|
3606
|
+
if (error instanceof ApiTimeoutError) {
|
|
3607
|
+
return "Body preview timed out. The log is still available on disk; try loading again.";
|
|
3608
|
+
}
|
|
3609
|
+
if (error instanceof Error) return error.message;
|
|
3610
|
+
return "Body preview could not be loaded.";
|
|
3611
|
+
}
|
|
3612
|
+
function bodyPreviewTextForView(enabled, state) {
|
|
3613
|
+
if (!enabled) return null;
|
|
3614
|
+
if (state.text !== "") return state.text;
|
|
3615
|
+
if (state.status === "success") return "";
|
|
3616
|
+
return null;
|
|
3617
|
+
}
|
|
3512
3618
|
function ExpandedPanel({
|
|
3513
3619
|
children,
|
|
3514
3620
|
className
|
|
@@ -3554,6 +3660,41 @@ function NoticeBlock({
|
|
|
3554
3660
|
}
|
|
3555
3661
|
);
|
|
3556
3662
|
}
|
|
3663
|
+
function BodyPreviewNotice({
|
|
3664
|
+
label,
|
|
3665
|
+
state,
|
|
3666
|
+
expectedBytes,
|
|
3667
|
+
onLoadMore
|
|
3668
|
+
}) {
|
|
3669
|
+
const totalBytes = state.totalBytes ?? expectedBytes;
|
|
3670
|
+
const loadedLabel = totalBytes === null ? formatBytes$1(state.offset) : `${formatBytes$1(state.offset)} / ${formatBytes$1(totalBytes)}`;
|
|
3671
|
+
const showLoadMore = state.status !== "loading" && state.hasMore;
|
|
3672
|
+
const actionLabel = state.status === "failed" ? "Retry" : "Load more";
|
|
3673
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3674
|
+
NoticeBlock,
|
|
3675
|
+
{
|
|
3676
|
+
tone: state.status === "failed" ? "warning" : "loading",
|
|
3677
|
+
title: `${label} body preview`,
|
|
3678
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
|
|
3679
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
|
|
3680
|
+
state.status === "idle" && totalBytes !== null ? `${label} body is ${formatBytes$1(totalBytes)}. Loading the first preview chunk.` : `Loaded ${loadedLabel}.`,
|
|
3681
|
+
state.status === "failed" && state.error !== null && ` ${state.error}`
|
|
3682
|
+
] }),
|
|
3683
|
+
showLoadMore && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3684
|
+
Button,
|
|
3685
|
+
{
|
|
3686
|
+
type: "button",
|
|
3687
|
+
variant: "outline",
|
|
3688
|
+
size: "sm",
|
|
3689
|
+
className: "h-7 border-white/10 bg-black/20 text-xs text-cyan-100 hover:border-cyan-300/35 hover:bg-cyan-400/10",
|
|
3690
|
+
onClick: onLoadMore,
|
|
3691
|
+
children: actionLabel
|
|
3692
|
+
}
|
|
3693
|
+
)
|
|
3694
|
+
] })
|
|
3695
|
+
}
|
|
3696
|
+
);
|
|
3697
|
+
}
|
|
3557
3698
|
function HeaderRows({
|
|
3558
3699
|
headers,
|
|
3559
3700
|
emptyLabel
|
|
@@ -3633,8 +3774,24 @@ const LogEntry = reactExports.memo(function({
|
|
|
3633
3774
|
const [expandToPath, setExpandToPath] = reactExports.useState(null);
|
|
3634
3775
|
const [hydratedLog, setHydratedLog] = reactExports.useState(null);
|
|
3635
3776
|
const [bodyHydrationState, setBodyHydrationState] = reactExports.useState("idle");
|
|
3777
|
+
const [requestPreview, setRequestPreview] = reactExports.useState(
|
|
3778
|
+
() => createEmptyBodyPreviewState(log.rawRequestBodyBytes ?? null)
|
|
3779
|
+
);
|
|
3780
|
+
const [responsePreview, setResponsePreview] = reactExports.useState(
|
|
3781
|
+
() => createEmptyBodyPreviewState(log.responseTextBytes ?? null)
|
|
3782
|
+
);
|
|
3636
3783
|
const requestJsonRef = reactExports.useRef(null);
|
|
3784
|
+
const logIdRef = reactExports.useRef(log.id);
|
|
3637
3785
|
const displayLog = hydratedLog ?? log;
|
|
3786
|
+
const useChunkedBody = hydratedLog === null && shouldUseChunkedBodyLoading(log);
|
|
3787
|
+
const requestPreviewBody = bodyPreviewTextForView(useChunkedBody, requestPreview);
|
|
3788
|
+
const responsePreviewBody = bodyPreviewTextForView(useChunkedBody, responsePreview);
|
|
3789
|
+
const rawRequestBodyForView = displayLog.rawRequestBody ?? requestPreviewBody;
|
|
3790
|
+
const responseTextForView = displayLog.responseText ?? responsePreviewBody;
|
|
3791
|
+
const requestPreviewIsActive = displayLog.rawRequestBody === null && requestPreviewBody !== null;
|
|
3792
|
+
const responsePreviewIsActive = displayLog.responseText === null && responsePreviewBody !== null;
|
|
3793
|
+
const requestPreviewShouldParse = !requestPreviewIsActive || requestPreview.hasMore === false && bodyByteCount(requestPreview.totalBytes) <= CHUNKED_BODY_THRESHOLD_BYTES;
|
|
3794
|
+
const responsePreviewShouldParse = !responsePreviewIsActive || responsePreview.hasMore === false && bodyByteCount(responsePreview.totalBytes) <= CHUNKED_BODY_THRESHOLD_BYTES;
|
|
3638
3795
|
const resolvedFormat = resolveLogFormat(displayLog);
|
|
3639
3796
|
const adapter = getLogFormatAdapter(resolvedFormat);
|
|
3640
3797
|
const requestAnalysis = reactExports.useMemo(
|
|
@@ -3651,10 +3808,16 @@ const LogEntry = reactExports.memo(function({
|
|
|
3651
3808
|
}
|
|
3652
3809
|
return stripClaudeCodeBillingHeader(displayLog.rawRequestBody).body;
|
|
3653
3810
|
}, [displayLog.rawRequestBody, resolvedFormat, strip]);
|
|
3654
|
-
const displayedRequestBody = strippedRequestBody ??
|
|
3655
|
-
const requestExpansion = useJsonBulkExpansion(
|
|
3656
|
-
|
|
3657
|
-
|
|
3811
|
+
const displayedRequestBody = strippedRequestBody ?? rawRequestBodyForView;
|
|
3812
|
+
const requestExpansion = useJsonBulkExpansion(
|
|
3813
|
+
requestPreviewShouldParse ? displayedRequestBody : null
|
|
3814
|
+
);
|
|
3815
|
+
const rawRequestExpansion = useJsonBulkExpansion(
|
|
3816
|
+
requestPreviewShouldParse ? rawRequestBodyForView : null
|
|
3817
|
+
);
|
|
3818
|
+
const responseExpansion = useJsonBulkExpansion(
|
|
3819
|
+
responsePreviewShouldParse ? responseTextForView : null
|
|
3820
|
+
);
|
|
3658
3821
|
const headersText = reactExports.useMemo(
|
|
3659
3822
|
() => displayLog.headers && Object.keys(displayLog.headers).length > 0 ? JSON.stringify(displayLog.headers, null, 2) : null,
|
|
3660
3823
|
[displayLog.headers]
|
|
@@ -3664,22 +3827,22 @@ const LogEntry = reactExports.memo(function({
|
|
|
3664
3827
|
[displayLog.rawHeaders]
|
|
3665
3828
|
);
|
|
3666
3829
|
const requestCopy = useCopyFeedback(displayedRequestBody);
|
|
3667
|
-
const rawRequestCopy = useCopyFeedback(
|
|
3830
|
+
const rawRequestCopy = useCopyFeedback(rawRequestBodyForView);
|
|
3668
3831
|
const headersCopy = useCopyFeedback(headersText);
|
|
3669
3832
|
const rawHeadersCopy = useCopyFeedback(rawHeadersText);
|
|
3670
|
-
const responseCopy = useCopyFeedback(
|
|
3833
|
+
const responseCopy = useCopyFeedback(responseTextForView);
|
|
3671
3834
|
const tabActions = reactExports.useMemo(
|
|
3672
3835
|
() => ({
|
|
3673
3836
|
request: {
|
|
3674
|
-
copyLabel: "Copy request body",
|
|
3837
|
+
copyLabel: requestPreviewIsActive ? "Copy loaded request preview" : "Copy request body",
|
|
3675
3838
|
copyText: displayedRequestBody,
|
|
3676
3839
|
copyCopied: requestCopy.copied,
|
|
3677
3840
|
onCopy: requestCopy.copy,
|
|
3678
|
-
expansion: {
|
|
3841
|
+
expansion: requestPreviewShouldParse ? {
|
|
3679
3842
|
isExpanded: requestExpansion.isExpanded,
|
|
3680
3843
|
isPending: requestExpansion.isPending,
|
|
3681
3844
|
onToggle: requestExpansion.toggle
|
|
3682
|
-
},
|
|
3845
|
+
} : null,
|
|
3683
3846
|
// "Diff with Raw" only makes sense when there's a raw body to compare
|
|
3684
3847
|
// against (Anthropic + strip pipeline produces one). "Diff with
|
|
3685
3848
|
// Previous" only exists when the parent wired up the compare drawer.
|
|
@@ -3694,15 +3857,15 @@ const LogEntry = reactExports.memo(function({
|
|
|
3694
3857
|
}
|
|
3695
3858
|
},
|
|
3696
3859
|
"raw-request": {
|
|
3697
|
-
copyLabel: "Copy raw request",
|
|
3698
|
-
copyText:
|
|
3860
|
+
copyLabel: requestPreviewIsActive ? "Copy loaded raw request preview" : "Copy raw request",
|
|
3861
|
+
copyText: rawRequestBodyForView,
|
|
3699
3862
|
copyCopied: rawRequestCopy.copied,
|
|
3700
3863
|
onCopy: rawRequestCopy.copy,
|
|
3701
|
-
expansion: {
|
|
3864
|
+
expansion: requestPreviewShouldParse ? {
|
|
3702
3865
|
isExpanded: rawRequestExpansion.isExpanded,
|
|
3703
3866
|
isPending: rawRequestExpansion.isPending,
|
|
3704
3867
|
onToggle: rawRequestExpansion.toggle
|
|
3705
|
-
}
|
|
3868
|
+
} : null
|
|
3706
3869
|
},
|
|
3707
3870
|
headers: {
|
|
3708
3871
|
copyLabel: "Copy headers",
|
|
@@ -3720,24 +3883,27 @@ const LogEntry = reactExports.memo(function({
|
|
|
3720
3883
|
expansion: null
|
|
3721
3884
|
},
|
|
3722
3885
|
raw: {
|
|
3723
|
-
copyLabel: "Copy response",
|
|
3724
|
-
copyText:
|
|
3886
|
+
copyLabel: responsePreviewIsActive ? "Copy loaded response preview" : "Copy response",
|
|
3887
|
+
copyText: responseTextForView,
|
|
3725
3888
|
copyCopied: responseCopy.copied,
|
|
3726
3889
|
onCopy: responseCopy.copy,
|
|
3727
|
-
expansion: {
|
|
3890
|
+
expansion: responsePreviewShouldParse ? {
|
|
3728
3891
|
isExpanded: responseExpansion.isExpanded,
|
|
3729
3892
|
isPending: responseExpansion.isPending,
|
|
3730
3893
|
onToggle: responseExpansion.toggle
|
|
3731
|
-
}
|
|
3894
|
+
} : null
|
|
3732
3895
|
}
|
|
3733
3896
|
}),
|
|
3734
3897
|
[
|
|
3735
3898
|
displayedRequestBody,
|
|
3899
|
+
requestPreviewIsActive,
|
|
3900
|
+
requestPreviewShouldParse,
|
|
3736
3901
|
requestCopy,
|
|
3737
3902
|
requestExpansion,
|
|
3738
3903
|
requestDiff,
|
|
3739
3904
|
displayLog,
|
|
3740
3905
|
displayLog.rawRequestBody,
|
|
3906
|
+
rawRequestBodyForView,
|
|
3741
3907
|
rawRequestCopy,
|
|
3742
3908
|
rawRequestExpansion,
|
|
3743
3909
|
headersText,
|
|
@@ -3745,6 +3911,9 @@ const LogEntry = reactExports.memo(function({
|
|
|
3745
3911
|
rawHeadersText,
|
|
3746
3912
|
rawHeadersCopy,
|
|
3747
3913
|
displayLog.responseText,
|
|
3914
|
+
responsePreviewIsActive,
|
|
3915
|
+
responsePreviewShouldParse,
|
|
3916
|
+
responseTextForView,
|
|
3748
3917
|
responseCopy,
|
|
3749
3918
|
responseExpansion,
|
|
3750
3919
|
resolvedFormat,
|
|
@@ -3773,6 +3942,44 @@ const LogEntry = reactExports.memo(function({
|
|
|
3773
3942
|
if (activeTab !== "request") setActiveTab("request");
|
|
3774
3943
|
}
|
|
3775
3944
|
});
|
|
3945
|
+
const loadBodyChunk = reactExports.useCallback(
|
|
3946
|
+
(part, offset) => {
|
|
3947
|
+
const setPreview = part === "request" ? setRequestPreview : setResponsePreview;
|
|
3948
|
+
setPreview((current) => ({
|
|
3949
|
+
...current,
|
|
3950
|
+
status: "loading",
|
|
3951
|
+
error: null
|
|
3952
|
+
}));
|
|
3953
|
+
const params = new URLSearchParams({
|
|
3954
|
+
part,
|
|
3955
|
+
offset: String(offset),
|
|
3956
|
+
limit: String(BODY_CHUNK_BYTES)
|
|
3957
|
+
});
|
|
3958
|
+
void fetchJsonWithTimeout(
|
|
3959
|
+
`/api/logs/${String(log.id)}/body?${params.toString()}`,
|
|
3960
|
+
LogBodyChunkSchema,
|
|
3961
|
+
BODY_CHUNK_TIMEOUT_MS
|
|
3962
|
+
).then((chunk) => {
|
|
3963
|
+
if (logIdRef.current !== log.id) return;
|
|
3964
|
+
setPreview((current) => ({
|
|
3965
|
+
status: "success",
|
|
3966
|
+
text: chunk.offset === 0 ? chunk.text : `${current.text}${chunk.text}`,
|
|
3967
|
+
offset: chunk.nextOffset ?? chunk.totalBytes,
|
|
3968
|
+
totalBytes: chunk.totalBytes,
|
|
3969
|
+
hasMore: chunk.hasMore,
|
|
3970
|
+
error: null
|
|
3971
|
+
}));
|
|
3972
|
+
}).catch((error) => {
|
|
3973
|
+
if (logIdRef.current !== log.id) return;
|
|
3974
|
+
setPreview((current) => ({
|
|
3975
|
+
...current,
|
|
3976
|
+
status: "failed",
|
|
3977
|
+
error: bodyPreviewErrorMessage(error)
|
|
3978
|
+
}));
|
|
3979
|
+
});
|
|
3980
|
+
},
|
|
3981
|
+
[log.id]
|
|
3982
|
+
);
|
|
3776
3983
|
const handleToggleExpanded = reactExports.useCallback(() => {
|
|
3777
3984
|
const nextExpanded = !expanded;
|
|
3778
3985
|
if (nextExpanded) {
|
|
@@ -3781,16 +3988,23 @@ const LogEntry = reactExports.memo(function({
|
|
|
3781
3988
|
setExpanded(nextExpanded);
|
|
3782
3989
|
}, [anatomySegments, expanded]);
|
|
3783
3990
|
reactExports.useEffect(() => {
|
|
3991
|
+
logIdRef.current = log.id;
|
|
3784
3992
|
setHydratedLog(null);
|
|
3785
3993
|
setBodyHydrationState("idle");
|
|
3786
|
-
|
|
3994
|
+
setRequestPreview(createEmptyBodyPreviewState(log.rawRequestBodyBytes ?? null));
|
|
3995
|
+
setResponsePreview(createEmptyBodyPreviewState(log.responseTextBytes ?? null));
|
|
3996
|
+
}, [log.id, log.rawRequestBodyBytes, log.responseTextBytes]);
|
|
3787
3997
|
reactExports.useEffect(() => {
|
|
3788
3998
|
if (!expanded) return;
|
|
3789
3999
|
if (!shouldHydrateLogBody(log)) return;
|
|
3790
4000
|
if (hydratedLog !== null || bodyHydrationState !== "idle") return;
|
|
3791
4001
|
let cancelled = false;
|
|
3792
4002
|
setBodyHydrationState("loading");
|
|
3793
|
-
void
|
|
4003
|
+
void fetchJsonWithTimeout(
|
|
4004
|
+
`/api/logs/${String(log.id)}`,
|
|
4005
|
+
CapturedLogSchema,
|
|
4006
|
+
FULL_LOG_HYDRATION_TIMEOUT_MS
|
|
4007
|
+
).then((fullLog) => {
|
|
3794
4008
|
if (cancelled) return;
|
|
3795
4009
|
setHydratedLog(fullLog);
|
|
3796
4010
|
setBodyHydrationState("idle");
|
|
@@ -3801,7 +4015,29 @@ const LogEntry = reactExports.memo(function({
|
|
|
3801
4015
|
return () => {
|
|
3802
4016
|
cancelled = true;
|
|
3803
4017
|
};
|
|
3804
|
-
}, [
|
|
4018
|
+
}, [
|
|
4019
|
+
bodyHydrationState,
|
|
4020
|
+
expanded,
|
|
4021
|
+
hydratedLog,
|
|
4022
|
+
log.bodyContentMode,
|
|
4023
|
+
log.id,
|
|
4024
|
+
log.rawRequestBodyBytes,
|
|
4025
|
+
log.responseTextBytes
|
|
4026
|
+
]);
|
|
4027
|
+
reactExports.useEffect(() => {
|
|
4028
|
+
if (!expanded) return;
|
|
4029
|
+
if (!useChunkedBody) return;
|
|
4030
|
+
if (requestPreview.status !== "idle") return;
|
|
4031
|
+
if (activeTab !== "request" && activeTab !== "raw-request" && activeTab !== "anatomy") return;
|
|
4032
|
+
loadBodyChunk("request", 0);
|
|
4033
|
+
}, [activeTab, expanded, loadBodyChunk, requestPreview.status, useChunkedBody]);
|
|
4034
|
+
reactExports.useEffect(() => {
|
|
4035
|
+
if (!expanded) return;
|
|
4036
|
+
if (!useChunkedBody) return;
|
|
4037
|
+
if (responsePreview.status !== "idle") return;
|
|
4038
|
+
if (activeTab !== "parsed" && activeTab !== "raw") return;
|
|
4039
|
+
loadBodyChunk("response", 0);
|
|
4040
|
+
}, [activeTab, expanded, loadBodyChunk, responsePreview.status, useChunkedBody]);
|
|
3805
4041
|
reactExports.useEffect(() => {
|
|
3806
4042
|
const handleLogFocusRequest = (event) => {
|
|
3807
4043
|
const request = readLogFocusRequest(event);
|
|
@@ -3865,17 +4101,37 @@ const LogEntry = reactExports.memo(function({
|
|
|
3865
4101
|
]
|
|
3866
4102
|
}
|
|
3867
4103
|
),
|
|
3868
|
-
shouldShowRawRequestTab(resolvedFormat, viewMode, strip) && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw-request", children: activeTab === "raw-request" && /* @__PURE__ */ jsxRuntimeExports.
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
4104
|
+
shouldShowRawRequestTab(resolvedFormat, viewMode, strip) && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw-request", children: activeTab === "raw-request" && /* @__PURE__ */ jsxRuntimeExports.jsxs(ExpandedPanel, { children: [
|
|
4105
|
+
useChunkedBody && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4106
|
+
BodyPreviewNotice,
|
|
4107
|
+
{
|
|
4108
|
+
label: "Request",
|
|
4109
|
+
state: requestPreview,
|
|
4110
|
+
expectedBytes: log.rawRequestBodyBytes ?? null,
|
|
4111
|
+
onLoadMore: () => loadBodyChunk("request", requestPreview.offset)
|
|
4112
|
+
}
|
|
4113
|
+
),
|
|
4114
|
+
rawRequestBodyForView === null ? /* @__PURE__ */ jsxRuntimeExports.jsx(EmptyState, { children: useChunkedBody ? "Request preview is loading" : "No request body" }) : rawRequestExpansion.parsedData !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx(TabFallback, {}), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4115
|
+
LazyJsonViewer,
|
|
4116
|
+
{
|
|
4117
|
+
data: rawRequestExpansion.parsedData,
|
|
4118
|
+
bulkDepth: rawRequestExpansion.bulkDepth,
|
|
4119
|
+
bulkRevision: rawRequestExpansion.bulkRevision
|
|
4120
|
+
}
|
|
4121
|
+
) }) : /* @__PURE__ */ jsxRuntimeExports.jsx(RawTextBlock, { children: rawRequestBodyForView })
|
|
4122
|
+
] }) }),
|
|
3876
4123
|
/* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "request", children: activeTab === "request" && /* @__PURE__ */ jsxRuntimeExports.jsxs(ExpandedPanel, { children: [
|
|
3877
4124
|
bodyHydrationState === "loading" && /* @__PURE__ */ jsxRuntimeExports.jsx(NoticeBlock, { tone: "loading", children: "Loading full request and response bodies..." }),
|
|
3878
4125
|
bodyHydrationState === "failed" && /* @__PURE__ */ jsxRuntimeExports.jsx(NoticeBlock, { tone: "warning", children: "Full request and response bodies could not be loaded. Compact metadata is still available." }),
|
|
4126
|
+
useChunkedBody && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4127
|
+
BodyPreviewNotice,
|
|
4128
|
+
{
|
|
4129
|
+
label: "Request",
|
|
4130
|
+
state: requestPreview,
|
|
4131
|
+
expectedBytes: log.rawRequestBodyBytes ?? null,
|
|
4132
|
+
onLoadMore: () => loadBodyChunk("request", requestPreview.offset)
|
|
4133
|
+
}
|
|
4134
|
+
),
|
|
3879
4135
|
warnings.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(NoticeBlock, { tone: "warning", children: [
|
|
3880
4136
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 font-semibold text-amber-300", children: [
|
|
3881
4137
|
/* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { className: "size-3.5" }),
|
|
@@ -3897,7 +4153,7 @@ const LogEntry = reactExports.memo(function({
|
|
|
3897
4153
|
displayedBody: displayedRequestBody,
|
|
3898
4154
|
emptyLabel: "No transformation applied; raw and sent request bodies are identical."
|
|
3899
4155
|
}
|
|
3900
|
-
) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: requestJsonRef, children: displayedRequestBody === null ? /* @__PURE__ */ jsxRuntimeExports.jsx(EmptyState, { children: "No request body" }) : requestExpansion.parsedData !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx(TabFallback, {}), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4156
|
+
) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: requestJsonRef, children: displayedRequestBody === null ? /* @__PURE__ */ jsxRuntimeExports.jsx(EmptyState, { children: useChunkedBody ? "Request preview is loading" : "No request body" }) : requestExpansion.parsedData !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx(TabFallback, {}), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3901
4157
|
LazyJsonViewer,
|
|
3902
4158
|
{
|
|
3903
4159
|
data: requestExpansion.parsedData,
|
|
@@ -3949,16 +4205,25 @@ const LogEntry = reactExports.memo(function({
|
|
|
3949
4205
|
}
|
|
3950
4206
|
) }) }),
|
|
3951
4207
|
/* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw", children: activeTab === "raw" && /* @__PURE__ */ jsxRuntimeExports.jsxs(ExpandedPanel, { className: "space-y-3", children: [
|
|
4208
|
+
useChunkedBody && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4209
|
+
BodyPreviewNotice,
|
|
4210
|
+
{
|
|
4211
|
+
label: "Response",
|
|
4212
|
+
state: responsePreview,
|
|
4213
|
+
expectedBytes: log.responseTextBytes ?? null,
|
|
4214
|
+
onLoadMore: () => loadBodyChunk("response", responsePreview.offset)
|
|
4215
|
+
}
|
|
4216
|
+
),
|
|
3952
4217
|
displayLog.error !== void 0 && displayLog.error !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(NoticeBlock, { tone: "danger", title: "SSE Error", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono", children: displayLog.error }) }),
|
|
3953
|
-
|
|
4218
|
+
responseTextForView !== null && responsePreviewShouldParse ? /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx(TabFallback, {}), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3954
4219
|
LazyJsonViewerFromString,
|
|
3955
4220
|
{
|
|
3956
|
-
text:
|
|
4221
|
+
text: responseTextForView,
|
|
3957
4222
|
defaultExpandDepth: 0,
|
|
3958
4223
|
bulkDepth: responseExpansion.bulkDepth,
|
|
3959
4224
|
bulkRevision: responseExpansion.bulkRevision
|
|
3960
4225
|
}
|
|
3961
|
-
) }) : /* @__PURE__ */ jsxRuntimeExports.jsx(EmptyState, { children: "No response" }),
|
|
4226
|
+
) }) : responseTextForView !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(RawTextBlock, { children: responseTextForView }) : /* @__PURE__ */ jsxRuntimeExports.jsx(EmptyState, { children: useChunkedBody ? "Response preview is loading" : "No response" }),
|
|
3962
4227
|
displayLog.streaming === true && /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx(TabFallback, {}), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3963
4228
|
LazyStreamingChunkSequence,
|
|
3964
4229
|
{
|
|
@@ -3967,20 +4232,31 @@ const LogEntry = reactExports.memo(function({
|
|
|
3967
4232
|
}
|
|
3968
4233
|
) })
|
|
3969
4234
|
] }) }),
|
|
3970
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "parsed", children: activeTab === "parsed" && /* @__PURE__ */ jsxRuntimeExports.
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
4235
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "parsed", children: activeTab === "parsed" && /* @__PURE__ */ jsxRuntimeExports.jsxs(ExpandedPanel, { children: [
|
|
4236
|
+
useChunkedBody && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4237
|
+
BodyPreviewNotice,
|
|
4238
|
+
{
|
|
4239
|
+
label: "Response",
|
|
4240
|
+
state: responsePreview,
|
|
4241
|
+
expectedBytes: log.responseTextBytes ?? null,
|
|
4242
|
+
onLoadMore: () => loadBodyChunk("response", responsePreview.offset)
|
|
4243
|
+
}
|
|
4244
|
+
),
|
|
4245
|
+
responsePreviewIsActive && !responsePreviewShouldParse ? /* @__PURE__ */ jsxRuntimeExports.jsx(RawTextBlock, { children: responseTextForView }) : /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx(TabFallback, {}), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4246
|
+
LazyResponseView,
|
|
4247
|
+
{
|
|
4248
|
+
responseText: responseTextForView,
|
|
4249
|
+
responseStatus: displayLog.responseStatus,
|
|
4250
|
+
streaming: displayLog.streaming,
|
|
4251
|
+
inputTokens: displayLog.inputTokens,
|
|
4252
|
+
outputTokens: displayLog.outputTokens,
|
|
4253
|
+
cacheCreationInputTokens: displayLog.cacheCreationInputTokens,
|
|
4254
|
+
cacheReadInputTokens: displayLog.cacheReadInputTokens,
|
|
4255
|
+
apiFormat: resolvedFormat,
|
|
4256
|
+
error: displayLog.error
|
|
4257
|
+
}
|
|
4258
|
+
) })
|
|
4259
|
+
] }) })
|
|
3984
4260
|
] })
|
|
3985
4261
|
}
|
|
3986
4262
|
)
|
|
@@ -5562,12 +5838,562 @@ function SelectScrollDownButton({
|
|
|
5562
5838
|
}
|
|
5563
5839
|
);
|
|
5564
5840
|
}
|
|
5841
|
+
const ALERT_SUMMARY_FETCH_TIMEOUT_MS = 8e3;
|
|
5842
|
+
const ALERT_LIST_FETCH_TIMEOUT_MS = 1e4;
|
|
5843
|
+
function alertsPath(filters) {
|
|
5844
|
+
const params = new URLSearchParams();
|
|
5845
|
+
if (filters.severity !== void 0) params.set("severity", filters.severity);
|
|
5846
|
+
if (filters.category !== void 0) params.set("category", filters.category);
|
|
5847
|
+
if (filters.source !== void 0) params.set("source", filters.source);
|
|
5848
|
+
if (filters.limit !== void 0) params.set("limit", String(filters.limit));
|
|
5849
|
+
const query = params.toString();
|
|
5850
|
+
return query.length > 0 ? `/api/alerts?${query}` : "/api/alerts";
|
|
5851
|
+
}
|
|
5852
|
+
function useAlerts(filters, enabled) {
|
|
5853
|
+
const response = useSWR(
|
|
5854
|
+
enabled ? alertsPath(filters) : null,
|
|
5855
|
+
(url) => fetchJsonWithTimeout(url, AlertListResponseSchema, ALERT_LIST_FETCH_TIMEOUT_MS)
|
|
5856
|
+
);
|
|
5857
|
+
return {
|
|
5858
|
+
alerts: response.data?.alerts ?? [],
|
|
5859
|
+
total: response.data?.total ?? 0,
|
|
5860
|
+
summary: response.data?.summary ?? null,
|
|
5861
|
+
isLoading: response.isLoading,
|
|
5862
|
+
error: response.error,
|
|
5863
|
+
mutate: response.mutate
|
|
5864
|
+
};
|
|
5865
|
+
}
|
|
5866
|
+
function useAlertSummary() {
|
|
5867
|
+
const response = useSWR(
|
|
5868
|
+
"/api/alerts/summary",
|
|
5869
|
+
(url) => fetchJsonWithTimeout(url, AlertSummarySchema, ALERT_SUMMARY_FETCH_TIMEOUT_MS),
|
|
5870
|
+
{
|
|
5871
|
+
refreshInterval: 5e3,
|
|
5872
|
+
revalidateOnFocus: true
|
|
5873
|
+
}
|
|
5874
|
+
);
|
|
5875
|
+
return {
|
|
5876
|
+
summary: response.data ?? null,
|
|
5877
|
+
isLoading: response.isLoading,
|
|
5878
|
+
error: response.error,
|
|
5879
|
+
mutate: response.mutate
|
|
5880
|
+
};
|
|
5881
|
+
}
|
|
5882
|
+
const ALERT_CATEGORIES = [
|
|
5883
|
+
"request-failure",
|
|
5884
|
+
"request-timeout",
|
|
5885
|
+
"slow-response",
|
|
5886
|
+
"tool-schema",
|
|
5887
|
+
"run-failure",
|
|
5888
|
+
"group-failure",
|
|
5889
|
+
"group-incomplete"
|
|
5890
|
+
];
|
|
5891
|
+
function severityLabel(severity) {
|
|
5892
|
+
switch (severity) {
|
|
5893
|
+
case "critical":
|
|
5894
|
+
return "Critical";
|
|
5895
|
+
case "warning":
|
|
5896
|
+
return "Warning";
|
|
5897
|
+
case "notice":
|
|
5898
|
+
return "Notice";
|
|
5899
|
+
}
|
|
5900
|
+
}
|
|
5901
|
+
function sourceLabel(source) {
|
|
5902
|
+
switch (source) {
|
|
5903
|
+
case "proxy":
|
|
5904
|
+
return "Proxy";
|
|
5905
|
+
case "tool-schema":
|
|
5906
|
+
return "Tool schema";
|
|
5907
|
+
case "run":
|
|
5908
|
+
return "Run";
|
|
5909
|
+
case "group":
|
|
5910
|
+
return "Group";
|
|
5911
|
+
}
|
|
5912
|
+
}
|
|
5913
|
+
function categoryLabel(category) {
|
|
5914
|
+
switch (category) {
|
|
5915
|
+
case "request-failure":
|
|
5916
|
+
return "Request failure";
|
|
5917
|
+
case "request-timeout":
|
|
5918
|
+
return "Timeout";
|
|
5919
|
+
case "slow-response":
|
|
5920
|
+
return "Slow response";
|
|
5921
|
+
case "tool-schema":
|
|
5922
|
+
return "Tool calling";
|
|
5923
|
+
case "run-failure":
|
|
5924
|
+
return "Run failure";
|
|
5925
|
+
case "group-failure":
|
|
5926
|
+
return "Group failure";
|
|
5927
|
+
case "group-incomplete":
|
|
5928
|
+
return "Group incomplete";
|
|
5929
|
+
}
|
|
5930
|
+
}
|
|
5931
|
+
function severityAccent(severity) {
|
|
5932
|
+
switch (severity) {
|
|
5933
|
+
case "critical":
|
|
5934
|
+
return "border-red-400/40 bg-red-500/12 text-red-100";
|
|
5935
|
+
case "warning":
|
|
5936
|
+
return "border-amber-300/35 bg-amber-400/12 text-amber-100";
|
|
5937
|
+
case "notice":
|
|
5938
|
+
return "border-sky-300/35 bg-sky-400/12 text-sky-100";
|
|
5939
|
+
}
|
|
5940
|
+
}
|
|
5941
|
+
function severityDot(severity) {
|
|
5942
|
+
switch (severity) {
|
|
5943
|
+
case "critical":
|
|
5944
|
+
return "bg-red-400";
|
|
5945
|
+
case "warning":
|
|
5946
|
+
return "bg-amber-300";
|
|
5947
|
+
case "notice":
|
|
5948
|
+
return "bg-sky-300";
|
|
5949
|
+
}
|
|
5950
|
+
}
|
|
5951
|
+
function severityText(severity) {
|
|
5952
|
+
switch (severity) {
|
|
5953
|
+
case "critical":
|
|
5954
|
+
return "text-red-200";
|
|
5955
|
+
case "warning":
|
|
5956
|
+
return "text-amber-100";
|
|
5957
|
+
case "notice":
|
|
5958
|
+
return "text-sky-100";
|
|
5959
|
+
}
|
|
5960
|
+
}
|
|
5961
|
+
function categoryIcon(category) {
|
|
5962
|
+
switch (category) {
|
|
5963
|
+
case "request-failure":
|
|
5964
|
+
case "run-failure":
|
|
5965
|
+
case "group-failure":
|
|
5966
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(ShieldAlert, { className: "size-3.5" });
|
|
5967
|
+
case "request-timeout":
|
|
5968
|
+
case "slow-response":
|
|
5969
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(Clock3, { className: "size-3.5" });
|
|
5970
|
+
case "tool-schema":
|
|
5971
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { className: "size-3.5" });
|
|
5972
|
+
case "group-incomplete":
|
|
5973
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(Bell, { className: "size-3.5" });
|
|
5974
|
+
}
|
|
5975
|
+
}
|
|
5976
|
+
function formatTimestamp$1(value) {
|
|
5977
|
+
const millis = Date.parse(value);
|
|
5978
|
+
if (!Number.isFinite(millis)) return value;
|
|
5979
|
+
return new Intl.DateTimeFormat(void 0, {
|
|
5980
|
+
month: "short",
|
|
5981
|
+
day: "2-digit",
|
|
5982
|
+
hour: "2-digit",
|
|
5983
|
+
minute: "2-digit",
|
|
5984
|
+
hour12: false
|
|
5985
|
+
}).format(new Date(millis));
|
|
5986
|
+
}
|
|
5987
|
+
function formatCount(value) {
|
|
5988
|
+
return value.toLocaleString();
|
|
5989
|
+
}
|
|
5990
|
+
function providerModel$1(alert) {
|
|
5991
|
+
if (alert.provider !== null && alert.model !== null) return `${alert.provider} / ${alert.model}`;
|
|
5992
|
+
if (alert.provider !== null) return alert.provider;
|
|
5993
|
+
if (alert.model !== null) return alert.model;
|
|
5994
|
+
return "Unknown target";
|
|
5995
|
+
}
|
|
5996
|
+
function filtersFrom(severity, category) {
|
|
5997
|
+
const filters = { limit: 100 };
|
|
5998
|
+
if (severity !== "all") filters.severity = severity;
|
|
5999
|
+
if (category !== "all") filters.category = category;
|
|
6000
|
+
return filters;
|
|
6001
|
+
}
|
|
6002
|
+
function selectedAlertFrom(alerts, selectedAlertId) {
|
|
6003
|
+
if (selectedAlertId !== null) {
|
|
6004
|
+
const selected = alerts.find((alert) => alert.id === selectedAlertId);
|
|
6005
|
+
if (selected !== void 0) return selected;
|
|
6006
|
+
}
|
|
6007
|
+
return alerts[0] ?? null;
|
|
6008
|
+
}
|
|
6009
|
+
function AlertSummaryPill({
|
|
6010
|
+
label,
|
|
6011
|
+
count,
|
|
6012
|
+
tone
|
|
6013
|
+
}) {
|
|
6014
|
+
const toneClass = tone === "critical" ? "border-red-400/30 bg-red-500/10 text-red-100" : tone === "warning" ? "border-amber-300/30 bg-amber-400/10 text-amber-100" : tone === "notice" ? "border-sky-300/30 bg-sky-400/10 text-sky-100" : "border-border bg-muted/40 text-foreground";
|
|
6015
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cn("rounded-md border px-3 py-2", toneClass), children: [
|
|
6016
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] font-semibold uppercase opacity-75", children: label }),
|
|
6017
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 text-lg font-semibold leading-none", children: formatCount(count) })
|
|
6018
|
+
] });
|
|
6019
|
+
}
|
|
6020
|
+
function SeverityFilterButton({
|
|
6021
|
+
value,
|
|
6022
|
+
active,
|
|
6023
|
+
label,
|
|
6024
|
+
count,
|
|
6025
|
+
onClick
|
|
6026
|
+
}) {
|
|
6027
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
6028
|
+
Button,
|
|
6029
|
+
{
|
|
6030
|
+
type: "button",
|
|
6031
|
+
variant: active ? "secondary" : "ghost",
|
|
6032
|
+
size: "sm",
|
|
6033
|
+
"aria-pressed": active,
|
|
6034
|
+
onClick: () => onClick(value),
|
|
6035
|
+
className: "h-7 px-2",
|
|
6036
|
+
children: [
|
|
6037
|
+
label,
|
|
6038
|
+
count !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", children: formatCount(count) }) : null
|
|
6039
|
+
]
|
|
6040
|
+
}
|
|
6041
|
+
);
|
|
6042
|
+
}
|
|
6043
|
+
function CategoryFilterButton({
|
|
6044
|
+
category,
|
|
6045
|
+
active,
|
|
6046
|
+
count,
|
|
6047
|
+
onClick
|
|
6048
|
+
}) {
|
|
6049
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
6050
|
+
Button,
|
|
6051
|
+
{
|
|
6052
|
+
type: "button",
|
|
6053
|
+
variant: active ? "secondary" : "ghost",
|
|
6054
|
+
size: "sm",
|
|
6055
|
+
"aria-pressed": active,
|
|
6056
|
+
onClick: () => onClick(category),
|
|
6057
|
+
className: "h-7 px-2",
|
|
6058
|
+
children: [
|
|
6059
|
+
categoryIcon(category),
|
|
6060
|
+
categoryLabel(category),
|
|
6061
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", children: formatCount(count) })
|
|
6062
|
+
]
|
|
6063
|
+
}
|
|
6064
|
+
);
|
|
6065
|
+
}
|
|
6066
|
+
function AlertList({
|
|
6067
|
+
alerts,
|
|
6068
|
+
selectedAlertId,
|
|
6069
|
+
isLoading,
|
|
6070
|
+
error,
|
|
6071
|
+
onSelect
|
|
6072
|
+
}) {
|
|
6073
|
+
if (isLoading) {
|
|
6074
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-h-56 items-center justify-center rounded-md border border-dashed border-border text-xs text-muted-foreground", children: [
|
|
6075
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "mr-2 size-3.5 animate-spin" }),
|
|
6076
|
+
"Loading alerts..."
|
|
6077
|
+
] });
|
|
6078
|
+
}
|
|
6079
|
+
if (error !== void 0) {
|
|
6080
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded-md border border-red-300/30 bg-red-500/10 px-3 py-2 text-xs text-red-100", children: error.message });
|
|
6081
|
+
}
|
|
6082
|
+
if (alerts.length === 0) {
|
|
6083
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex min-h-56 items-center justify-center rounded-md border border-dashed border-border px-4 text-center text-xs text-muted-foreground", children: "No alerts match the current filters." });
|
|
6084
|
+
}
|
|
6085
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "min-h-0 space-y-2 overflow-y-auto pr-1", children: alerts.map((alert) => {
|
|
6086
|
+
const selected = alert.id === selectedAlertId;
|
|
6087
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
6088
|
+
"button",
|
|
6089
|
+
{
|
|
6090
|
+
type: "button",
|
|
6091
|
+
onClick: () => onSelect(alert.id),
|
|
6092
|
+
className: cn(
|
|
6093
|
+
"w-full rounded-md border px-3 py-3 text-left transition-colors",
|
|
6094
|
+
selected ? "border-primary/60 bg-primary/12" : "border-border bg-background/70 hover:border-primary/30 hover:bg-muted/60"
|
|
6095
|
+
),
|
|
6096
|
+
children: [
|
|
6097
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start justify-between gap-3", children: [
|
|
6098
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
|
|
6099
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
6100
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: cn("size-2 rounded-full", severityDot(alert.severity)) }),
|
|
6101
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: cn("text-xs font-semibold", severityText(alert.severity)), children: severityLabel(alert.severity) }),
|
|
6102
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate text-xs text-muted-foreground", children: categoryLabel(alert.category) })
|
|
6103
|
+
] }),
|
|
6104
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 truncate text-sm font-semibold", children: alert.title }),
|
|
6105
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 line-clamp-2 text-xs text-muted-foreground", children: alert.message })
|
|
6106
|
+
] }),
|
|
6107
|
+
alert.count > 1 ? /* @__PURE__ */ jsxRuntimeExports.jsxs(Badge, { variant: "outline", className: "shrink-0", children: [
|
|
6108
|
+
"x",
|
|
6109
|
+
formatCount(alert.count)
|
|
6110
|
+
] }) : null
|
|
6111
|
+
] }),
|
|
6112
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2 flex min-w-0 items-center justify-between gap-2 text-[11px] text-muted-foreground", children: [
|
|
6113
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: providerModel$1(alert) }),
|
|
6114
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0", children: formatTimestamp$1(alert.lastSeenAt) })
|
|
6115
|
+
] })
|
|
6116
|
+
]
|
|
6117
|
+
},
|
|
6118
|
+
alert.id
|
|
6119
|
+
);
|
|
6120
|
+
}) });
|
|
6121
|
+
}
|
|
6122
|
+
function AlertEvidenceLinks({
|
|
6123
|
+
alert,
|
|
6124
|
+
onNavigate
|
|
6125
|
+
}) {
|
|
6126
|
+
if (alert.evidence.length === 0) {
|
|
6127
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-muted-foreground", children: "No linked evidence" });
|
|
6128
|
+
}
|
|
6129
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-wrap gap-2", children: alert.evidence.map((link) => {
|
|
6130
|
+
if (link.href === null) {
|
|
6131
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", children: link.label }, `${link.kind}:${link.id}`);
|
|
6132
|
+
}
|
|
6133
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx("a", { href: link.href, onClick: onNavigate, children: link.label }) }, `${link.kind}:${link.id}`);
|
|
6134
|
+
}) });
|
|
6135
|
+
}
|
|
6136
|
+
function AlertDetails({
|
|
6137
|
+
alert,
|
|
6138
|
+
onNavigate
|
|
6139
|
+
}) {
|
|
6140
|
+
if (alert === null) {
|
|
6141
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex min-h-56 items-center justify-center rounded-md border border-dashed border-border px-4 text-center text-xs text-muted-foreground", children: "Select an alert to inspect its evidence." });
|
|
6142
|
+
}
|
|
6143
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-h-0 overflow-y-auto rounded-md border border-border bg-background/70", children: [
|
|
6144
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: cn("border-b px-4 py-3", severityAccent(alert.severity)), children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start justify-between gap-3", children: [
|
|
6145
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
|
|
6146
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
6147
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", children: severityLabel(alert.severity) }),
|
|
6148
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", children: sourceLabel(alert.source) }),
|
|
6149
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", children: categoryLabel(alert.category) })
|
|
6150
|
+
] }),
|
|
6151
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "mt-3 text-base font-semibold", children: alert.title }),
|
|
6152
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mt-1 text-sm opacity-85", children: alert.message })
|
|
6153
|
+
] }),
|
|
6154
|
+
alert.count > 1 ? /* @__PURE__ */ jsxRuntimeExports.jsxs(Badge, { variant: "outline", className: "shrink-0", children: [
|
|
6155
|
+
formatCount(alert.count),
|
|
6156
|
+
" hits"
|
|
6157
|
+
] }) : null
|
|
6158
|
+
] }) }),
|
|
6159
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4 p-4", children: [
|
|
6160
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid gap-2 text-xs sm:grid-cols-2", children: [
|
|
6161
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(DetailRow, { label: "Target", value: providerModel$1(alert) }),
|
|
6162
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(DetailRow, { label: "Last seen", value: formatTimestamp$1(alert.lastSeenAt) }),
|
|
6163
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(DetailRow, { label: "First seen", value: formatTimestamp$1(alert.firstSeenAt) }),
|
|
6164
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(DetailRow, { label: "Protocol", value: alert.apiFormat ?? "n/a" })
|
|
6165
|
+
] }),
|
|
6166
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
|
|
6167
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mb-2 text-xs font-semibold text-muted-foreground", children: "Evidence" }),
|
|
6168
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(AlertEvidenceLinks, { alert, onNavigate })
|
|
6169
|
+
] }),
|
|
6170
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid gap-2 text-xs sm:grid-cols-2", children: [
|
|
6171
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
6172
|
+
DetailRow,
|
|
6173
|
+
{
|
|
6174
|
+
label: "Logs",
|
|
6175
|
+
value: alert.logIds.length === 0 ? "n/a" : formatCount(alert.logIds.length)
|
|
6176
|
+
}
|
|
6177
|
+
),
|
|
6178
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
6179
|
+
DetailRow,
|
|
6180
|
+
{
|
|
6181
|
+
label: "Sessions",
|
|
6182
|
+
value: alert.sessionIds.length === 0 ? "n/a" : formatCount(alert.sessionIds.length)
|
|
6183
|
+
}
|
|
6184
|
+
),
|
|
6185
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
6186
|
+
DetailRow,
|
|
6187
|
+
{
|
|
6188
|
+
label: "Runs",
|
|
6189
|
+
value: alert.runIds.length === 0 ? "n/a" : formatCount(alert.runIds.length)
|
|
6190
|
+
}
|
|
6191
|
+
),
|
|
6192
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
6193
|
+
DetailRow,
|
|
6194
|
+
{
|
|
6195
|
+
label: "Groups",
|
|
6196
|
+
value: alert.groupIds.length === 0 ? "n/a" : formatCount(alert.groupIds.length)
|
|
6197
|
+
}
|
|
6198
|
+
)
|
|
6199
|
+
] })
|
|
6200
|
+
] })
|
|
6201
|
+
] });
|
|
6202
|
+
}
|
|
6203
|
+
function DetailRow({ label, value }) {
|
|
6204
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-md border border-border bg-muted/30 px-3 py-2", children: [
|
|
6205
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] font-semibold uppercase text-muted-foreground", children: label }),
|
|
6206
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 truncate font-mono text-[11px] text-foreground", children: value })
|
|
6207
|
+
] });
|
|
6208
|
+
}
|
|
6209
|
+
function AlertsDialog() {
|
|
6210
|
+
const [open, setOpen] = reactExports.useState(false);
|
|
6211
|
+
const [severityFilter, setSeverityFilter] = reactExports.useState("all");
|
|
6212
|
+
const [categoryFilter, setCategoryFilter] = reactExports.useState("all");
|
|
6213
|
+
const [selectedAlertId, setSelectedAlertId] = reactExports.useState(null);
|
|
6214
|
+
const { summary, isLoading: summaryLoading } = useAlertSummary();
|
|
6215
|
+
const filters = reactExports.useMemo(
|
|
6216
|
+
() => filtersFrom(severityFilter, categoryFilter),
|
|
6217
|
+
[categoryFilter, severityFilter]
|
|
6218
|
+
);
|
|
6219
|
+
const { alerts, total, isLoading, error, mutate } = useAlerts(filters, open);
|
|
6220
|
+
const selectedAlert = reactExports.useMemo(
|
|
6221
|
+
() => selectedAlertFrom(alerts, selectedAlertId),
|
|
6222
|
+
[alerts, selectedAlertId]
|
|
6223
|
+
);
|
|
6224
|
+
reactExports.useEffect(() => {
|
|
6225
|
+
if (!open) return;
|
|
6226
|
+
const selectedExists = selectedAlertId !== null && alerts.some((alert) => alert.id === selectedAlertId);
|
|
6227
|
+
if (selectedExists) return;
|
|
6228
|
+
setSelectedAlertId(alerts[0]?.id ?? null);
|
|
6229
|
+
}, [alerts, open, selectedAlertId]);
|
|
6230
|
+
const totalCount = summary?.total ?? 0;
|
|
6231
|
+
const criticalCount = summary?.critical ?? 0;
|
|
6232
|
+
const warningCount = summary?.warning ?? 0;
|
|
6233
|
+
const noticeCount = summary?.notice ?? 0;
|
|
6234
|
+
const triggerTone = criticalCount > 0 ? "bg-red-500" : warningCount > 0 ? "bg-amber-400" : "bg-sky-400";
|
|
6235
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(Dialog, { open, onOpenChange: setOpen, children: [
|
|
6236
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(DialogTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
6237
|
+
Button,
|
|
6238
|
+
{
|
|
6239
|
+
type: "button",
|
|
6240
|
+
variant: "ghost",
|
|
6241
|
+
size: "icon",
|
|
6242
|
+
className: "relative size-8",
|
|
6243
|
+
"aria-label": "Alerts",
|
|
6244
|
+
title: "Alerts",
|
|
6245
|
+
children: [
|
|
6246
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Siren, { className: "size-4" }),
|
|
6247
|
+
summaryLoading ? /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "absolute -right-0.5 -top-0.5 size-3 animate-spin text-muted-foreground" }) : totalCount > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
6248
|
+
"span",
|
|
6249
|
+
{
|
|
6250
|
+
className: cn(
|
|
6251
|
+
"absolute -right-1 -top-1 min-w-4 rounded-full px-1 text-[10px] font-semibold leading-4 text-white",
|
|
6252
|
+
triggerTone
|
|
6253
|
+
),
|
|
6254
|
+
children: formatCount(totalCount)
|
|
6255
|
+
}
|
|
6256
|
+
) : null,
|
|
6257
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sr-only", children: "Alerts" })
|
|
6258
|
+
]
|
|
6259
|
+
}
|
|
6260
|
+
) }),
|
|
6261
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(DialogContent, { className: "flex max-h-[86vh] max-w-6xl flex-col overflow-hidden", children: [
|
|
6262
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(DialogHeader, { children: [
|
|
6263
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(DialogTitle, { className: "flex items-center gap-2 pr-8", children: [
|
|
6264
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Siren, { className: "size-5 text-red-300" }),
|
|
6265
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Alerts" }),
|
|
6266
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", children: formatCount(totalCount) })
|
|
6267
|
+
] }),
|
|
6268
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(DialogDescription, { className: "sr-only", children: "Local alert board for captured Inspector evidence." })
|
|
6269
|
+
] }),
|
|
6270
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid gap-2 sm:grid-cols-4", children: [
|
|
6271
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(AlertSummaryPill, { label: "Open", count: summary?.open ?? 0, tone: "neutral" }),
|
|
6272
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(AlertSummaryPill, { label: "Critical", count: criticalCount, tone: "critical" }),
|
|
6273
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(AlertSummaryPill, { label: "Warning", count: warningCount, tone: "warning" }),
|
|
6274
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(AlertSummaryPill, { label: "Notice", count: noticeCount, tone: "notice" })
|
|
6275
|
+
] }),
|
|
6276
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-h-0 flex-wrap items-center justify-between gap-2 border-y border-border py-3", children: [
|
|
6277
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap gap-1", children: [
|
|
6278
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
6279
|
+
SeverityFilterButton,
|
|
6280
|
+
{
|
|
6281
|
+
value: "all",
|
|
6282
|
+
active: severityFilter === "all",
|
|
6283
|
+
label: "All",
|
|
6284
|
+
count: summary?.total ?? null,
|
|
6285
|
+
onClick: setSeverityFilter
|
|
6286
|
+
}
|
|
6287
|
+
),
|
|
6288
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
6289
|
+
SeverityFilterButton,
|
|
6290
|
+
{
|
|
6291
|
+
value: "critical",
|
|
6292
|
+
active: severityFilter === "critical",
|
|
6293
|
+
label: "Critical",
|
|
6294
|
+
count: criticalCount,
|
|
6295
|
+
onClick: setSeverityFilter
|
|
6296
|
+
}
|
|
6297
|
+
),
|
|
6298
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
6299
|
+
SeverityFilterButton,
|
|
6300
|
+
{
|
|
6301
|
+
value: "warning",
|
|
6302
|
+
active: severityFilter === "warning",
|
|
6303
|
+
label: "Warning",
|
|
6304
|
+
count: warningCount,
|
|
6305
|
+
onClick: setSeverityFilter
|
|
6306
|
+
}
|
|
6307
|
+
),
|
|
6308
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
6309
|
+
SeverityFilterButton,
|
|
6310
|
+
{
|
|
6311
|
+
value: "notice",
|
|
6312
|
+
active: severityFilter === "notice",
|
|
6313
|
+
label: "Notice",
|
|
6314
|
+
count: noticeCount,
|
|
6315
|
+
onClick: setSeverityFilter
|
|
6316
|
+
}
|
|
6317
|
+
)
|
|
6318
|
+
] }),
|
|
6319
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
6320
|
+
Button,
|
|
6321
|
+
{
|
|
6322
|
+
type: "button",
|
|
6323
|
+
variant: "outline",
|
|
6324
|
+
size: "sm",
|
|
6325
|
+
className: "h-7 shrink-0 px-2",
|
|
6326
|
+
onClick: () => void mutate(),
|
|
6327
|
+
children: [
|
|
6328
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { className: "size-3.5" }),
|
|
6329
|
+
"Refresh"
|
|
6330
|
+
]
|
|
6331
|
+
}
|
|
6332
|
+
)
|
|
6333
|
+
] }),
|
|
6334
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap gap-1", children: [
|
|
6335
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
6336
|
+
Button,
|
|
6337
|
+
{
|
|
6338
|
+
type: "button",
|
|
6339
|
+
variant: categoryFilter === "all" ? "secondary" : "ghost",
|
|
6340
|
+
size: "sm",
|
|
6341
|
+
"aria-pressed": categoryFilter === "all",
|
|
6342
|
+
onClick: () => setCategoryFilter("all"),
|
|
6343
|
+
className: "h-7 px-2",
|
|
6344
|
+
children: "All categories"
|
|
6345
|
+
}
|
|
6346
|
+
),
|
|
6347
|
+
ALERT_CATEGORIES.map((category) => {
|
|
6348
|
+
const categoryCount = summary?.byCategory.find((item) => item.category === category)?.count ?? 0;
|
|
6349
|
+
if (categoryCount === 0) return null;
|
|
6350
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
6351
|
+
CategoryFilterButton,
|
|
6352
|
+
{
|
|
6353
|
+
category,
|
|
6354
|
+
active: categoryFilter === category,
|
|
6355
|
+
count: categoryCount,
|
|
6356
|
+
onClick: setCategoryFilter
|
|
6357
|
+
},
|
|
6358
|
+
category
|
|
6359
|
+
);
|
|
6360
|
+
})
|
|
6361
|
+
] }),
|
|
6362
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid min-h-0 flex-1 gap-4 md:grid-cols-[360px_minmax(0,1fr)]", children: [
|
|
6363
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-h-0 flex-col", children: [
|
|
6364
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-2 flex items-center justify-between text-xs text-muted-foreground", children: [
|
|
6365
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
|
|
6366
|
+
formatCount(total),
|
|
6367
|
+
" matched"
|
|
6368
|
+
] }),
|
|
6369
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: alerts.length < total ? `${formatCount(alerts.length)} shown` : "Latest" })
|
|
6370
|
+
] }),
|
|
6371
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
6372
|
+
AlertList,
|
|
6373
|
+
{
|
|
6374
|
+
alerts,
|
|
6375
|
+
selectedAlertId: selectedAlert?.id ?? null,
|
|
6376
|
+
isLoading,
|
|
6377
|
+
error,
|
|
6378
|
+
onSelect: setSelectedAlertId
|
|
6379
|
+
}
|
|
6380
|
+
)
|
|
6381
|
+
] }),
|
|
6382
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(AlertDetails, { alert: selectedAlert, onNavigate: () => setOpen(false) })
|
|
6383
|
+
] })
|
|
6384
|
+
] })
|
|
6385
|
+
] });
|
|
6386
|
+
}
|
|
6387
|
+
const GROUP_EVIDENCE_FETCH_TIMEOUT_MS = 12e3;
|
|
5565
6388
|
function evidenceKey(group) {
|
|
5566
6389
|
if (group === null || group.evidence === null) return null;
|
|
5567
6390
|
return `/api/groups/${encodeURIComponent(group.id)}/evidence`;
|
|
5568
6391
|
}
|
|
5569
6392
|
function useGroupEvidence(group) {
|
|
5570
|
-
const response = useSWR(
|
|
6393
|
+
const response = useSWR(
|
|
6394
|
+
evidenceKey(group),
|
|
6395
|
+
(url) => fetchJsonWithTimeout(url, GroupEvidenceReadResponseSchema, GROUP_EVIDENCE_FETCH_TIMEOUT_MS)
|
|
6396
|
+
);
|
|
5571
6397
|
return {
|
|
5572
6398
|
evidenceResponse: response.data ?? null,
|
|
5573
6399
|
isLoading: response.isLoading,
|
|
@@ -5575,8 +6401,12 @@ function useGroupEvidence(group) {
|
|
|
5575
6401
|
mutate: response.mutate
|
|
5576
6402
|
};
|
|
5577
6403
|
}
|
|
6404
|
+
const GROUPS_FETCH_TIMEOUT_MS = 8e3;
|
|
5578
6405
|
function useGroups() {
|
|
5579
|
-
const response = useSWR(
|
|
6406
|
+
const response = useSWR(
|
|
6407
|
+
"/api/groups",
|
|
6408
|
+
(url) => fetchJsonWithTimeout(url, InspectorGroupsListResponseSchema, GROUPS_FETCH_TIMEOUT_MS)
|
|
6409
|
+
);
|
|
5580
6410
|
return {
|
|
5581
6411
|
groups: response.data?.groups ?? [],
|
|
5582
6412
|
isLoading: response.isLoading,
|
|
@@ -5620,14 +6450,22 @@ function summarizeGroup(group) {
|
|
|
5620
6450
|
missingStatusMembers
|
|
5621
6451
|
};
|
|
5622
6452
|
}
|
|
5623
|
-
function buildGroupMemberRows(group, exportResult) {
|
|
6453
|
+
function buildGroupMemberRows(group, exportResult, evidenceResponse = null) {
|
|
5624
6454
|
if (exportResult !== null && exportResult.group.id === group.id) {
|
|
5625
6455
|
return exportResult.members.map((exportMember) => ({
|
|
5626
6456
|
member: exportMember.member,
|
|
5627
|
-
exportMember
|
|
6457
|
+
exportMember,
|
|
6458
|
+
source: "export"
|
|
6459
|
+
}));
|
|
6460
|
+
}
|
|
6461
|
+
if (evidenceResponse !== null && evidenceResponse.group.id === group.id && evidenceResponse.members !== null) {
|
|
6462
|
+
return evidenceResponse.members.map((exportMember) => ({
|
|
6463
|
+
member: exportMember.member,
|
|
6464
|
+
exportMember,
|
|
6465
|
+
source: "evidence"
|
|
5628
6466
|
}));
|
|
5629
6467
|
}
|
|
5630
|
-
return group.members.map((member) => ({ member, exportMember: null }));
|
|
6468
|
+
return group.members.map((member) => ({ member, exportMember: null, source: "group" }));
|
|
5631
6469
|
}
|
|
5632
6470
|
function statusClassName(status) {
|
|
5633
6471
|
switch (status) {
|
|
@@ -5980,7 +6818,7 @@ function GroupDetails({
|
|
|
5980
6818
|
const exportedSummary = exportSummaryFor(group, exportResult, evidenceResponse);
|
|
5981
6819
|
const evidenceReport = evidenceReportFor(group, exportResult, evidenceResponse);
|
|
5982
6820
|
const evidence = evidenceFor(group, exportResult);
|
|
5983
|
-
const rows = buildGroupMemberRows(group, exportResult);
|
|
6821
|
+
const rows = buildGroupMemberRows(group, exportResult, evidenceResponse);
|
|
5984
6822
|
const isExporting = exportingGroupId === group.id;
|
|
5985
6823
|
const isDeleting = deletingGroupId === group.id;
|
|
5986
6824
|
const isConfirmingDelete = deleteTarget?.id === group.id;
|
|
@@ -6259,6 +7097,7 @@ function MemberRow({ row }) {
|
|
|
6259
7097
|
const session = row.exportMember?.session ?? null;
|
|
6260
7098
|
const log = latestLog(row);
|
|
6261
7099
|
const sessionPath = session?.inspectorPath ?? getSessionPath(member.sessionId);
|
|
7100
|
+
const canOpenLiveSession = row.source !== "evidence";
|
|
6262
7101
|
const requests = session?.requestCount ?? null;
|
|
6263
7102
|
const errors = session?.errorCount ?? null;
|
|
6264
7103
|
const tokens = session?.tokenUsage.total ?? null;
|
|
@@ -6266,7 +7105,7 @@ function MemberRow({ row }) {
|
|
|
6266
7105
|
const sessionLabel = firstNonEmpty(member.label, member.sessionId);
|
|
6267
7106
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "border-b border-border last:border-0", children: [
|
|
6268
7107
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("td", { className: "max-w-56 px-3 py-2", children: [
|
|
6269
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
7108
|
+
canOpenLiveSession ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
6270
7109
|
"a",
|
|
6271
7110
|
{
|
|
6272
7111
|
href: sessionPath,
|
|
@@ -6280,6 +7119,16 @@ function MemberRow({ row }) {
|
|
|
6280
7119
|
/* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { className: "size-3 shrink-0 text-muted-foreground" })
|
|
6281
7120
|
]
|
|
6282
7121
|
}
|
|
7122
|
+
) : /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
7123
|
+
"span",
|
|
7124
|
+
{
|
|
7125
|
+
className: "inline-flex max-w-full min-w-0 items-center gap-2 font-mono text-xs text-foreground",
|
|
7126
|
+
title: `Evidence snapshot for ${sessionLabel}`,
|
|
7127
|
+
children: [
|
|
7128
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "min-w-0 truncate", children: sessionLabel }),
|
|
7129
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", className: "shrink-0 border-zinc-200 bg-zinc-50 text-zinc-600", children: "evidence" })
|
|
7130
|
+
]
|
|
7131
|
+
}
|
|
6283
7132
|
),
|
|
6284
7133
|
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-0.5 truncate text-[11px] text-muted-foreground", children: displayText(member.agent) })
|
|
6285
7134
|
] }),
|
|
@@ -10851,6 +11700,7 @@ function BrandHeader({
|
|
|
10851
11700
|
] })
|
|
10852
11701
|
] }),
|
|
10853
11702
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [
|
|
11703
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(AlertsDialog, {}),
|
|
10854
11704
|
/* @__PURE__ */ jsxRuntimeExports.jsx(GroupsDialog, {}),
|
|
10855
11705
|
/* @__PURE__ */ jsxRuntimeExports.jsx(SettingsDialog, {})
|
|
10856
11706
|
] })
|
|
@@ -10877,6 +11727,7 @@ function BrandHeader({
|
|
|
10877
11727
|
/* @__PURE__ */ jsxRuntimeExports.jsx(McpReadyBadge, {})
|
|
10878
11728
|
] }),
|
|
10879
11729
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "justify-self-end flex items-center gap-1", children: [
|
|
11730
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(AlertsDialog, {}),
|
|
10880
11731
|
/* @__PURE__ */ jsxRuntimeExports.jsx(GroupsDialog, {}),
|
|
10881
11732
|
/* @__PURE__ */ jsxRuntimeExports.jsx(SettingsDialog, {})
|
|
10882
11733
|
] })
|
|
@@ -11180,6 +12031,7 @@ function ProxyViewer({
|
|
|
11180
12031
|
onModelChange,
|
|
11181
12032
|
onClearAll,
|
|
11182
12033
|
onClearGroup,
|
|
12034
|
+
onImportLogs,
|
|
11183
12035
|
viewMode,
|
|
11184
12036
|
captureMode,
|
|
11185
12037
|
strip,
|
|
@@ -11195,12 +12047,16 @@ function ProxyViewer({
|
|
|
11195
12047
|
const { providers } = useProviders();
|
|
11196
12048
|
const [exporting, setExporting] = reactExports.useState(null);
|
|
11197
12049
|
const [exportError, setExportError] = reactExports.useState(null);
|
|
12050
|
+
const [importing, setImporting] = reactExports.useState(false);
|
|
12051
|
+
const [importStatus, setImportStatus] = reactExports.useState(null);
|
|
12052
|
+
const [importError, setImportError] = reactExports.useState(null);
|
|
11198
12053
|
const [comparePair, setComparePair] = reactExports.useState(null);
|
|
11199
12054
|
const [crabEntrancePhase, setCrabEntrancePhase] = reactExports.useState(
|
|
11200
12055
|
"hidden"
|
|
11201
12056
|
);
|
|
11202
12057
|
const logListRef = reactExports.useRef(null);
|
|
11203
12058
|
const logListWrapperRef = reactExports.useRef(null);
|
|
12059
|
+
const importInputRef = reactExports.useRef(null);
|
|
11204
12060
|
useKeyboardNavigation(logListRef, logListWrapperRef, { pageWide: true });
|
|
11205
12061
|
reactExports.useEffect(() => {
|
|
11206
12062
|
const perCrabDuration = 400;
|
|
@@ -11236,6 +12092,30 @@ function ProxyViewer({
|
|
|
11236
12092
|
},
|
|
11237
12093
|
[logs]
|
|
11238
12094
|
);
|
|
12095
|
+
const handleImportClick = reactExports.useCallback(() => {
|
|
12096
|
+
importInputRef.current?.click();
|
|
12097
|
+
}, []);
|
|
12098
|
+
const handleImportFileChange = reactExports.useCallback(
|
|
12099
|
+
(event) => {
|
|
12100
|
+
const file = event.currentTarget.files?.[0];
|
|
12101
|
+
event.currentTarget.value = "";
|
|
12102
|
+
if (file === void 0 || onImportLogs === void 0) return;
|
|
12103
|
+
setImporting(true);
|
|
12104
|
+
setImportError(null);
|
|
12105
|
+
setImportStatus(null);
|
|
12106
|
+
void onImportLogs(file).then((result) => {
|
|
12107
|
+
const skipped = result.skipped > 0 ? `, skipped ${String(result.skipped)}` : "";
|
|
12108
|
+
setImportStatus(
|
|
12109
|
+
`Imported ${String(result.imported)} logs as ${result.sessionId}${skipped}.`
|
|
12110
|
+
);
|
|
12111
|
+
}).catch((err) => {
|
|
12112
|
+
setImportError(err instanceof Error ? err.message : "Failed to import logs");
|
|
12113
|
+
}).finally(() => {
|
|
12114
|
+
setImporting(false);
|
|
12115
|
+
});
|
|
12116
|
+
},
|
|
12117
|
+
[onImportLogs]
|
|
12118
|
+
);
|
|
11239
12119
|
reactExports.useEffect(() => {
|
|
11240
12120
|
setComparePair(null);
|
|
11241
12121
|
}, [selectedSession, selectedModel]);
|
|
@@ -11348,6 +12228,32 @@ function ProxyViewer({
|
|
|
11348
12228
|
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm font-semibold text-foreground", children: "Logs" }),
|
|
11349
12229
|
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-0.5 font-mono text-[11px] text-muted-foreground", children: activeScopeLabel })
|
|
11350
12230
|
] }),
|
|
12231
|
+
onImportLogs !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
12232
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
12233
|
+
"input",
|
|
12234
|
+
{
|
|
12235
|
+
ref: importInputRef,
|
|
12236
|
+
type: "file",
|
|
12237
|
+
accept: ".zip,.json,.jsonl,application/zip,application/json",
|
|
12238
|
+
className: "hidden",
|
|
12239
|
+
onChange: handleImportFileChange
|
|
12240
|
+
}
|
|
12241
|
+
),
|
|
12242
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
12243
|
+
"button",
|
|
12244
|
+
{
|
|
12245
|
+
type: "button",
|
|
12246
|
+
onClick: handleImportClick,
|
|
12247
|
+
disabled: importing,
|
|
12248
|
+
className: "inline-flex h-8 items-center gap-1.5 rounded-md px-3 text-xs text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
|
12249
|
+
title: "Import Agent Inspector export",
|
|
12250
|
+
children: importing ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Importing..." }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
12251
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { className: "size-3.5" }),
|
|
12252
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Import" })
|
|
12253
|
+
] })
|
|
12254
|
+
}
|
|
12255
|
+
)
|
|
12256
|
+
] }),
|
|
11351
12257
|
exportActionVisibility.redacted && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
11352
12258
|
"button",
|
|
11353
12259
|
{
|
|
@@ -11395,7 +12301,9 @@ function ProxyViewer({
|
|
|
11395
12301
|
)
|
|
11396
12302
|
] }),
|
|
11397
12303
|
/* @__PURE__ */ jsxRuntimeExports.jsx(LogPaginationBar, { logs, pagination, embedded: true }),
|
|
11398
|
-
exportError !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: exportError })
|
|
12304
|
+
exportError !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: exportError }),
|
|
12305
|
+
importError !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: importError }),
|
|
12306
|
+
importStatus !== null && importError === null && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 rounded-md border border-emerald-500/25 bg-emerald-500/10 px-3 py-2 text-xs text-emerald-200", children: importStatus })
|
|
11399
12307
|
] }),
|
|
11400
12308
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-4 lg:px-5", children: [
|
|
11401
12309
|
/* @__PURE__ */ jsxRuntimeExports.jsx(SessionOpeningSlate, { stats: slateStats }),
|
|
@@ -11827,6 +12735,28 @@ function ProxyViewerContainer({
|
|
|
11827
12735
|
}
|
|
11828
12736
|
})();
|
|
11829
12737
|
}, []);
|
|
12738
|
+
const handleImportLogs = reactExports.useCallback(async (file) => {
|
|
12739
|
+
const formData = new FormData();
|
|
12740
|
+
formData.set("file", file);
|
|
12741
|
+
const result = await fetchJson("/api/logs/import", ImportLogsResponseSchema, {
|
|
12742
|
+
method: "POST",
|
|
12743
|
+
body: formData
|
|
12744
|
+
});
|
|
12745
|
+
setAllLogs((prev) => {
|
|
12746
|
+
const importedIds = new Set(result.logs.map((log) => log.id));
|
|
12747
|
+
const next = [...prev.filter((log) => !importedIds.has(log.id)), ...result.logs].sort(
|
|
12748
|
+
(left, right) => left.id - right.id
|
|
12749
|
+
);
|
|
12750
|
+
const trimmed = trimClientLogs(next);
|
|
12751
|
+
logIndexRef.current = buildLogIndex(trimmed);
|
|
12752
|
+
return trimmed;
|
|
12753
|
+
});
|
|
12754
|
+
setSelectedSession(result.sessionId);
|
|
12755
|
+
setSelectedModel("__all__");
|
|
12756
|
+
setLogPage(null);
|
|
12757
|
+
setError(null);
|
|
12758
|
+
return result;
|
|
12759
|
+
}, []);
|
|
11830
12760
|
const { strip, captureMode, slowResponseThresholdSeconds, timeDisplayFormat } = useStripConfig();
|
|
11831
12761
|
const viewMode = captureMode;
|
|
11832
12762
|
const loadOldestPage = reactExports.useCallback(() => {
|
|
@@ -11886,6 +12816,7 @@ function ProxyViewerContainer({
|
|
|
11886
12816
|
onModelChange: setSelectedModel,
|
|
11887
12817
|
onClearAll: handleClearAll,
|
|
11888
12818
|
onClearGroup: handleClearGroup,
|
|
12819
|
+
onImportLogs: initialSessionId === void 0 ? handleImportLogs : void 0,
|
|
11889
12820
|
isLoading: initialSessionId === void 0 ? !streamInitialized : sessionPageLoading,
|
|
11890
12821
|
pagination,
|
|
11891
12822
|
sessionMemberships,
|