@tonyclaw/llm-inspector 1.16.5 → 1.17.0

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.
Files changed (37) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{CompareDrawer-C1w4KUGZ.js → CompareDrawer-C4fie5g5.js} +1 -1
  3. package/.output/public/assets/{ReplayDialog-DR2Sgq_g.js → ReplayDialog-Dme5uOR9.js} +1 -1
  4. package/.output/public/assets/{RequestAnatomy-DAre35kj.js → RequestAnatomy-ChBLDNFH.js} +1 -1
  5. package/.output/public/assets/{ResponseView-ackes7_g.js → ResponseView-wGeqBzVU.js} +1 -1
  6. package/.output/public/assets/{StreamingChunkSequence-GrXwIGKA.js → StreamingChunkSequence-zeJZQLqT.js} +1 -1
  7. package/.output/public/assets/index-DoGvsnbA.css +1 -0
  8. package/.output/public/assets/index-DpbutOvo.js +101 -0
  9. package/.output/public/assets/{json-viewer-C_QUhGeu.js → json-viewer-BV-WUszW.js} +1 -1
  10. package/.output/public/assets/{main-CDMdNDY_.js → main-DRu10KNQ.js} +1 -1
  11. package/.output/server/_libs/lucide-react.mjs +6 -6
  12. package/.output/server/_ssr/{CompareDrawer-ftkJxyk6.mjs → CompareDrawer-C4-CQL5w.mjs} +4 -4
  13. package/.output/server/_ssr/{ReplayDialog-DcmE3lj5.mjs → ReplayDialog-BTb1Bam8.mjs} +4 -4
  14. package/.output/server/_ssr/{RequestAnatomy-rK_LNMdG.mjs → RequestAnatomy-CZFV1IvL.mjs} +2 -2
  15. package/.output/server/_ssr/{ResponseView-CbQ4n-aJ.mjs → ResponseView-CTZekh65.mjs} +4 -4
  16. package/.output/server/_ssr/{StreamingChunkSequence-84FZkIzv.mjs → StreamingChunkSequence-C38Ynabd.mjs} +3 -3
  17. package/.output/server/_ssr/{index-CDjLoMsk.mjs → index-Cnu-QzAy.mjs} +159 -32
  18. package/.output/server/_ssr/index.mjs +2 -2
  19. package/.output/server/_ssr/{json-viewer-B-qpM5xC.mjs → json-viewer-DROqpjS9.mjs} +2 -2
  20. package/.output/server/_ssr/{router-BrdjOUEW.mjs → router-pP4GCTQx.mjs} +20 -6
  21. package/.output/server/{_tanstack-start-manifest_v-DmOZEcJ3.mjs → _tanstack-start-manifest_v-CphS4rZd.mjs} +1 -1
  22. package/.output/server/index.mjs +58 -58
  23. package/package.json +1 -1
  24. package/src/components/ProxyViewer.tsx +6 -1
  25. package/src/components/ProxyViewerContainer.tsx +2 -1
  26. package/src/components/providers/SettingsDialog.tsx +52 -1
  27. package/src/components/proxy-viewer/ConversationGroup.tsx +4 -0
  28. package/src/components/proxy-viewer/LogEntry.tsx +4 -0
  29. package/src/components/proxy-viewer/LogEntryHeader.tsx +47 -4
  30. package/src/components/proxy-viewer/TurnGroup.tsx +36 -7
  31. package/src/lib/runtimeConfig.ts +9 -0
  32. package/src/lib/useOnboarding.ts +7 -1
  33. package/src/lib/useStripConfig.ts +33 -2
  34. package/src/proxy/config.ts +17 -7
  35. package/src/routes/api/config.ts +7 -0
  36. package/.output/public/assets/index-BGzHFOEX.css +0 -1
  37. package/.output/public/assets/index-DX88k9br.js +0 -101
@@ -1,5 +1,5 @@
1
1
  import { r as reactExports, j as jsxRuntimeExports, a as React } from "../_libs/react.mjs";
