@tonyclaw/llm-inspector 1.7.8 → 1.8.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 (35) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/index-BLVa7n9b.css +1 -0
  3. package/.output/public/assets/index-DH3FOgcK.js +97 -0
  4. package/.output/public/assets/main-Beo3LJDa.js +17 -0
  5. package/.output/server/_libs/dequal.mjs +27 -0
  6. package/.output/server/_libs/lucide-react.mjs +98 -91
  7. package/.output/server/_libs/swr.mjs +938 -0
  8. package/.output/server/_libs/use-sync-external-store.mjs +64 -1
  9. package/.output/server/_libs/zod.mjs +1 -0
  10. package/.output/server/_ssr/{index-hNquJMfH.mjs → index-HkueJ4Un.mjs} +218 -71
  11. package/.output/server/_ssr/index.mjs +2 -2
  12. package/.output/server/_ssr/{router-MmnX-LYh.mjs → router-DTswxb7l.mjs} +264 -52
  13. package/.output/server/_tanstack-start-manifest_v-DhUuivt-.mjs +4 -0
  14. package/.output/server/index.mjs +26 -26
  15. package/package.json +2 -1
  16. package/src/components/ProxyViewer.tsx +2 -0
  17. package/src/components/providers/ProviderCard.tsx +38 -33
  18. package/src/components/providers/ProviderLogo.tsx +6 -1
  19. package/src/components/providers/ProvidersPanel.tsx +144 -43
  20. package/src/components/providers/SettingsDialog.tsx +5 -3
  21. package/src/components/proxy-viewer/ConversationGroup.tsx +3 -3
  22. package/src/components/proxy-viewer/LogEntry.tsx +6 -3
  23. package/src/components/proxy-viewer/LogEntryHeader.tsx +3 -2
  24. package/src/lib/useProviders.ts +30 -0
  25. package/src/proxy/formats/anthropic/stream.ts +3 -2
  26. package/src/proxy/formats/openai/stream.ts +3 -2
  27. package/src/proxy/handler.ts +5 -0
  28. package/src/proxy/providers.ts +98 -0
  29. package/src/routes/__root.tsx +4 -1
  30. package/src/routes/api/providers.export.ts +26 -0
  31. package/src/routes/api/providers.import.ts +47 -0
  32. package/.output/public/assets/index-B3RwBPLW.css +0 -1
  33. package/.output/public/assets/index-C8o6bEv6.js +0 -97
  34. package/.output/public/assets/main-Bxc5pKCu.js +0 -17
  35. package/.output/server/_tanstack-start-manifest_v-CYKtU_9S.mjs +0 -4
