@tonyclaw/llm-inspector 1.7.9 → 1.9.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 (41) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/index-DdJSLfxK.css +1 -0
  3. package/.output/public/assets/index-DyKLPMPn.js +97 -0
  4. package/.output/public/assets/main-Cu0oTDfX.js +17 -0
  5. package/.output/server/_libs/dequal.mjs +27 -0
  6. package/.output/server/_libs/swr.mjs +938 -0
  7. package/.output/server/_libs/use-sync-external-store.mjs +64 -1
  8. package/.output/server/_ssr/{index-CAIDMqNv.mjs → index-COIATcfa.mjs} +186 -88
  9. package/.output/server/_ssr/index.mjs +2 -2
  10. package/.output/server/_ssr/{router-CsCLdrXq.mjs → router-CwmgKXBJ.mjs} +259 -74
  11. package/.output/server/{_tanstack-start-manifest_v-BF6ge6dS.mjs → _tanstack-start-manifest_v-C7hQOzvX.mjs} +1 -1
  12. package/.output/server/index.mjs +23 -23
  13. package/README.md +8 -209
  14. package/package.json +2 -1
  15. package/src/components/ProxyViewer.tsx +2 -0
  16. package/src/components/ProxyViewerContainer.tsx +10 -1
  17. package/src/components/providers/ProviderCard.tsx +57 -48
  18. package/src/components/providers/ProviderForm.tsx +21 -0
  19. package/src/components/providers/ProviderLogo.tsx +6 -1
  20. package/src/components/providers/ProvidersPanel.tsx +29 -34
  21. package/src/components/providers/SettingsDialog.tsx +5 -3
  22. package/src/components/proxy-viewer/LogEntry.tsx +7 -0
  23. package/src/components/proxy-viewer/ResponseView.tsx +32 -6
  24. package/src/lib/useProviders.ts +30 -0
  25. package/src/proxy/chunkStorage.ts +4 -6
  26. package/src/proxy/formats/anthropic/schemas.ts +9 -0
  27. package/src/proxy/formats/anthropic/stream.ts +11 -0
  28. package/src/proxy/formats/openai/stream.ts +15 -0
  29. package/src/proxy/handler.ts +34 -27
  30. package/src/proxy/logIndex.ts +52 -7
  31. package/src/proxy/logger.ts +60 -10
  32. package/src/proxy/providers.ts +5 -0
  33. package/src/proxy/schemas.ts +2 -0
  34. package/src/proxy/socketTracker.ts +90 -36
  35. package/src/proxy/store.ts +24 -14
  36. package/src/routes/__root.tsx +4 -1
  37. package/src/routes/api/providers.$providerId.ts +1 -0
  38. package/src/routes/api/providers.ts +2 -0
  39. package/.output/public/assets/index-B3RwBPLW.css +0 -1
  40. package/.output/public/assets/index-CB8ZIeEk.js +0 -97
  41. package/.output/public/assets/main-BrU8NdGQ.js +0 -17
@@ -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
+ };
@@ -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-CsCLdrXq.mjs";
2
+ import { C as CapturedLogSchema, a as parseRequest, p as parseOpenAIResponse, I as InspectorResponseSchema } from "./router-CwmgKXBJ.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,6 +7,7 @@ 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 { u as useSWR } from "../_libs/swr.mjs";
10
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
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";
@@ -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.9.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 })
@@ -1494,22 +1509,37 @@ function ResponseView({
1494
1509
  outputTokens,
1495
1510
  cacheCreationInputTokens,
1496
1511
  cacheReadInputTokens,
1497
- apiFormat
1512
+ apiFormat,
1513
+ error
1498
1514
  }) {
1499
- if (responseText === null) {
1515
+ if (responseText === null && error === void 0) {
1500
1516
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 py-3", children: [
1501
1517
  /* @__PURE__ */ jsxRuntimeExports.jsx(StatusIndicator, { status: responseStatus }),
1502
1518
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-muted-foreground italic", children: "No response" })
1503
1519
  ] });
1504
1520
  }