2
- import { C as CapturedLogSchema, R as RuntimeConfigSchema, r as requestFormatForPath, c as createPendingProviderTestResults, P as ProviderTestResultsSchema, b as createFailedProviderTestResults, d as ProviderConfigSchema, s as stripClaudeCodeBillingHeader, p as parseOpenAIResponse, O as OpenAIRequestSchema, A as AnthropicResponseSchema$1, a as AnthropicRequestSchema } from "./router-BrdjOUEW.mjs";
2
+ import { C as CapturedLogSchema, D as DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS, R as RuntimeConfigSchema, r as requestFormatForPath, c as createPendingProviderTestResults, P as ProviderTestResultsSchema, b as createFailedProviderTestResults, M as MAX_SLOW_RESPONSE_THRESHOLD_SECONDS, d as ProviderConfigSchema, s as stripClaudeCodeBillingHeader, p as parseOpenAIResponse, O as OpenAIRequestSchema, A as AnthropicResponseSchema$1, a as AnthropicRequestSchema } from "./router-pP4GCTQx.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";
@@ -9,7 +9,7 @@ import { R as Root, T as Trigger$1, C as Content, a as Close, b as Title, P as P
9
9
  import { d as diffJson, a as diffLines } from "../_libs/diff.mjs";
10
10
  import { u 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 { C as Check, X, D as Download, S as Settings, a as ChevronDown, U as Upload, b as Scan, P as Plus, c as Copy, d as CircleAlert, e as ChevronUp, L as LoaderCircle, f as ChevronRight, g as User, h as Clock, M as MessageSquare, Z as Zap, T as Trash2, E as EyeOff, i as Eye, j as ExternalLink, R as RotateCw, k as Pencil, G as GitCompareArrows, l as Minus, m as CircleCheckBig, O as OctagonAlert, n as TriangleAlert, W as Wrench, o as Globe, F as FileTerminal, p as Radio, q as ChevronsUp, r as ChevronsDown, s as RotateCcw, t as CircleQuestionMark, u as Server, v as Gauge, w as Lock, x as Wifi, y as WifiOff, A as ArrowUp, z as ArrowDown, B as Rows3, H as Columns2 } from "../_libs/lucide-react.mjs";
12
+ import { C as Check, X, D as Download, S as Settings, a as ChevronDown, U as Upload, b as Scan, P as Plus, c as Copy, d as CircleAlert, e as ChevronUp, L as LoaderCircle, f as ChevronRight, g as User, h as Clock, M as MessageSquare, Z as Zap, T as Trash2, i as TriangleAlert, E as EyeOff, j as Eye, k as ExternalLink, R as RotateCw, l as Pencil, G as GitCompareArrows, m as Minus, n as CircleCheckBig, O as OctagonAlert, W as Wrench, o as Globe, F as FileTerminal, p as Radio, q as ChevronsUp, r as ChevronsDown, s as RotateCcw, t as CircleQuestionMark, u as Server, v as Gauge, w as Lock, x as Wifi, y as WifiOff, A as ArrowUp, z as ArrowDown, B as Rows3, H as Columns2 } from "../_libs/lucide-react.mjs";
13
13
  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";
14
14
  import { R as Root2$1, L as List, T as Trigger$2, C as Content$1 } from "../_libs/radix-ui__react-tabs.mjs";
15
15
  import { P as Provider, R as Root3, T as Trigger$3, a as Portal$2, C as Content2$1, A as Arrow2 } from "../_libs/radix-ui__react-tooltip.mjs";
@@ -165,12 +165,30 @@ function useStripConfig() {
165
165
  );
166
166
  const { mutate: globalMutate } = useSWRConfig();
167
167
  const strip = response.data?.stripClaudeCodeBillingHeader ?? false;
168
+ const slowResponseThresholdSeconds = response.data?.slowResponseThresholdSeconds ?? DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS;
169
+ const optimisticConfig = (patch) => ({
170
+ stripClaudeCodeBillingHeader: response.data?.stripClaudeCodeBillingHeader ?? false,
171
+ hasSeenOnboarding: response.data?.hasSeenOnboarding ?? false,
172
+ slowResponseThresholdSeconds: response.data?.slowResponseThresholdSeconds ?? DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS,
173
+ ...patch
174
+ });
168
175
  const setStrip = async (next) => {
169
176
  await globalMutate(
170
177
  STRIP_CONFIG_SWR_KEY,
171
178
  setRuntimeConfig({ stripClaudeCodeBillingHeader: next }),
172
179
  {
173
- optimisticData: { stripClaudeCodeBillingHeader: next },
180
+ optimisticData: optimisticConfig({ stripClaudeCodeBillingHeader: next }),
181
+ rollbackOnError: true,
182
+ revalidate: false
183
+ }
184
+ );
185
+ };
186
+ const setSlowResponseThresholdSeconds = async (next) => {
187
+ await globalMutate(
188
+ STRIP_CONFIG_SWR_KEY,
189
+ setRuntimeConfig({ slowResponseThresholdSeconds: next }),
190
+ {
191
+ optimisticData: optimisticConfig({ slowResponseThresholdSeconds: next }),
174
192
  rollbackOnError: true,
175
193
  revalidate: false
176
194
  }
@@ -178,9 +196,11 @@ function useStripConfig() {
178
196
  };
179
197
  return {
180
198
  strip,
199
+ slowResponseThresholdSeconds,
181
200
  isLoading: response.isLoading,
182
201
  isError: response.error !== void 0,
183
- setStrip
202
+ setStrip,
203
+ setSlowResponseThresholdSeconds
184
204
  };
185
205
  }
186
206
  const ONBOARDING_SWR_KEY = "/api/config";
@@ -219,7 +239,8 @@ function useOnboarding() {
219
239
  await globalMutate(ONBOARDING_SWR_KEY, patchRuntimeConfig({ hasSeenOnboarding: true }), {
220
240
  optimisticData: {
221
241
  stripClaudeCodeBillingHeader: response.data?.stripClaudeCodeBillingHeader ?? false,
222
- hasSeenOnboarding: true
242
+ hasSeenOnboarding: true,
243
+ slowResponseThresholdSeconds: response.data?.slowResponseThresholdSeconds ?? DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS
223
244
  },
224
245
  rollbackOnError: true,
225
246
  revalidate: false
@@ -335,10 +356,6 @@ async function exportLogsAsZip(logs) {
335
356
  document.body.removeChild(anchor);
336
357
  URL.revokeObjectURL(url);
337
358
  }
338
- const version = "1.16.5";
339
- const packageJson = {
340
- version
341
- };
342
359
  function cn(...inputs) {
343
360
  return twMerge(clsx(inputs));
344
361
  }
@@ -353,6 +370,10 @@ function getStatusCategory(status) {
353
370
  if (status >= 500) return "server_error";
354
371
  return "pending";
355
372
  }
373
+ const version = "1.17.0";
374
+ const packageJson = {
375
+ version
376
+ };
356
377
  const badgeVariants = cva(
357
378
  "inline-flex items-center justify-center rounded-full border border-transparent px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
358
379
  {
@@ -543,7 +564,7 @@ const API_FORMAT_LABELS = {
543
564
  openai: "OpenAI",
544
565
  unknown: "Unknown"
545
566
  };
546
- function formatTimestamp(iso) {
567
+ function formatTimestamp$1(iso) {
547
568
  const date = new Date(iso);
548
569
  return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" });
549
570
  }
@@ -627,9 +648,9 @@ function ConversationHeader({
627
648
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0", children: [
628
649
  /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3" }),
629
650
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums", children: [
630
- formatTimestamp(startTime),
651
+ formatTimestamp$1(startTime),
631
652
  " - ",
632
- formatTimestamp(endTime)
653
+ formatTimestamp$1(endTime)
633
654
  ] })
634
655
  ] }),
635
656
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0", children: [
@@ -1402,27 +1423,27 @@ function TabsContent({
1402
1423
  );
1403
1424
  }
1404
1425
  const LazyCompareDrawer = reactExports.lazy(
1405
- () => import("./CompareDrawer-ftkJxyk6.mjs").then((m) => ({ default: m.CompareDrawer }))
1426
+ () => import("./CompareDrawer-C4-CQL5w.mjs").then((m) => ({ default: m.CompareDrawer }))
1406
1427
  );
1407
1428
  const LazyReplayDialog = reactExports.lazy(
1408
- () => import("./ReplayDialog-DcmE3lj5.mjs").then((m) => ({ default: m.ReplayDialog }))
1429
+ () => import("./ReplayDialog-BTb1Bam8.mjs").then((m) => ({ default: m.ReplayDialog }))
1409
1430
  );
1410
1431
  const LazyRequestAnatomy = reactExports.lazy(
1411
- () => import("./RequestAnatomy-rK_LNMdG.mjs").then((m) => ({ default: m.RequestAnatomy }))
1432
+ () => import("./RequestAnatomy-CZFV1IvL.mjs").then((m) => ({ default: m.RequestAnatomy }))
1412
1433
  );
1413
1434
  const LazyResponseView = reactExports.lazy(
1414
- () => import("./ResponseView-CbQ4n-aJ.mjs").then((m) => ({ default: m.ResponseView }))
1435
+ () => import("./ResponseView-CTZekh65.mjs").then((m) => ({ default: m.ResponseView }))
1415
1436
  );
1416
1437
  const LazyStreamingChunkSequence = reactExports.lazy(
1417
- () => import("./StreamingChunkSequence-84FZkIzv.mjs").then((m) => ({
1438
+ () => import("./StreamingChunkSequence-C38Ynabd.mjs").then((m) => ({
1418
1439
  default: m.StreamingChunkSequence
1419
1440
  }))
1420
1441
  );
1421
1442
  const LazyJsonViewer = reactExports.lazy(
1422
- () => import("./json-viewer-B-qpM5xC.mjs").then((m) => ({ default: m.JsonViewer }))
1443
+ () => import("./json-viewer-DROqpjS9.mjs").then((m) => ({ default: m.JsonViewer }))
1423
1444
  );
1424
1445
  const LazyJsonViewerFromString = reactExports.lazy(
1425
- () => import("./json-viewer-B-qpM5xC.mjs").then((m) => ({ default: m.JsonViewerFromString }))
1446
+ () => import("./json-viewer-DROqpjS9.mjs").then((m) => ({ default: m.JsonViewerFromString }))
1426
1447
  );
1427
1448
  const HIGHLIGHT_DURATION_MS = 1200;
1428
1449
  const MAX_HIGHLIGHT_ATTEMPTS = 12;
@@ -1820,6 +1841,13 @@ function formatElapsed$1(ms) {
1820
1841
  if (ms < 1e3) return `${ms}ms`;
1821
1842
  return `${(ms / 1e3).toFixed(1)}s`;
1822
1843
  }
1844
+ function formatTimestamp(iso) {
1845
+ const d = new Date(iso);
1846
+ const hh = String(d.getHours()).padStart(2, "0");
1847
+ const mm = String(d.getMinutes()).padStart(2, "0");
1848
+ const ss = String(d.getSeconds()).padStart(2, "0");
1849
+ return `${hh}:${mm}:${ss}`;
1850
+ }
1823
1851
  function CacheTrendIndicator({ trend }) {
1824
1852
  if (trend === null) return null;
1825
1853
  const isUp = trend.direction === "up";
@@ -1845,9 +1873,11 @@ const LogEntryHeader = reactExports.memo(function({
1845
1873
  onCopyRequest,
1846
1874
  requestCopied = false,
1847
1875
  onToggleRequestExpansion,
1848
- requestExpansionState = null
1876
+ requestExpansionState = null,
1877
+ slowResponseThresholdSeconds = 0
1849
1878
  }) {
1850
1879
  const statusCategory = getStatusCategory(log.responseStatus);
1880
+ const isSlowResponse = log.elapsedMs !== null && slowResponseThresholdSeconds > 0 && log.elapsedMs > slowResponseThresholdSeconds * 1e3;
1851
1881
  const hasTokens = log.inputTokens !== null || log.outputTokens !== null;
1852
1882
  const toolNamesJoined = reactExports.useMemo(() => responseToolNames?.join(", ") ?? null, [responseToolNames]);
1853
1883
  return /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
@@ -1875,6 +1905,13 @@ const LogEntryHeader = reactExports.memo(function({
1875
1905
  "#",
1876
1906
  log.id
1877
1907
  ] }),
1908
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1909
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0", children: [
1910
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3" }),
1911
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: formatTimestamp(log.timestamp) })
1912
+ ] }) }),
1913
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: log.timestamp })
1914
+ ] }),
1878
1915
  log.model !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1879