@@ -1 +1,64 @@
1
-
1
+ import { b as requireReact } from "./react.mjs";
2
+ var shim = { exports: {} };
3
+ var useSyncExternalStoreShim_production = {};
4
+ var hasRequiredUseSyncExternalStoreShim_production;
5
+ function requireUseSyncExternalStoreShim_production() {
6
+ if (hasRequiredUseSyncExternalStoreShim_production) return useSyncExternalStoreShim_production;
7
+ hasRequiredUseSyncExternalStoreShim_production = 1;
8
+ var React = /* @__PURE__ */ requireReact();
9
+ function is(x, y) {
10
+ return x === y && (0 !== x || 1 / x === 1 / y) || x !== x && y !== y;
11
+ }
12
+ var objectIs = "function" === typeof Object.is ? Object.is : is, useState = React.useState, useEffect = React.useEffect, useLayoutEffect = React.useLayoutEffect, useDebugValue = React.useDebugValue;
13
+ function useSyncExternalStore$2(subscribe, getSnapshot) {
14
+ var value = getSnapshot(), _useState = useState({ inst: { value, getSnapshot } }), inst = _useState[0].inst, forceUpdate = _useState[1];
15
+ useLayoutEffect(
16
+ function() {
17
+ inst.value = value;
18
+ inst.getSnapshot = getSnapshot;
19
+ checkIfSnapshotChanged(inst) && forceUpdate({ inst });
20
+ },
21
+ [subscribe, value, getSnapshot]
22
+ );
23
+ useEffect(
24
+ function() {
25
+ checkIfSnapshotChanged(inst) && forceUpdate({ inst });
26
+ return subscribe(function() {
27
+ checkIfSnapshotChanged(inst) && forceUpdate({ inst });
28
+ });
29
+ },
30
+ [subscribe]
31
+ );
32
+ useDebugValue(value);
33
+ return value;
34
+ }
35
+ function checkIfSnapshotChanged(inst) {
36
+ var latestGetSnapshot = inst.getSnapshot;
37
+ inst = inst.value;
38
+ try {
39
+ var nextValue = latestGetSnapshot();
40
+ return !objectIs(inst, nextValue);
41
+ } catch (error) {
42
+ return true;
43
+ }
44
+ }
45
+ function useSyncExternalStore$1(subscribe, getSnapshot) {
46
+ return getSnapshot();
47
+ }
48
+ var shim2 = "undefined" === typeof window || "undefined" === typeof window.document || "undefined" === typeof window.document.createElement ? useSyncExternalStore$1 : useSyncExternalStore$2;
49
+ useSyncExternalStoreShim_production.useSyncExternalStore = void 0 !== React.useSyncExternalStore ? React.useSyncExternalStore : shim2;
50
+ return useSyncExternalStoreShim_production;
51
+ }
52
+ var hasRequiredShim;
53
+ function requireShim() {
54
+ if (hasRequiredShim) return shim.exports;
55
+ hasRequiredShim = 1;
56
+ {
57
+ shim.exports = /* @__PURE__ */ requireUseSyncExternalStoreShim_production();
58
+ }
59
+ return shim.exports;
60
+ }
61
+ var shimExports = /* @__PURE__ */ requireShim();
62
+ export {
63
+ shimExports as s
64
+ };
@@ -4451,6 +4451,7 @@ export {
4451
4451
  lazy as c,
4452
4452
  discriminatedUnion as d,
4453
4453
  _null as e,
4454
+ unknown as f,
4454
4455
  literal as l,
4455
4456
  number as n,
4456
4457
  object as o,
@@ -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, a as parseRequest, p as parseOpenAIResponse, I as InspectorResponseSchema } from "./router-MmnX-LYh.mjs";
2
+ import { C as CapturedLogSchema, a as parseRequest, p as parseOpenAIResponse, I as InspectorResponseSchema } from "./router-DTswxb7l.mjs";
3
3
  import { u as useVirtualizer } from "../_libs/tanstack__react-virtual.mjs";
4
4
  import { J as JSZip } from "../_libs/jszip.mjs";
5
5
  import { c as clsx } from "../_libs/clsx.mjs";
@@ -7,9 +7,10 @@ import { t as twMerge } from "../_libs/tailwind-merge.mjs";
7
7
  import { c as cva } from "../_libs/class-variance-authority.mjs";
8
8
  import { R as Root, T as Trigger$1, C as Content, a as Close, b as Title, P as Portal$1, O as Overlay } from "../_libs/radix-ui__react-dialog.mjs";
9
9
  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";
10
- import { D as Download, L as LayoutGrid, a as List, S as Settings, C as ChevronDown, b as Check, R as RotateCcw, X, P as Plus, c as Copy, d as CircleAlert, e as ChevronUp, f as ChevronRight, g as Clock, M as MessageSquare, Z as Zap, h as LoaderCircle, W as Wrench, G as Globe, U as User, F as FileTerminal, i as Radio, E as ExternalLink, j as EyeOff, k as Eye, l as RotateCw, m as Pencil, T as Trash2, n as TriangleAlert, o as Minus, p as CircleCheckBig, q as CircleStop, r as CircleQuestionMark, s as Server, t as Gauge, u as Lock, v as Wifi, w as WifiOff, x as ChevronsUp, y as ChevronsDown, z as Terminal, B as Brain } from "../_libs/lucide-react.mjs";
10
+ import { u as useSWR } from "../_libs/swr.mjs";
11
+ import { D as Download, L as LayoutGrid, a as List, S as Settings, C as ChevronDown, b as Check, R as RotateCcw, X, U as Upload, P as Plus, c as Copy, d as CircleAlert, e as ChevronUp, f as ChevronRight, g as Clock, M as MessageSquare, Z as Zap, h as LoaderCircle, W as Wrench, G as Globe, i as User, F as FileTerminal, j as Radio, E as ExternalLink, k as EyeOff, l as Eye, m as RotateCw, n as Pencil, T as Trash2, o as TriangleAlert, p as Minus, q as CircleCheckBig, r as CircleStop, s as CircleQuestionMark, t as Server, u as Gauge, v as Lock, w as Wifi, x as WifiOff, y as ChevronsUp, z as ChevronsDown, A as Terminal, B as Brain } from "../_libs/lucide-react.mjs";
11
12
  import { M as Markdown } from "../_libs/react-markdown.mjs";