1505
- const isError = responseStatus !== null && responseStatus >= 400;
1506
- if (isError) {
1521
+ const isHttpError = responseStatus !== null && responseStatus >= 400;
1522
+ if (isHttpError) {
1507
1523
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
1508
1524
  /* @__PURE__ */ jsxRuntimeExports.jsx(StatusIndicator, { status: responseStatus }),
1509
- /* @__PURE__ */ jsxRuntimeExports.jsx(ErrorResponseView, { text: responseText })
1525
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ErrorResponseView, { text: responseText ?? "" }),
1526
+ error !== void 0 && error !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded border border-destructive/50 bg-destructive/10 p-3 text-xs", children: [
1527
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-semibold text-destructive mb-1", children: "SSE Error" }),
1528
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-muted-foreground font-mono", children: error })
1529
+ ] })
1510
1530
  ] });
1511
1531
  }
1512
- const parsed = parseResponse(responseText, apiFormat);
1532
+ if (error !== void 0 && error !== null) {
1533
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
1534
+ /* @__PURE__ */ jsxRuntimeExports.jsx(StatusIndicator, { status: responseStatus }),
1535
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded border border-destructive/50 bg-destructive/10 p-3 text-xs", children: [
1536
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-semibold text-destructive mb-1", children: "SSE Error" }),
1537
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-muted-foreground font-mono", children: error })
1538
+ ] }),
1539
+ responseText !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ErrorResponseView, { text: responseText }) })
1540
+ ] });
1541
+ }
1542
+ const parsed = responseText !== null ? parseResponse(responseText, apiFormat) : null;
1513
1543
  if (parsed !== null) {
1514
1544
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
1515
1545
  /* @__PURE__ */ jsxRuntimeExports.jsx(StatusIndicator, { status: responseStatus }),
@@ -1538,7 +1568,7 @@ function ResponseView({
1538
1568
  ] })
1539
1569
  ] })
1540
1570
  ] }),
1541
- /* @__PURE__ */ jsxRuntimeExports.jsx(MarkdownFallbackView, { text: responseText })
1571
+ /* @__PURE__ */ jsxRuntimeExports.jsx(MarkdownFallbackView, { text: responseText ?? "" })
1542
1572
  ] });
1543
1573
  }
1544
1574
  const ReplayResultSchema = object({
@@ -1921,6 +1951,10 @@ const LogEntry = reactExports.memo(function LogEntry2({
1921
1951
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground truncate", title: value, children: value })
1922
1952
  ] }, key)) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No raw headers captured" }) }) }),
1923
1953
  /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3 space-y-3", children: [
1954
+ log.error !== void 0 && log.error !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded border border-destructive/50 bg-destructive/10 p-3 text-xs", children: [
1955
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-semibold text-destructive mb-1", children: "SSE Error" }),
1956
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-muted-foreground font-mono", children: log.error })
1957
+ ] }),
1924
1958
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1925
1959
  CopyButton,
1926
1960
  {
@@ -1949,7 +1983,8 @@ const LogEntry = reactExports.memo(function LogEntry2({
1949
1983
  outputTokens: log.outputTokens,
1950
1984
  cacheCreationInputTokens: log.cacheCreationInputTokens,
1951
1985
  cacheReadInputTokens: log.cacheReadInputTokens,
1952
- apiFormat: log.apiFormat
1986
+ apiFormat: log.apiFormat,
1987
+ error: log.error
1953
1988
  }
1954
1989
  ) }) })
1955
1990
  ] }) })