1916
  /* @__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" }) }) }),
1880
1917
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: log.model })
@@ -1893,9 +1930,24 @@ const LogEntryHeader = reactExports.memo(function({
1893
1930
  ]
1894
1931
  }
1895
1932
  ),
1896
- log.elapsedMs !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0", children: [
1897
- /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3" }),
1898
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: formatElapsed$1(log.elapsedMs) })
1933
+ log.elapsedMs !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1934
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
1935
+ "span",
1936
+ {
1937
+ className: cn(
1938
+ "flex items-center gap-1 text-xs shrink-0",
1939
+ isSlowResponse ? "text-amber-400" : "text-muted-foreground"
1940
+ ),
1941
+ children: [
1942
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3" }),
1943
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: formatElapsed$1(log.elapsedMs) }),
1944
+ isSlowResponse && /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { className: "size-3", "aria-label": "Slow response" })
1945
+ ]
1946
+ }
1947
+ ) }),
1948
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: isSlowResponse ? `Slow response: ${formatElapsed$1(log.elapsedMs)} exceeds ${formatElapsed$1(
1949
+ slowResponseThresholdSeconds * 1e3
1950
+ )}` : "Elapsed response time" })
1899
1951
  ] }),
1900
1952
  hasTokens && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-xs shrink-0", children: [
1901
1953
  /* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { className: "size-3 text-muted-foreground" }),
@@ -2451,6 +2503,7 @@ const LogEntry = reactExports.memo(function({
2451
2503
  log,
2452
2504
  viewMode = "simple",
2453
2505
  strip,
2506
+ slowResponseThresholdSeconds,
2454
2507
  cacheTrend = null,
2455
2508
  onCompareWithPrevious
2456
2509
  }) {
@@ -2510,6 +2563,7 @@ const LogEntry = reactExports.memo(function({
2510
2563
  onToggle: () => setExpanded(!expanded),
2511
2564
  responseToolNames: responseAnalysis.toolNames,
2512
2565
  cacheTrend,
2566
+ slowResponseThresholdSeconds,
2513
2567
  onReplay: onCompareWithPrevious === void 0 ? void 0 : () => {
2514
2568
  setReplayOpen(true);
2515
2569
  },
@@ -2834,6 +2888,7 @@ const TurnGroup = reactExports.memo(function TurnGroup2({
2834
2888
  entries,
2835
2889
  viewMode,
2836
2890
  strip,
2891
+ slowResponseThresholdSeconds,
2837
2892
  cacheTrends,
2838
2893
  onCompareWithPrevious,
2839
2894
  comparisonPredecessors,
@@ -2897,6 +2952,7 @@ const TurnGroup = reactExports.memo(function TurnGroup2({
2897
2952
  const StartCrab = reactExports.useMemo(() => getCrabVariant(entries[0]?.log.id ?? 0), [entries]);
2898
2953
  const EndCrab = reactExports.useMemo(() => getCrabVariant(entries[lastIdx]?.log.id ?? 0), [entries, lastIdx]);
2899
2954
  const bgClass = turnIndex % 2 === 0 ? "bg-muted/10" : "bg-muted/25";
2955
+ const aggregateIsSlow = aggregate.hasElapsed && slowResponseThresholdSeconds > 0 && aggregate.totalElapsed > slowResponseThresholdSeconds * 1e3;
2900
2956
  const [layoutVersion, setLayoutVersion] = reactExports.useState(0);
2901
2957
  const containerRef = reactExports.useRef(null);
2902
2958
  reactExports.useEffect(() => {
@@ -3017,10 +3073,25 @@ const TurnGroup = reactExports.memo(function TurnGroup2({
3017
3073
  entries.length > 1 ? "s" : ""
3018
3074
  ] }),
3019
3075
  uniqueProviders.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex items-center gap-0.5 shrink-0", children: uniqueProviders.map((p) => /* @__PURE__ */ jsxRuntimeExports.jsx(ProviderLogo, { provider: p, className: "size-4" }, p)) }),