12
- import { a as array, s as string, u as union, o as object, l as literal, b as boolean, n as number } from "../_libs/zod.mjs";
13
+ import { a as array, s as string, u as union, o as object, l as literal, n as number, b as boolean } from "../_libs/zod.mjs";
13
14
  import { R as Root2$1, L as List$1, T as Trigger$2, C as Content$1 } from "../_libs/radix-ui__react-tabs.mjs";
14
15
  import { S as Slot } from "../_libs/radix-ui__react-slot.mjs";
15
16
  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";
@@ -57,6 +58,8 @@ import "../_libs/mimic-function.mjs";
57
58
  import "../_libs/semver.mjs";
58
59
  import "../_libs/uint8array-extras.mjs";
59
60
  import "node:child_process";
61
+ import "../_libs/use-sync-external-store.mjs";
62
+ import "../_libs/dequal.mjs";
60
63
  import "../_libs/tanstack__virtual-core.mjs";
61
64
  import "../_libs/readable-stream.mjs";
62
65
  import "../_libs/process-nextick-args.mjs";
@@ -196,6 +199,10 @@ async function exportLogsAsZip(logs) {
196
199
  document.body.removeChild(anchor);
197
200
  URL.revokeObjectURL(url);
198
201
  }
202
+ const version = "1.8.0";
203
+ const packageJson = {
204
+ version
205
+ };
199
206
  function cn(...inputs) {
200
207
  return twMerge(clsx(inputs));
201
208
  }
@@ -891,7 +898,15 @@ const AnthropicLogo = React.memo(
891
898
  ({ className }) => /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: AnthropicLogoSvg, alt: "Anthropic", className, style: sizeStyle })
892
899
  );
893
900
  const OpenAILogo = React.memo(
894
- ({ className }) => /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: OpenAILogoSvg, alt: "OpenAI", className, style: sizeStyle })
901
+ ({ className }) => /* @__PURE__ */ jsxRuntimeExports.jsx(
902
+ "img",
903
+ {
904
+ src: OpenAILogoSvg,
905
+ alt: "OpenAI",
906
+ className,
907
+ style: { ...sizeStyle, background: "white", borderRadius: "4px" }
908
+ }
909
+ )
895
910
  );
896
911
  const DeepSeekLogo = React.memo(
897
912
  ({ className }) => /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: DeepSeekLogoSvg, alt: "DeepSeek", className, style: sizeStyle })
@@ -946,7 +961,7 @@ const STATUS_BADGE_CLASSES = {
946
961
  server_error: "",
947
962
  pending: "bg-muted text-muted-foreground border-border"
948
963
  };
949
- function LogEntryHeader({
964
+ const LogEntryHeader = reactExports.memo(function LogEntryHeader2({
950
965
  log,
951
966
  parsedRequest,
952
967
  expanded,
@@ -1098,7 +1113,7 @@ function LogEntryHeader({
1098
1113
  ]
1099
1114
  }
1100
1115
  );
1101
- }
1116
+ });
1102
1117
  function Dialog({
1103
1118
  ...props
1104
1119
  }) {
@@ -1832,7 +1847,10 @@ function CopyButton({
1832
1847
  }
1833
1848
  );
1834
1849
  }
1835
- function LogEntry({ log, viewMode = "simple" }) {
1850
+ const LogEntry = reactExports.memo(function LogEntry2({
1851
+ log,
1852
+ viewMode = "simple"
1853
+ }) {
1836
1854
  const [expanded, setExpanded] = reactExports.useState(false);
1837
1855
  const [requestCopied, setRequestCopied] = reactExports.useState(false);
1838
1856
  const [responseCopied, setResponseCopied] = reactExports.useState(false);
@@ -1953,7 +1971,7 @@ function LogEntry({ log, viewMode = "simple" }) {
1953
1971
  ] }),
1954
1972
  /* @__PURE__ */ jsxRuntimeExports.jsx(ReplayDialog, { log, open: replayOpen, onOpenChange: setReplayOpen })
1955
1973
  ] });