@@ -2120,6 +2155,9 @@ function maskApiKey(apiKey) {
2120
2155
  function hasSuccessField(result) {
2121
2156
  return Object.prototype.hasOwnProperty.call(result, "success");
2122
2157
  }
2158
+ function isNotConfiguredState(result) {
2159
+ return Object.prototype.hasOwnProperty.call(result, "notConfigured");
2160
+ }
2123
2161
  function getErrorIcon(type) {
2124
2162
  const iconProps = { className: "size-3", strokeWidth: 2 };
2125
2163
  switch (type) {
@@ -2143,18 +2181,21 @@ function getErrorIcon(type) {
2143
2181
  return /* @__PURE__ */ jsxRuntimeExports.jsx(CircleAlert, { ...iconProps });
2144
2182
  }
2145
2183
  }
2146
- function TestStatus({
2147
- result,
2148
- isTesting
2149
- }) {
2184
+ function TestStatus({ result }) {
2150
2185
  if (!hasSuccessField(result)) {
2151
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [
2152
- /* @__PURE__ */ jsxRuntimeExports.jsx(Minus, { className: "size-3" }),
2153
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Not configured" })
2186
+ if (isNotConfiguredState(result)) {
2187
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 text-xs text-muted-foreground shrink-0", children: [
2188
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Minus, { className: "size-3" }),
2189
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Not configured" })
2190
+ ] });
2191
+ }
2192
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 text-xs text-muted-foreground shrink-0", children: [
2193
+ /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCw, { className: "size-3 animate-spin" }),
2194
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Testing..." })
2154
2195
  ] });
2155
2196
  }
2156
2197
  if (result.success) {
2157
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 text-xs text-green-600", children: [
2198
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 text-xs text-green-600 shrink-0", children: [
2158
2199
  /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheckBig, { className: "size-3" }),
2159
2200
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Connected" })
2160
2201
  ] });
@@ -2163,20 +2204,18 @@ function TestStatus({
2163
2204
  const errorMessage = error?.message ?? "Connection failed";
2164
2205
  const errorHint = error?.hint;
2165
2206
  const errorType = error?.type ?? "unknown";
2166
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1 min-w-0", children: [
2167
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
2168
- "div",
2169
- {
2170
- className: "flex items-center gap-1 text-xs text-red-600 min-w-0",
2171
- title: error?.details ?? errorMessage,
2172
- children: [
2173
- getErrorIcon(errorType),
2174
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: errorMessage })
2175
- ]
2176
- }
2177
- ),
2178
- errorHint !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-muted-foreground pl-4 truncate", title: errorHint, children: errorHint })
2179
- ] });
2207
+ const fullMessage = errorHint !== void 0 ? `${errorMessage} ${errorHint}` : errorMessage;
2208
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
2209
+ "div",
2210
+ {
2211
+ className: "flex items-center gap-1 text-xs text-red-600 shrink-0 max-w-[200px]",
2212
+ title: error?.details ?? fullMessage,
2213
+ children: [
2214
+ getErrorIcon(errorType),
2215
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: errorMessage })
2216
+ ]
2217
+ }
2218
+ );
2180
2219
  }
2181
2220
  function ProviderCard({
2182
2221
  provider,
@@ -2188,25 +2227,25 @@ function ProviderCard({
2188
2227
  onTest
2189
2228
  }) {
2190
2229
  const [showApiKey, setShowApiKey] = reactExports.useState(false);
2230
+ const docsUrl = provider.apiDocsUrl ?? Object.entries(KNOWN_PROVIDER_DOCS).find(
2231
+ ([keyword]) => provider.name.toLowerCase().includes(keyword)
2232
+ )?.[1];
2191
2233
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border rounded-lg p-4 flex flex-col gap-3 bg-card", children: [
2192
2234
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start justify-between gap-2", children: [
2193
2235
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-2 min-w-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium truncate", children: provider.model !== void 0 && provider.model !== "" ? `${provider.model} (${provider.name})` : provider.name }) }),
2194
- Object.entries(KNOWN_PROVIDER_DOCS).map(
2195
- ([keyword, url]) => provider.name.toLowerCase().includes(keyword) ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
2196
- "a",
2197
- {
2198
- href: url,
2199
- target: "_blank",
2200
- rel: "noopener noreferrer",
2201
- className: "text-muted-foreground hover:text-foreground transition-colors flex items-center gap-1 text-xs",
2202
- title: "View API documentation",
2203
- children: [
2204
- /* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { className: "size-3" }),
2205
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sr-only", children: "Docs" })
2206
- ]
2207
- },
2208
- keyword
2209
- ) : null
2236
+ docsUrl !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(
2237
+ "a",
2238
+ {
2239
+ href: docsUrl,
2240
+ target: "_blank",
2241
+ rel: "noopener noreferrer",
2242
+ className: "text-muted-foreground hover:text-foreground transition-colors flex items-center gap-1 text-xs",
2243
+ title: "View API documentation",
2244
+ children: [
2245
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { className: "size-3" }),
2246
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sr-only", children: "Docs" })
2247
+ ]
2248
+ }
2210
2249
  )
2211
2250
  ] }),
2212
2251
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
@@ -2228,7 +2267,7 @@ function ProviderCard({
2228
2267
  " ",
2229
2268
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: provider.anthropicBaseUrl })
2230
2269
  ] }),
2231
- testResults && /* @__PURE__ */ jsxRuntimeExports.jsx(TestStatus, { result: testResults.anthropic.nonStreaming, isTesting })
2270
+ testResults && /* @__PURE__ */ jsxRuntimeExports.jsx(TestStatus, { result: testResults.anthropic.nonStreaming })
2232
2271
  ] }),
2233
2272
  provider.openaiBaseUrl !== void 0 && provider.openaiBaseUrl !== "" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
2234
2273
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs text-muted-foreground min-w-0 flex-1", children: [
@@ -2236,7 +2275,7 @@ function ProviderCard({
2236
2275
  " ",
2237
2276
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: provider.openaiBaseUrl })
2238
2277
  ] }),