3020
- aggregate.hasElapsed && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground shrink-0", children: [
3021
- /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3" }),
3022
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: formatElapsed(aggregate.totalElapsed) })
3023
- ] }),
3076
+ aggregate.hasElapsed && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
3077
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
3078
+ "span",
3079
+ {
3080
+ className: cn(
3081
+ "flex items-center gap-1 shrink-0",
3082
+ aggregateIsSlow ? "text-amber-400" : "text-muted-foreground"
3083
+ ),
3084
+ children: [
3085
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3" }),
3086
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: formatElapsed(aggregate.totalElapsed) }),
3087
+ aggregateIsSlow && /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { className: "size-3", "aria-label": "Slow response" })
3088
+ ]
3089
+ }
3090
+ ) }),
3091
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: aggregateIsSlow ? `Slow response: ${formatElapsed(
3092
+ aggregate.totalElapsed
3093
+ )} exceeds ${formatElapsed(slowResponseThresholdSeconds * 1e3)}` : "Total elapsed response time" })
3094
+ ] }) }),
3024
3095
  aggregate.hasTokens && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 shrink-0", children: [
3025
3096
  /* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { className: "size-3 text-muted-foreground" }),
3026
3097
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums", children: [
@@ -3069,6 +3140,7 @@ const TurnGroup = reactExports.memo(function TurnGroup2({
3069
3140
  log,
3070
3141
  viewMode,
3071
3142
  strip,
3143
+ slowResponseThresholdSeconds,
3072
3144
  cacheTrend: cacheTrends?.get(log.id) ?? null,
3073
3145
  onCompareWithPrevious: comparisonPredecessors.has(log.id) ? onCompareWithPrevious : void 0
3074
3146
  }
@@ -3092,6 +3164,7 @@ const ConversationGroup = reactExports.memo(function({
3092
3164
  group,
3093
3165
  viewMode = "simple",
3094
3166
  strip,
3167
+ slowResponseThresholdSeconds,
3095
3168
  cacheTrends,
3096
3169
  onCompareWithPrevious,
3097
3170
  comparisonPredecessors,
@@ -3131,6 +3204,7 @@ const ConversationGroup = reactExports.memo(function({
3131
3204
  entries: tg.entries,
3132
3205
  viewMode,
3133
3206
  strip,
3207
+ slowResponseThresholdSeconds,
3134
3208
  cacheTrends,
3135
3209
  onCompareWithPrevious,
3136
3210
  comparisonPredecessors,
@@ -4919,7 +4993,13 @@ function SettingsDialog() {
4919
4993
  ] });
4920
4994
  }
4921
4995
  function ProxySettingsTab() {
4922
- const { strip, isLoading, setStrip } = useStripConfig();
4996
+ const {
4997
+ strip,
4998
+ slowResponseThresholdSeconds,
4999
+ isLoading,
5000
+ setStrip,
5001
+ setSlowResponseThresholdSeconds
5002
+ } = useStripConfig();
4923
5003
  const [error, setError] = reactExports.useState(null);
4924
5004
  const [pending, setPending] = reactExports.useState(false);
4925
5005
  const handleToggle = reactExports.useCallback(
@@ -4936,6 +5016,20 @@ function ProxySettingsTab() {
4936
5016
  },
4937
5017
  [setStrip]
4938
5018
  );
5019
+ const handleThresholdChange = reactExports.useCallback(
5020
+ async (next) => {
5021
+ setError(null);
5022
+ setPending(true);
5023
+ try {
5024
+ await setSlowResponseThresholdSeconds(next);
5025
+ } catch (err) {
5026
+ setError(err instanceof Error ? err.message : String(err));
5027
+ } finally {
5028
+ setPending(false);
5029
+ }
5030
+ },
5031
+ [setSlowResponseThresholdSeconds]
5032
+ );
4939
5033
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
4940
5034
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1", children: [
4941
5035
  /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-sm font-semibold", children: "Claude Code billing header" }),
@@ -4965,6 +5059,36 @@ function ProxySettingsTab() {
4965
5059
  ),
4966
5060
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm", children: isLoading ? "Loading…" : strip ? "Stripping enabled" : "Stripping disabled" })
4967
5061
  ] }),
5062
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1", children: [
5063
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { htmlFor: "slow-response-threshold", className: "text-sm font-semibold", children: "Slow response threshold" }),
5064
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-muted-foreground", children: [
5065
+ "Logs whose elapsed time exceeds this many seconds show a warning icon next to the duration. Set to ",
5066
+ /* @__PURE__ */ jsxRuntimeExports.jsx("code", { children: "0" }),
5067
+ " to disable the indicator."
5068
+ ] }),
5069
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
5070
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
5071
+ "input",
5072
+ {
5073
+ id: "slow-response-threshold",
5074
+ type: "number",
5075
+ min: 0,
5076
+ max: MAX_SLOW_RESPONSE_THRESHOLD_SECONDS,
5077
+ step: 1,
5078
+ value: slowResponseThresholdSeconds,
5079
+ disabled: isLoading || pending,
5080
+ onChange: (e) => {
5081
+ const next = Number(e.currentTarget.value);
5082
+ if (!Number.isInteger(next)) return;
5083
+ if (next < 0 || next > MAX_SLOW_RESPONSE_THRESHOLD_SECONDS) return;
5084
+ void handleThresholdChange(next);
5085
+ },
5086
+ className: "h-8 w-24 rounded-md border border-input bg-transparent px-2 text-sm font-mono disabled:cursor-not-allowed disabled:opacity-50"
5087
+ }
5088
+ ),
5089
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-muted-foreground", children: "seconds" })
5090
+ ] })
5091
+ ] }),
4968
5092
  error !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-destructive", children: [