1956
- }
1974
+ });
1957
1975
  function computeStats(logs) {
1958
1976
  let totalInput = 0;
1959
1977
  let totalOutput = 0;
@@ -1963,7 +1981,7 @@ function computeStats(logs) {
1963
1981
  }
1964
1982
  return { totalInputTokens: totalInput, totalOutputTokens: totalOutput };
1965
1983
  }
1966
- function ConversationGroup({
1984
+ const ConversationGroup = reactExports.memo(function ConversationGroup2({
1967
1985
  group,
1968
1986
  viewMode = "simple"
1969
1987
  }) {
@@ -1988,7 +2006,7 @@ function ConversationGroup({
1988
2006
  ),
1989
2007
  expanded && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pl-4 border-l-2 border-muted ml-3", children: group.logs.map((log) => /* @__PURE__ */ jsxRuntimeExports.jsx(LogEntry, { log, viewMode }, log.id)) })
1990
2008
  ] });
1991
- }
2009
+ });
1992
2010
  function Select({
1993
2011
  ...props
1994
2012
  }) {
@@ -2117,6 +2135,9 @@ function maskApiKey(apiKey) {
2117
2135
  function hasSuccessField(result) {
2118
2136
  return Object.prototype.hasOwnProperty.call(result, "success");
2119
2137
  }
2138
+ function isNotConfiguredState(result) {
2139
+ return Object.prototype.hasOwnProperty.call(result, "notConfigured");
2140
+ }
2120
2141
  function getErrorIcon(type) {
2121
2142
  const iconProps = { className: "size-3", strokeWidth: 2 };
2122
2143
  switch (type) {
@@ -2140,18 +2161,21 @@ function getErrorIcon(type) {
2140
2161
  return /* @__PURE__ */ jsxRuntimeExports.jsx(CircleAlert, { ...iconProps });
2141
2162
  }
2142
2163
  }
2143
- function TestStatus({
2144
- result,
2145
- isTesting
2146
- }) {
2164
+ function TestStatus({ result }) {
2147
2165
  if (!hasSuccessField(result)) {
2148
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [
2149
- /* @__PURE__ */ jsxRuntimeExports.jsx(Minus, { className: "size-3" }),
2150
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Not configured" })
2166
+ if (isNotConfiguredState(result)) {
2167
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 text-xs text-muted-foreground shrink-0", children: [
2168
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Minus, { className: "size-3" }),
2169
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Not configured" })
2170
+ ] });
2171
+ }
2172
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 text-xs text-muted-foreground shrink-0", children: [
2173
+ /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCw, { className: "size-3 animate-spin" }),
2174
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Testing..." })
2151
2175
  ] });
2152
2176
  }
2153
2177
  if (result.success) {
2154
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 text-xs text-green-600", children: [
2178
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 text-xs text-green-600 shrink-0", children: [
2155
2179
  /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheckBig, { className: "size-3" }),
2156
2180
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Connected" })
2157
2181
  ] });