2239
- testResults && /* @__PURE__ */ jsxRuntimeExports.jsx(TestStatus, { result: testResults.openai.nonStreaming, isTesting })
2278
+ testResults && /* @__PURE__ */ jsxRuntimeExports.jsx(TestStatus, { result: testResults.openai.nonStreaming })
2240
2279
  ] }),
2241
2280
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2 pt-1 border-t", children: [
2242
2281
  onTest !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(
@@ -2312,6 +2351,7 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
2312
2351
  const [model, setModel] = reactExports.useState(provider?.model ?? "");
2313
2352
  const [format, setFormat] = reactExports.useState(provider?.format ?? "anthropic");
2314
2353
  const [baseUrl, setBaseUrl] = reactExports.useState(provider?.baseUrl ?? "");
2354
+ const [apiDocsUrl, setApiDocsUrl] = reactExports.useState(provider?.apiDocsUrl ?? "");
2315
2355
  const [errors, setErrors] = reactExports.useState({});
2316
2356
  const [isSubmitting, setIsSubmitting] = reactExports.useState(false);
2317
2357
  const [manualBaseUrlOverride, setManualBaseUrlOverride] = reactExports.useState(false);
@@ -2324,6 +2364,7 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
2324
2364
  setModel(provider.model ?? "");
2325
2365
  setFormat(provider.format ?? "anthropic");
2326
2366
  setBaseUrl(provider.baseUrl ?? "");
2367
+ setApiDocsUrl(provider.apiDocsUrl ?? "");
2327
2368
  setManualBaseUrlOverride(false);
2328
2369
  }
2329
2370
  }, [provider]);
@@ -2382,7 +2423,8 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
2382
2423
  apiKey: apiKey.trim(),
2383
2424
  model: model.trim() || void 0,
2384
2425
  format,
2385
- baseUrl: baseUrl.trim() || void 0
2426
+ baseUrl: baseUrl.trim() || void 0,
2427
+ apiDocsUrl: apiDocsUrl.trim() || void 0
2386
2428
  });
2387
2429
  } finally {
2388
2430
  setIsSubmitting(false);
@@ -2494,6 +2536,21 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
2494
2536
  errors.baseUrl !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-destructive", children: errors.baseUrl }),
2495
2537
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: "Base URL for the provider API." })
2496
2538
  ] }),
2539
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
2540
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { htmlFor: "provider-api-docs-url", className: "text-sm font-medium", children: "API Docs URL" }),
2541
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
2542
+ "input",
2543
+ {
2544
+ id: "provider-api-docs-url",
2545
+ type: "text",
2546
+ value: apiDocsUrl,
2547
+ onChange: (e) => setApiDocsUrl(e.target.value),
2548
+ placeholder: "https://api.example.com/docs",
2549
+ className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:border-ring focus-visible:outline-ring focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50"
2550
+ }
2551
+ ),
2552
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: "Optional API documentation URL. If not set, uses known provider docs." })
2553
+ ] }),
2497
2554
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2 justify-end pt-2", children: [
2498
2555
  /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { type: "button", variant: "outline", onClick: onCancel, disabled: isSubmitting, children: "Cancel" }),