4969
5093
  "Failed to save: ",
4970
5094
  error
@@ -5214,7 +5338,8 @@ function ProxyViewer({
5214
5338
  onClearGroup,
5215
5339
  viewMode,
5216
5340
  onViewModeChange,
5217
- strip
5341
+ strip,
5342
+ slowResponseThresholdSeconds
5218
5343
  }) {
5219
5344
  const { totalIn, totalOut } = reactExports.useMemo(() => computeTokenSummary(logs), [logs]);
5220
5345
  const [exporting, setExporting] = reactExports.useState(false);
@@ -5344,7 +5469,7 @@ function ProxyViewer({
5344
5469
  logs.length,
5345
5470
  " request",
5346
5471
  logs.length !== 1 ? "s" : "",
5347
- totalIn > 0 || totalOut > 0 ? ` · ${totalIn.toLocaleString()} in / ${totalOut.toLocaleString()} out` : ""
5472
+ totalIn > 0 || totalOut > 0 ? ` · ${formatTokens(totalIn)} in / ${formatTokens(totalOut)} out` : ""
5348
5473
  ] }),
5349
5474
  logs.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
5350
5475
  "button",
@@ -5389,6 +5514,7 @@ function ProxyViewer({
5389
5514
  group,
5390
5515
  viewMode,
5391
5516
  strip,
5517
+ slowResponseThresholdSeconds,
5392
5518
  cacheTrends,
5393
5519
  onCompareWithPrevious: handleCompareWithPrevious,
5394
5520
  comparisonPredecessors,
@@ -5589,7 +5715,7 @@ function ProxyViewerContainer() {
5589
5715
  }
5590
5716
  })();