@@ -2160,20 +2184,18 @@ function TestStatus({
2160
2184
  const errorMessage = error?.message ?? "Connection failed";
2161
2185
  const errorHint = error?.hint;
2162
2186
  const errorType = error?.type ?? "unknown";
2163
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1 min-w-0", children: [
2164
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
2165
- "div",
2166
- {
2167
- className: "flex items-center gap-1 text-xs text-red-600 min-w-0",
2168
- title: error?.details ?? errorMessage,
2169
- children: [
2170
- getErrorIcon(errorType),
2171
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: errorMessage })
2172
- ]
2173
- }
2174
- ),
2175
- errorHint !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-muted-foreground pl-4 truncate", title: errorHint, children: errorHint })
2176
- ] });
2187
+ const fullMessage = errorHint !== void 0 ? `${errorMessage} ${errorHint}` : errorMessage;
2188
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
2189
+ "div",
2190
+ {
2191
+ className: "flex items-center gap-1 text-xs text-red-600 shrink-0 max-w-[200px]",
2192
+ title: error?.details ?? fullMessage,
2193
+ children: [
2194
+ getErrorIcon(errorType),
2195
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: errorMessage })
2196
+ ]
2197
+ }
2198
+ );
2177
2199
  }
2178
2200
  function ProviderCard({
2179
2201
  provider,
@@ -2225,7 +2247,7 @@ function ProviderCard({
2225
2247
  " ",
2226
2248
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: provider.anthropicBaseUrl })
2227
2249
  ] }),
2228
- testResults && /* @__PURE__ */ jsxRuntimeExports.jsx(TestStatus, { result: testResults.anthropic.nonStreaming, isTesting })
2250
+ testResults && /* @__PURE__ */ jsxRuntimeExports.jsx(TestStatus, { result: testResults.anthropic.nonStreaming })
2229
2251
  ] }),
2230
2252
  provider.openaiBaseUrl !== void 0 && provider.openaiBaseUrl !== "" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
2231
2253
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs text-muted-foreground min-w-0 flex-1", children: [
@@ -2233,7 +2255,7 @@ function ProviderCard({
2233
2255
  " ",
2234
2256
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: provider.openaiBaseUrl })
2235
2257
  ] }),
2236
- testResults && /* @__PURE__ */ jsxRuntimeExports.jsx(TestStatus, { result: testResults.openai.nonStreaming, isTesting })
2258
+ testResults && /* @__PURE__ */ jsxRuntimeExports.jsx(TestStatus, { result: testResults.openai.nonStreaming })
2237
2259
  ] }),