2499
2556
  /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { type: "submit", disabled: isSubmitting, children: isSubmitting ? "Saving..." : provider ? "Update Provider" : "Add Provider" })
@@ -2505,13 +2562,11 @@ function ProvidersPanel({
2505
2562
  externalTestResults,
2506
2563
  externalTestingProviders,
2507
2564
  externalTestingTimeLeft,
2508
- onProvidersChange,
2565
+ onProvidersMutate,
2509
2566
  onTestResultsChange,
2510
2567
  onTestingProvidersChange,
2511
2568
  onTestingTimeLeftChange
2512
2569
  }) {
2513
- const [internalProviders, setInternalProviders] = reactExports.useState([]);
2514
- const [isLoading, setIsLoading] = reactExports.useState(true);
2515
2570
  const [showForm, setShowForm] = reactExports.useState(false);
2516
2571
  const [editingProvider, setEditingProvider] = reactExports.useState();
2517
2572
  const [error, setError] = reactExports.useState(null);
@@ -2522,8 +2577,7 @@ function ProvidersPanel({
2522
2577
  );
2523
2578
  const [configPath, setConfigPath] = reactExports.useState(null);
2524
2579
  const [configPathCopied, setConfigPathCopied] = reactExports.useState(false);
2525
- const providers = externalProviders ?? internalProviders;
2526
- const setProviders = onProvidersChange ?? setInternalProviders;
2580
+ const providers = externalProviders ?? [];
2527
2581
  const testResults = externalTestResults ?? internalTestResults;
2528
2582
  const testingProviders = externalTestingProviders ?? internalTestingProviders;
2529
2583
  const testingTimeLeft = externalTestingTimeLeft ?? internalTestingTimeLeft;
@@ -2538,19 +2592,7 @@ function ProvidersPanel({
2538
2592
  setInternalTestingTimeLeft((prev) => ({ ...prev, [id]: seconds }));
2539
2593
  }
2540
2594
  };
2541
- const fetchProviders = reactExports.useCallback(async () => {
2542
- try {
2543
- const providersRes = await fetch("/api/providers");
2544
- const providersData = await providersRes.json();
2545
- setProviders(providersData);
2546
- } catch {
2547
- setError("Failed to load providers");
2548
- } finally {
2549
- setIsLoading(false);
2550
- }
2551
- }, []);
2552
2595
  reactExports.useEffect(() => {
2553
- void fetchProviders();
2554
2596
  void (async () => {
2555
2597
  try {
2556
2598
  const res = await fetch("/api/config/paths");
@@ -2561,13 +2603,13 @@ function ProvidersPanel({
2561
2603
  } catch {
2562
2604
  }
2563
2605
  })();
2564
- }, [fetchProviders]);
2606
+ }, []);
2565
2607
  const TEST_TIMEOUT_SECONDS = 30;
2566
2608
  const runTest = reactExports.useCallback(
2567
2609
  async (providerId) => {
2568
2610
  const resetResults = {
2569
- anthropic: { nonStreaming: { notConfigured: true }, streaming: { notConfigured: true } },
2570
- openai: { nonStreaming: { notConfigured: true }, streaming: { notConfigured: true } }
2611
+ anthropic: { nonStreaming: { testing: true }, streaming: { testing: true } },
2612
+ openai: { nonStreaming: { testing: true }, streaming: { testing: true } }
2571
2613
  };
2572
2614
  if (onTestResultsChange) {
2573
2615
  onTestResultsChange(providerId, resetResults);
@@ -2690,7 +2732,7 @@ function ProvidersPanel({
2690
2732
  return;
2691
2733
  }
2692
2734
  const newProvider = await res.json();
2693
- await fetchProviders();
2735
+ onProvidersMutate?.();
2694
2736
  setShowForm(false);
2695
2737
  await runTest(newProvider.id);
2696
2738
  })();
@@ -2717,7 +2759,7 @@ function ProvidersPanel({
2717
2759
  return;
2718
2760
  }
2719
2761
  const updated = await res.json();
2720
- await fetchProviders();
2762
+ onProvidersMutate?.();
2721
2763
  setEditingProvider(void 0);
2722
2764
  await runTest(updated.id);
2723
2765
  })();
@@ -2733,7 +2775,7 @@ function ProvidersPanel({
2733
2775
  setError(err.error ?? "Failed to delete provider");
2734
2776
  return;
2735
2777
  }
2736
- await fetchProviders();
2778
+ onProvidersMutate?.();
2737
2779
  })();
2738
2780
  }
2739
2781
  const fileInputRef = reactExports.useRef(null);
@@ -2782,7 +2824,7 @@ function ProvidersPanel({
2782
2824
  });
2783
2825
  const data = ImportResponseSchema.parse(await res.json());
2784
2826
  if (res.ok && data.imported !== void 0 && data.imported > 0) {
2785
- await fetchProviders();
2827
+ onProvidersMutate?.();
2786
2828
  setError(null);
2787
2829
  } else if (data.errors && data.errors.length > 0) {
2788
2830
  setError(data.errors.join("; "));
@@ -2795,7 +2837,7 @@ function ProvidersPanel({
2795
2837
  e.target.value = "";
2796
2838
  })();
2797
2839
  }
2798
- if (isLoading && providers.length === 0) {
2840
+ if (providers.length === 0) {
2799
2841
  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..." }) });
2800
2842
  }
2801
2843
  if (showForm || editingProvider) {
@@ -2815,17 +2857,35 @@ function ProvidersPanel({
2815
2857
  ] });
2816
2858
  }
2817
2859
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
2818
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
2860
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between sticky top-0 z-10 bg-background pb-2", children: [
2819
2861
  /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-lg font-medium", children: "Providers" }),
2820
2862
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
2821
- /* @__PURE__ */ jsxRuntimeExports.jsxs(Button, { variant: "outline", size: "sm", onClick: () => handleExport(), className: "gap-1", children: [
2822
- /* @__PURE__ */ jsxRuntimeExports.jsx(Download, { className: "size-3" }),
2823
- "Export"
2824
- ] }),
2825
- /* @__PURE__ */ jsxRuntimeExports.jsxs(Button, { variant: "outline", size: "sm", onClick: handleImportClick, className: "gap-1", children: [
2826
- /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { className: "size-3" }),
2827
- "Import"
2828
- ] }),
2863
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
2864
+ Button,
2865
+ {
2866
+ variant: "outline",
2867
+ size: "sm",
2868
+ onClick: () => handleExport(),
2869
+ className: "gap-1 hover:bg-muted",
2870
+ children: [
2871
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Download, { className: "size-3" }),
2872
+ "Export"
2873
+ ]
2874
+ }
2875
+ ),
2876
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
2877
+ Button,
2878
+ {
2879
+ variant: "outline",
2880
+ size: "sm",
2881
+ onClick: handleImportClick,
2882
+ className: "gap-1 hover:bg-muted",
2883
+ children: [
2884
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { className: "size-3" }),
2885
+ "Import"
2886
+ ]
2887
+ }
2888
+ ),
2829
2889
  /* @__PURE__ */ jsxRuntimeExports.jsx(
2830
2890
  "input",
2831
2891
  {
@@ -2888,10 +2948,34 @@ function ProvidersPanel({
2888
2948
  )) })