5591
5717
  }, []);
5592
- const { strip } = useStripConfig();
5718
+ const { strip, slowResponseThresholdSeconds } = useStripConfig();
5593
5719
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
5594
5720
  error !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed top-4 right-4 bg-destructive text-destructive-foreground px-4 py-2 rounded-md text-sm z-50", children: error }),
5595
5721
  /* @__PURE__ */ jsxRuntimeExports.jsx(OnboardingBanner, {}),
@@ -5607,7 +5733,8 @@ function ProxyViewerContainer() {
5607
5733
  onClearGroup: handleClearGroup,
5608
5734
  viewMode,
5609
5735
  onViewModeChange: setViewMode,
5610
- strip
5736
+ strip,
5737
+ slowResponseThresholdSeconds
5611
5738
  }
5612
5739
  )
5613
5740
  ] });
@@ -198,7 +198,7 @@ function getResponse() {
198
198
  return event.res;
199
199
  }
200
200
  async function getStartManifest(matchedRoutes) {
201
- const { tsrStartManifest } = await import("../_tanstack-start-manifest_v-DmOZEcJ3.mjs");
201
+ const { tsrStartManifest } = await import("../_tanstack-start-manifest_v-CphS4rZd.mjs");
202
202
  const startManifest = tsrStartManifest();
203
203
  const rootRoute = startManifest.routes[rootRouteId] = startManifest.routes[rootRouteId] || {};
204
204
  rootRoute.assets = rootRoute.assets || [];
@@ -767,7 +767,7 @@ let entriesPromise;
767
767
  let baseManifestPromise;
768
768
  let cachedFinalManifestPromise;
769
769
  async function loadEntries() {
770
- const routerEntry = await import("./router-BrdjOUEW.mjs").then((n) => n.e);
770
+ const routerEntry = await import("./router-pP4GCTQx.mjs").then((n) => n.e);
771
771
  const startEntry = await import("./start-HYkvq4Ni.mjs");
772
772
  return { startEntry, routerEntry };
773
773
  }
@@ -1,6 +1,6 @@
1
1
  import { r as reactExports, j as jsxRuntimeExports } from "../_libs/react.mjs";
2
- import { q as parseJsonText, c as cn, k as TooltipProvider, l as Tooltip, m as TooltipTrigger, n as TooltipContent } from "./index-CDjLoMsk.mjs";
3
- import "./router-BrdjOUEW.mjs";
2
+ import { q as parseJsonText, c as cn, k as TooltipProvider, l as Tooltip, m as TooltipTrigger, n as TooltipContent } from "./index-Cnu-QzAy.mjs";
3
+ import "./router-pP4GCTQx.mjs";
4
4
  import "../_libs/modelcontextprotocol__server.mjs";
5
5
  import "../_libs/jszip.mjs";
6
6
  import { C as Check, c as Copy, a as ChevronDown, f as ChevronRight, r as ChevronsDown } from "../_libs/lucide-react.mjs";
@@ -46,7 +46,7 @@ import "../_libs/debounce-fn.mjs";
46
46
  import "../_libs/mimic-function.mjs";
47
47
  import "../_libs/semver.mjs";
48
48
  import "../_libs/uint8array-extras.mjs";
49
- const appCss = "/assets/index-BGzHFOEX.css";
49
+ const appCss = "/assets/index-DoGvsnbA.css";
50
50
  const Route$k = createRootRoute({
51
51
  head: () => ({
52
52
  meta: [
@@ -70,7 +70,7 @@ function RootDocument({ children }) {
70
70
  ] })
71
71
  ] });
72
72
  }