2238
2260
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2 pt-1 border-t", children: [
2239
2261
  onTest !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(
@@ -2502,13 +2524,11 @@ function ProvidersPanel({
2502
2524
  externalTestResults,
2503
2525
  externalTestingProviders,
2504
2526
  externalTestingTimeLeft,
2505
- onProvidersChange,
2527
+ onProvidersMutate,
2506
2528
  onTestResultsChange,
2507
2529
  onTestingProvidersChange,
2508
2530
  onTestingTimeLeftChange
2509
2531
  }) {
2510
- const [internalProviders, setInternalProviders] = reactExports.useState([]);
2511
- const [isLoading, setIsLoading] = reactExports.useState(true);
2512
2532
  const [showForm, setShowForm] = reactExports.useState(false);
2513
2533
  const [editingProvider, setEditingProvider] = reactExports.useState();
2514
2534
  const [error, setError] = reactExports.useState(null);
@@ -2519,8 +2539,7 @@ function ProvidersPanel({
2519
2539
  );
2520
2540
  const [configPath, setConfigPath] = reactExports.useState(null);
2521
2541
  const [configPathCopied, setConfigPathCopied] = reactExports.useState(false);
2522
- const providers = externalProviders ?? internalProviders;
2523
- const setProviders = onProvidersChange ?? setInternalProviders;
2542
+ const providers = externalProviders ?? [];
2524
2543
  const testResults = externalTestResults ?? internalTestResults;
2525
2544
  const testingProviders = externalTestingProviders ?? internalTestingProviders;
2526
2545
  const testingTimeLeft = externalTestingTimeLeft ?? internalTestingTimeLeft;
@@ -2535,19 +2554,7 @@ function ProvidersPanel({
2535
2554
  setInternalTestingTimeLeft((prev) => ({ ...prev, [id]: seconds }));
2536
2555
  }
2537
2556
  };
2538
- const fetchProviders = reactExports.useCallback(async () => {
2539
- try {
2540
- const providersRes = await fetch("/api/providers");
2541
- const providersData = await providersRes.json();
2542
- setProviders(providersData);
2543
- } catch {
2544
- setError("Failed to load providers");
2545
- } finally {
2546
- setIsLoading(false);
2547
- }
2548
- }, []);
2549
2557
  reactExports.useEffect(() => {
2550
- void fetchProviders();
2551
2558
  void (async () => {
2552
2559
  try {
2553
2560
  const res = await fetch("/api/config/paths");
@@ -2558,10 +2565,19 @@ function ProvidersPanel({
2558
2565
  } catch {
2559
2566
  }
2560
2567
  })();
2561
- }, [fetchProviders]);
2568
+ }, []);
2562
2569
  const TEST_TIMEOUT_SECONDS = 30;
2563
2570
  const runTest = reactExports.useCallback(
2564
2571
  async (providerId) => {
2572
+ const resetResults = {
2573
+ anthropic: { nonStreaming: { testing: true }, streaming: { testing: true } },
2574
+ openai: { nonStreaming: { testing: true }, streaming: { testing: true } }
2575
+ };
2576
+ if (onTestResultsChange) {
2577
+ onTestResultsChange(providerId, resetResults);
2578
+ } else {
2579
+ setInternalTestResults((prev) => ({ ...prev, [providerId]: resetResults }));
2580
+ }
2565
2581
  if (onTestingProvidersChange) {
2566
2582
  onTestingProvidersChange(providerId, true);
2567
2583
  } else {
@@ -2614,7 +2630,8 @@ function ProvidersPanel({
2614
2630
  }
2615
2631
  }
2616
2632
  } catch (err) {
2617
- if (err instanceof Error && err.name === "AbortError") {
2633
+ const isAbort = err instanceof Error && err.name === "AbortError";
2634
+ if (isAbort) {
2618
2635
  const timeoutResult = {
2619
2636
  anthropic: {
2620
2637
  nonStreaming: {
@@ -2640,14 +2657,17 @@ function ProvidersPanel({
2640
2657
  } finally {
2641
2658
  clearInterval(intervalId);
2642
2659
  setTestingTimeLeft(providerId, void 0);
2643
- if (onTestingProvidersChange) {
2644
- onTestingProvidersChange(providerId, false);
2645
- } else {
2646
- setInternalTestingProviders((prev) => {
2647
- const next = new Set(prev);
2648
- next.delete(providerId);
2649
- return next;
2650
- });
2660
+ try {
2661
+ if (onTestingProvidersChange) {
2662
+ onTestingProvidersChange(providerId, false);
2663
+ } else {
2664
+ setInternalTestingProviders((prev) => {
2665
+ const next = new Set(prev);
2666
+ next.delete(providerId);
2667
+ return next;
2668
+ });
2669
+ }
2670
+ } catch {
2651
2671
  }
2652
2672
  }
2653
2673
  },
@@ -2674,7 +2694,7 @@ function ProvidersPanel({
2674
2694
  return;
2675
2695
  }
2676
2696
  const newProvider = await res.json();
2677
- await fetchProviders();
2697
+ onProvidersMutate?.();
2678
2698
  setShowForm(false);
2679
2699
  await runTest(newProvider.id);
2680
2700
  })();
@@ -2701,7 +2721,7 @@ function ProvidersPanel({
2701
2721
  return;
2702
2722
  }
2703
2723
  const updated = await res.json();
2704
- await fetchProviders();
2724
+ onProvidersMutate?.();
2705
2725
  setEditingProvider(void 0);
2706
2726
  await runTest(updated.id);
2707
2727
  })();
@@ -2717,10 +2737,69 @@ function ProvidersPanel({
2717
2737
  setError(err.error ?? "Failed to delete provider");
2718
2738
  return;
2719
2739
  }
2720
- await fetchProviders();
2740
+ onProvidersMutate?.();
2721
2741
  })();
2722
2742
  }
2723
- if (isLoading && providers.length === 0) {
2743
+ const fileInputRef = reactExports.useRef(null);
2744
+ function handleExport(includeKeys) {
2745
+ const url = `/api/providers/export${""}`;
2746
+ void (async () => {
2747
+ try {
2748
+ const res = await fetch(url);
2749
+ if (!res.ok) {
2750
+ setError("Failed to export providers");
2751
+ return;
2752
+ }
2753
+ const blob = await res.blob();
2754
+ const downloadUrl = URL.createObjectURL(blob);
2755
+ const a = document.createElement("a");
2756
+ a.href = downloadUrl;
2757
+ a.download = res.headers.get("Content-Disposition")?.match(/filename="(.+)"/)?.[1] ?? "providers.json";
2758
+ document.body.appendChild(a);
2759
+ a.click();
2760
+ document.body.removeChild(a);
2761
+ URL.revokeObjectURL(downloadUrl);
2762
+ } catch {
2763
+ setError("Failed to export providers");
2764
+ }
2765
+ })();
2766
+ }
2767
+ function handleImportClick() {
2768
+ fileInputRef.current?.click();
2769
+ }
2770
+ function handleFileChange(e) {
2771
+ const file = e.target.files?.[0];
2772
+ if (!file) return;
2773
+ void (async () => {
2774
+ try {
2775
+ const text = await file.text();
2776
+ const res = await fetch("/api/providers/import", {
2777
+ method: "POST",
2778
+ headers: { "Content-Type": "application/json" },
2779
+ body: JSON.stringify(text)
2780
+ });
2781
+ const ImportResponseSchema = object({
2782
+ success: boolean().optional(),
2783
+ imported: number().optional(),
2784
+ message: string().optional(),
2785
+ errors: array(string()).optional()
2786
+ });
2787
+ const data = ImportResponseSchema.parse(await res.json());
2788
+ if (res.ok && data.imported !== void 0 && data.imported > 0) {
2789
+ onProvidersMutate?.();
2790
+ setError(null);
2791
+ } else if (data.errors && data.errors.length > 0) {
2792
+ setError(data.errors.join("; "));
2793
+ } else {
2794
+ setError(data.message ?? "Import failed");
2795
+ }
2796
+ } catch {
2797
+ setError("Failed to import providers");
2798
+ }
2799
+ e.target.value = "";
2800
+ })();
2801
+ }
2802
+ if (providers.length === 0) {
2724
2803
  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center py-8", children: /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-muted-foreground", children: "Loading providers..." }) });
2725
2804
  }
2726
2805
  if (showForm || editingProvider) {
@@ -2740,11 +2819,49 @@ function ProvidersPanel({
2740
2819
  ] });
2741
2820
  }
2742
2821
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
2743
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
2822
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between sticky top-0 z-10 bg-background pb-2", children: [
2744
2823
  /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-lg font-medium", children: "Providers" }),
2745
- /* @__PURE__ */ jsxRuntimeExports.jsxs(Button, { onClick: () => setShowForm(true), size: "sm", className: "gap-1", children: [
2746
- /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { className: "size-4" }),
2747
- "Add Provider"
2824
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
2825
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
2826
+ Button,
2827
+ {
2828
+ variant: "outline",
2829
+ size: "sm",
2830
+ onClick: () => handleExport(),
2831
+ className: "gap-1 hover:bg-muted",
2832
+ children: [
2833
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Download, { className: "size-3" }),
2834
+ "Export"
2835
+ ]
2836
+ }
2837
+ ),
2838
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
2839
+ Button,
2840
+ {
2841
+ variant: "outline",
2842
+ size: "sm",
2843
+ onClick: handleImportClick,
2844
+ className: "gap-1 hover:bg-muted",
2845
+ children: [
2846
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { className: "size-3" }),
2847
+ "Import"
2848
+ ]
2849
+ }
2850
+ ),
2851
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
2852
+ "input",
2853
+ {
2854
+ type: "file",
2855
+ ref: fileInputRef,
2856
+ accept: ".json",
2857
+ onChange: handleFileChange,
2858
+ style: { display: "none" }
2859
+ }
2860
+ ),
2861
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(Button, { onClick: () => setShowForm(true), size: "sm", className: "gap-1", children: [
2862
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { className: "size-4" }),
2863
+ "Add Provider"
2864
+ ] })
2748
2865
  ] })