2889
2949
  ] });
2890
2950
  }
2951
+ async function fetcher(url) {
2952
+ const response = await fetch(url);
2953
+ const data = await response.json();
2954
+ if (!Array.isArray(data)) {
2955
+ return [];
2956
+ }
2957
+ return data;
2958
+ }
2959
+ function useProviders() {
2960
+ const response = useSWR(
2961
+ "/api/providers",
2962
+ fetcher,
2963
+ {
2964
+ revalidateOnFocus: false,
2965
+ revalidateIfStale: false
2966
+ }
2967
+ );
2968
+ return {
2969
+ providers: response.data ?? [],
2970
+ isLoading: response.isLoading,
2971
+ isError: response.error,
2972
+ mutate: response.mutate
2973
+ };
2974
+ }
2891
2975
  function SettingsDialog() {
2892
2976
  const [open, setOpen] = reactExports.useState(false);
2893
2977
  const [activeTab, setActiveTab] = reactExports.useState("providers");
2894
- const [providers, setProviders] = reactExports.useState([]);
2978
+ const { providers, mutate } = useProviders();
2895
2979
  const [testResults, setTestResults] = reactExports.useState({});
2896
2980
  const [testingProviders, setTestingProviders] = reactExports.useState(/* @__PURE__ */ new Set());
2897
2981
  const [testingTimeLeft, setTestingTimeLeft] = reactExports.useState({});
@@ -2941,7 +3025,9 @@ function SettingsDialog() {
2941
3025
  externalTestResults: testResults,
2942
3026
  externalTestingProviders: testingProviders,
2943
3027
  externalTestingTimeLeft: testingTimeLeft,
2944
- onProvidersChange: setProviders,
3028
+ onProvidersMutate: () => {
3029
+ void mutate();
3030
+ },
2945
3031
  onTestResultsChange: handleTestResultsChange,
2946
3032
  onTestingProvidersChange: handleTestingProvidersChange,
2947
3033
  onTestingTimeLeftChange: handleTestingTimeLeftChange
@@ -3051,6 +3137,10 @@ function ProxyViewer({
3051
3137
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "max-w-[1200px] mx-auto flex flex-col h-screen", style: { maxHeight: "100vh" }, children: [
3052
3138
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4 mb-4 px-6 pt-6", children: [
3053
3139
  /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { className: "text-lg font-bold flex-1", children: "LLM Proxy Inspector" }),
3140
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground text-xs font-mono", children: [
3141
+ "v",
3142
+ packageJson.version
3143
+ ] }),
3054
3144
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center border border-border rounded-md overflow-hidden", children: [
3055
3145
  /* @__PURE__ */ jsxRuntimeExports.jsx(
3056
3146
  "button",
@@ -3239,6 +3329,7 @@ function ProxyViewerContainer() {
3239
3329
  const [viewMode, setViewMode] = reactExports.useState("simple");
3240
3330
  const [error, setError] = reactExports.useState(null);
3241
3331
  const eventSourceRef = reactExports.useRef(null);
3332
+ const reconnectTimeoutRef = reactExports.useRef(null);
3242
3333
  const fetchSessionsAndModels = reactExports.useCallback(async () => {
3243
3334
  try {
3244
3335
  const [sessionsRes, modelsRes] = await Promise.all([
@@ -3313,7 +3404,10 @@ function ProxyViewerContainer() {
3313
3404
  es.onerror = () => {
3314
3405
  setError("SSE connection lost, reconnecting...");
3315
3406
  es.close();
3316
- setTimeout(connectSSE, 3e3);
3407
+ if (reconnectTimeoutRef.current !== null) {
3408
+ clearTimeout(reconnectTimeoutRef.current);
3409
+ }
3410
+ reconnectTimeoutRef.current = setTimeout(connectSSE, 3e3);
3317
3411
  };
3318
3412
  void fetchSessionsAndModels();
3319
3413
  }, [selectedSession, selectedModel, fetchSessionsAndModels]);
@@ -3324,6 +3418,10 @@ function ProxyViewerContainer() {
3324
3418
  eventSourceRef.current.close();
3325
3419
  eventSourceRef.current = null;
3326
3420
  }
3421
+ if (reconnectTimeoutRef.current !== null) {
3422
+ clearTimeout(reconnectTimeoutRef.current);
3423
+ reconnectTimeoutRef.current = null;
3424
+ }
3327
3425
  };
3328
3426
  }, [connectSSE]);
3329
3427
  const handleClearAll = reactExports.useCallback(() => {
@@ -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-BF6ge6dS.mjs");
200
+ const { tsrStartManifest } = await import("../_tanstack-start-manifest_v-C7hQOzvX.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-CsCLdrXq.mjs").then((n) => n.r);
769
+ const routerEntry = await import("./router-CwmgKXBJ.mjs").then((n) => n.r);
770
770
  const startEntry = await import("./start-HYkvq4Ni.mjs");
771
771
  return { startEntry, routerEntry };
772
772
  }