73
- const $$splitComponentImporter = () => import("./index-CDjLoMsk.mjs").then((n) => n.t);
73
+ const $$splitComponentImporter = () => import("./index-Cnu-QzAy.mjs").then((n) => n.t);
74
74
  const Route$j = createFileRoute("/")({
75
75
  component: lazyRouteComponent($$splitComponentImporter, "component")
76
76
  });
@@ -2158,9 +2158,12 @@ async function getClientInfo(request) {
2158
2158
  inflight.set(port, promise);
2159
2159
  return promise;
2160
2160
  }
2161
+ const DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS = 10;
2162
+ const MAX_SLOW_RESPONSE_THRESHOLD_SECONDS = 600;
2161
2163
  const RuntimeConfigSchema = object({
2162
2164
  stripClaudeCodeBillingHeader: boolean(),
2163
- hasSeenOnboarding: boolean().default(false)
2165
+ hasSeenOnboarding: boolean().default(false),
2166
+ slowResponseThresholdSeconds: number().int().min(0).max(MAX_SLOW_RESPONSE_THRESHOLD_SECONDS).default(DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS)
2164
2167
  });
2165
2168
  function getConfigFilePath() {
2166
2169
  return join(getDataDir(), "config.json");
@@ -2190,9 +2193,17 @@ function resolveInitialConfig() {
2190
2193
  }
2191
2194
  }
2192
2195
  if (process.env["LLM_INSPECTOR_STRIP_CLAUDE_CODE_BILLING_HEADER"] === "1") {
2193
- return { stripClaudeCodeBillingHeader: true, hasSeenOnboarding: false };
2196
+ return {
2197
+ stripClaudeCodeBillingHeader: true,
2198
+ hasSeenOnboarding: false,
2199
+ slowResponseThresholdSeconds: DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS
2200
+ };
2194
2201
  }
2195
- return { stripClaudeCodeBillingHeader: false, hasSeenOnboarding: false };
2202
+ return {
2203
+ stripClaudeCodeBillingHeader: false,
2204
+ hasSeenOnboarding: false,
2205
+ slowResponseThresholdSeconds: DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS
2206
+ };
2196
2207
  }