2749
2866
  ] }),
2750
2867
  configPath !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground bg-muted/30 rounded-md px-3 py-2", children: [
@@ -2793,10 +2910,34 @@ function ProvidersPanel({
2793
2910
  )) })
2794
2911
  ] });
2795
2912
  }
2913
+ async function fetcher(url) {
2914
+ const response = await fetch(url);
2915
+ const data = await response.json();
2916
+ if (!Array.isArray(data)) {
2917
+ return [];
2918
+ }
2919
+ return data;
2920
+ }
2921
+ function useProviders() {
2922
+ const response = useSWR(
2923
+ "/api/providers",
2924
+ fetcher,
2925
+ {
2926
+ revalidateOnFocus: false,
2927
+ revalidateIfStale: false
2928
+ }
2929
+ );
2930
+ return {
2931
+ providers: response.data ?? [],
2932
+ isLoading: response.isLoading,
2933
+ isError: response.error,
2934
+ mutate: response.mutate
2935
+ };
2936
+ }
2796
2937
  function SettingsDialog() {
2797
2938
  const [open, setOpen] = reactExports.useState(false);
2798
2939
  const [activeTab, setActiveTab] = reactExports.useState("providers");
2799
- const [providers, setProviders] = reactExports.useState([]);
2940
+ const { providers, mutate } = useProviders();
2800
2941
  const [testResults, setTestResults] = reactExports.useState({});
2801
2942
  const [testingProviders, setTestingProviders] = reactExports.useState(/* @__PURE__ */ new Set());
2802
2943
  const [testingTimeLeft, setTestingTimeLeft] = reactExports.useState({});
@@ -2846,7 +2987,9 @@ function SettingsDialog() {
2846
2987
  externalTestResults: testResults,
2847
2988
  externalTestingProviders: testingProviders,
2848
2989
  externalTestingTimeLeft: testingTimeLeft,
2849
- onProvidersChange: setProviders,
2990
+ onProvidersMutate: () => {
2991
+ void mutate();
2992
+ },
2850
2993
  onTestResultsChange: handleTestResultsChange,
2851
2994
  onTestingProvidersChange: handleTestingProvidersChange,
2852
2995
  onTestingTimeLeftChange: handleTestingTimeLeftChange
@@ -2956,6 +3099,10 @@ function ProxyViewer({
2956
3099
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "max-w-[1200px] mx-auto flex flex-col h-screen", style: { maxHeight: "100vh" }, children: [
2957
3100
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4 mb-4 px-6 pt-6", children: [
2958
3101
  /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { className: "text-lg font-bold flex-1", children: "LLM Proxy Inspector" }),
3102
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground text-xs font-mono", children: [
3103
+ "v",
3104
+ packageJson.version
3105
+ ] }),
2959
3106
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center border border-border rounded-md overflow-hidden", children: [
2960
3107
  /* @__PURE__ */ jsxRuntimeExports.jsx(
2961
3108
  "button",
@@ -197,7 +197,7 @@ function getResponse() {
197
197
  return event.res;
198
198
  }
199
199
  async function getStartManifest(matchedRoutes) {
200
- const { tsrStartManifest } = await import("../_tanstack-start-manifest_v-CYKtU_9S.mjs");
200
+ const { tsrStartManifest } = await import("../_tanstack-start-manifest_v-DhUuivt-.mjs");
201
201
  const startManifest = tsrStartManifest();
202
202
  const rootRoute = startManifest.routes[rootRouteId] = startManifest.routes[rootRouteId] || {};
203
203
  rootRoute.assets = rootRoute.assets || [];
@@ -766,7 +766,7 @@ let entriesPromise;
766
766
  let baseManifestPromise;
767
767
  let cachedFinalManifestPromise;
768
768
  async function loadEntries() {
769
- const routerEntry = await import("./router-MmnX-LYh.mjs").then((n) => n.r);
769
+ const routerEntry = await import("./router-DTswxb7l.mjs").then((n) => n.r);
770
770
  const startEntry = await import("./start-HYkvq4Ni.mjs");
771
771
  return { startEntry, routerEntry };
772
772
  }