2197
2208
  function getConfig() {
2198
2209
  return currentConfig;
@@ -3279,7 +3290,8 @@ const Route$c = createFileRoute("/api/health")({
3279
3290
  });
3280
3291
  const RuntimeConfigPatchSchema = object({
3281
3292
  stripClaudeCodeBillingHeader: boolean().optional(),
3282
- hasSeenOnboarding: boolean().optional()
3293
+ hasSeenOnboarding: boolean().optional(),
3294
+ slowResponseThresholdSeconds: number().int().min(0).max(MAX_SLOW_RESPONSE_THRESHOLD_SECONDS).optional()
3283
3295
  }).strict().refine((v) => Object.keys(v).length > 0, {
3284
3296
  message: "At least one field must be provided"
3285
3297
  });
@@ -5016,6 +5028,8 @@ const router = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProper
5016
5028
  export {
5017
5029
  AnthropicResponseSchema$1 as A,
5018
5030
  CapturedLogSchema as C,
5031
+ DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS as D,
5032
+ MAX_SLOW_RESPONSE_THRESHOLD_SECONDS as M,
5019
5033
  OpenAIRequestSchema as O,
5020
5034
  ProviderTestResultsSchema as P,
5021
5035
  RuntimeConfigSchema as R,
@@ -1,4 +1,4 @@
1
- const tsrStartManifest = () => ({ "routes": { "__root__": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/__root.tsx", "children": ["/", "/api/config", "/api/health", "/api/logs", "/api/mcp", "/api/models", "/api/providers", "/api/sessions", "/proxy/$"], "preloads": ["/assets/main-CDMdNDY_.js"], "assets": [] }, "/": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/index.tsx", "assets": [], "preloads": ["/assets/index-DX88k9br.js"] }, "/api/config": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/config.ts", "children": ["/api/config/paths"] }, "/api/health": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/health.ts" }, "/api/logs": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.ts", "children": ["/api/logs/$id", "/api/logs/stream"] }, "/api/mcp": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/mcp.ts" }, "/api/models": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/models.ts" }, "/api/providers": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.ts", "children": ["/api/providers/$providerId", "/api/providers/export", "/api/providers/import", "/api/providers/scan"] }, "/api/sessions": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/sessions.ts" }, "/proxy/$": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/proxy/$.ts" }, "/api/config/paths": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/config.paths.ts" }, "/api/logs/$id": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.$id.ts", "children": ["/api/logs/$id/chunks", "/api/logs/$id/replay"] }, "/api/logs/stream": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.stream.ts" }, "/api/providers/$providerId": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.$providerId.ts", "children": ["/api/providers/$providerId/test"] }, "/api/providers/export": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.export.ts" }, "/api/providers/import": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.import.ts" }, "/api/providers/scan": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.scan.ts" }, "/api/logs/$id/chunks": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.$id.chunks.ts" }, "/api/logs/$id/replay": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.$id.replay.ts" }, "/api/providers/$providerId/test": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.$providerId.test.ts", "children": ["/api/providers/$providerId/test/log"] }, "/api/providers/$providerId/test/log": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.$providerId.test.log.ts" } }, "clientEntry": "/assets/main-CDMdNDY_.js" });
1
+ const tsrStartManifest = () => ({ "routes": { "__root__": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/__root.tsx", "children": ["/", "/api/config", "/api/health", "/api/logs", "/api/mcp", "/api/models", "/api/providers", "/api/sessions", "/proxy/$"], "preloads": ["/assets/main-DRu10KNQ.js"], "assets": [] }, "/": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/index.tsx", "assets": [], "preloads": ["/assets/index-DpbutOvo.js"] }, "/api/config": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/config.ts", "children": ["/api/config/paths"] }, "/api/health": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/health.ts" }, "/api/logs": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.ts", "children": ["/api/logs/$id", "/api/logs/stream"] }, "/api/mcp": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/mcp.ts" }, "/api/models": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/models.ts" }, "/api/providers": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.ts", "children": ["/api/providers/$providerId", "/api/providers/export", "/api/providers/import", "/api/providers/scan"] }, "/api/sessions": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/sessions.ts" }, "/proxy/$": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/proxy/$.ts" }, "/api/config/paths": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/config.paths.ts" }, "/api/logs/$id": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.$id.ts", "children": ["/api/logs/$id/chunks", "/api/logs/$id/replay"] }, "/api/logs/stream": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.stream.ts" }, "/api/providers/$providerId": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.$providerId.ts", "children": ["/api/providers/$providerId/test"] }, "/api/providers/export": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.export.ts" }, "/api/providers/import": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.import.ts" }, "/api/providers/scan": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.scan.ts" }, "/api/logs/$id/chunks": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.$id.chunks.ts" }, "/api/logs/$id/replay": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.$id.replay.ts" }, "/api/providers/$providerId/test": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.$providerId.test.ts", "children": ["/api/providers/$providerId/test/log"] }, "/api/providers/$providerId/test/log": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.$providerId.test.log.ts" } }, "clientEntry": "/assets/main-DRu10KNQ.js" });
2
2
  export {
3
3
  tsrStartManifest
4
4
  };