@tonyclaw/llm-inspector 1.7.6 → 1.7.8

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.
@@ -14,4 +14,4 @@ Error generating stack: `+l.message+`
14
14
  `)}else{const p=o.indexOf(`
15
15
  `);if(p>=0){const S=o.slice(0,p).trim();o=o.slice(p+1),S.length>0&&(m=JSON.parse(S),f=!0)}}}return(async()=>{try{for(;;){const{value:y,done:h}=await r.read();y&&(o+=y);const p=o.lastIndexOf(`
16
16
  `);if(p>=0){const S=o.slice(0,p);o=o.slice(p+1);const b=S.split(`
17
- `).filter(Boolean);for(const _ of b)try{u(JSON.parse(_))}catch(R){i?.(`Invalid JSON line: ${_}`,R)}}if(h)break}}catch(y){i?.("Stream processing error:",y)}})(),u(m)}async function _E({jsonStream:a,onMessage:u,onError:i}){const r=a.getReader(),{value:o,done:f}=await r.read();if(f||!o)throw new Error("Stream ended before first object");const m=JSON.parse(o);return(async()=>{try{for(;;){const{value:y,done:h}=await r.read();if(h)break;if(y)try{u(JSON.parse(y))}catch(p){i?.(`Invalid JSON: ${y}`,p)}}}catch(y){i?.("Stream processing error:",y)}})(),u(m)}function EE(a){const u="/_serverFn/"+a;return Object.assign((...o)=>{const f=Lp()?.serverFns?.fetch;return gE(u,o,f??fetch)},{url:u,serverFnMeta:{id:a},[Ao]:!0})}const RE={key:"$TSS/serverfn",test:a=>typeof a!="function"||!(Ao in a)?!1:!!a[Ao],toSerializable:({serverFnMeta:a})=>({functionId:a.id}),fromSerializable:({functionId:a})=>EE(a)},TE="/assets/index-B3RwBPLW.css",Hp=G_({head:()=>({meta:[{charSet:"utf-8"},{name:"viewport",content:"width=device-width, initial-scale=1"},{title:"llm-inspector"}],links:[{rel:"stylesheet",href:TE}]}),component:AE});function AE(){return k.jsx(OE,{children:k.jsx(Cp,{})})}function OE({children:a}){return k.jsxs("html",{lang:"en",className:"dark",children:[k.jsx("head",{children:k.jsx(aE,{})}),k.jsxs("body",{children:[a,k.jsx(lE,{})]})]})}const ME="modulepreload",zE=function(a){return"/"+a},zy={},xE=function(u,i,r){let o=Promise.resolve();if(i&&i.length>0){let h=function(p){return Promise.all(p.map(S=>Promise.resolve(S).then(b=>({status:"fulfilled",value:b}),b=>({status:"rejected",reason:b}))))};document.getElementsByTagName("link");const m=document.querySelector("meta[property=csp-nonce]"),y=m?.nonce||m?.getAttribute("nonce");o=h(i.map(p=>{if(p=zE(p),p in zy)return;zy[p]=!0;const S=p.endsWith(".css"),b=S?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${p}"]${b}`))return;const _=document.createElement("link");if(_.rel=S?"stylesheet":ME,S||(_.as="script"),_.crossOrigin="",_.href=p,y&&_.setAttribute("nonce",y),document.head.appendChild(_),S)return new Promise((R,D)=>{_.addEventListener("load",R),_.addEventListener("error",()=>D(new Error(`Unable to preload CSS for ${p}`)))})}))}function f(m){const y=new Event("vite:preloadError",{cancelable:!0});if(y.payload=m,window.dispatchEvent(y),!y.defaultPrevented)throw m}return o.then(m=>{for(const y of m||[])y.status==="rejected"&&f(y.reason);return u().catch(f)})},wE=()=>xE(()=>import("./index-BKkFFKAM.js"),[]),CE=To("/")({component:V_(wE,"component")}),DE=CE.update({id:"/",path:"/",getParentRoute:()=>Hp}),UE={IndexRoute:DE},LE=Hp._addFileChildren(UE);function NE(){return P_({routeTree:LE,scrollRestoration:!1})}async function BE(){const a=await NE();let u;return u=[],window.__TSS_START_OPTIONS__={serializationAdapters:u},u.push(RE),a.options.serializationAdapters&&u.push(...a.options.serializationAdapters),a.update({basepath:"",serializationAdapters:u}),a.state.matches.length||await uE(a),a}async function jE(){const a=await BE();return window.$_TSR?.h(),a}let So;function HE(){return So||(So=jE()),k.jsx(p_,{promise:So,children:a=>k.jsx(I_,{router:a})})}nt.startTransition(()=>{h0.hydrateRoot(document,k.jsx(nt.StrictMode,{children:k.jsx(HE,{})}))});export{Ul as R,Mp as a,s0 as b,qE as c,XE as d,xy as g,k as j,nt as r};
17
+ `).filter(Boolean);for(const _ of b)try{u(JSON.parse(_))}catch(R){i?.(`Invalid JSON line: ${_}`,R)}}if(h)break}}catch(y){i?.("Stream processing error:",y)}})(),u(m)}async function _E({jsonStream:a,onMessage:u,onError:i}){const r=a.getReader(),{value:o,done:f}=await r.read();if(f||!o)throw new Error("Stream ended before first object");const m=JSON.parse(o);return(async()=>{try{for(;;){const{value:y,done:h}=await r.read();if(h)break;if(y)try{u(JSON.parse(y))}catch(p){i?.(`Invalid JSON: ${y}`,p)}}}catch(y){i?.("Stream processing error:",y)}})(),u(m)}function EE(a){const u="/_serverFn/"+a;return Object.assign((...o)=>{const f=Lp()?.serverFns?.fetch;return gE(u,o,f??fetch)},{url:u,serverFnMeta:{id:a},[Ao]:!0})}const RE={key:"$TSS/serverfn",test:a=>typeof a!="function"||!(Ao in a)?!1:!!a[Ao],toSerializable:({serverFnMeta:a})=>({functionId:a.id}),fromSerializable:({functionId:a})=>EE(a)},TE="/assets/index-B3RwBPLW.css",Hp=G_({head:()=>({meta:[{charSet:"utf-8"},{name:"viewport",content:"width=device-width, initial-scale=1"},{title:"llm-inspector"}],links:[{rel:"stylesheet",href:TE}]}),component:AE});function AE(){return k.jsx(OE,{children:k.jsx(Cp,{})})}function OE({children:a}){return k.jsxs("html",{lang:"en",className:"dark",children:[k.jsx("head",{children:k.jsx(aE,{})}),k.jsxs("body",{children:[a,k.jsx(lE,{})]})]})}const ME="modulepreload",zE=function(a){return"/"+a},zy={},xE=function(u,i,r){let o=Promise.resolve();if(i&&i.length>0){let h=function(p){return Promise.all(p.map(S=>Promise.resolve(S).then(b=>({status:"fulfilled",value:b}),b=>({status:"rejected",reason:b}))))};document.getElementsByTagName("link");const m=document.querySelector("meta[property=csp-nonce]"),y=m?.nonce||m?.getAttribute("nonce");o=h(i.map(p=>{if(p=zE(p),p in zy)return;zy[p]=!0;const S=p.endsWith(".css"),b=S?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${p}"]${b}`))return;const _=document.createElement("link");if(_.rel=S?"stylesheet":ME,S||(_.as="script"),_.crossOrigin="",_.href=p,y&&_.setAttribute("nonce",y),document.head.appendChild(_),S)return new Promise((R,D)=>{_.addEventListener("load",R),_.addEventListener("error",()=>D(new Error(`Unable to preload CSS for ${p}`)))})}))}function f(m){const y=new Event("vite:preloadError",{cancelable:!0});if(y.payload=m,window.dispatchEvent(y),!y.defaultPrevented)throw m}return o.then(m=>{for(const y of m||[])y.status==="rejected"&&f(y.reason);return u().catch(f)})},wE=()=>xE(()=>import("./index-C8o6bEv6.js"),[]),CE=To("/")({component:V_(wE,"component")}),DE=CE.update({id:"/",path:"/",getParentRoute:()=>Hp}),UE={IndexRoute:DE},LE=Hp._addFileChildren(UE);function NE(){return P_({routeTree:LE,scrollRestoration:!1})}async function BE(){const a=await NE();let u;return u=[],window.__TSS_START_OPTIONS__={serializationAdapters:u},u.push(RE),a.options.serializationAdapters&&u.push(...a.options.serializationAdapters),a.update({basepath:"",serializationAdapters:u}),a.state.matches.length||await uE(a),a}async function jE(){const a=await BE();return window.$_TSR?.h(),a}let So;function HE(){return So||(So=jE()),k.jsx(p_,{promise:So,children:a=>k.jsx(I_,{router:a})})}nt.startTransition(()=>{h0.hydrateRoot(document,k.jsx(nt.StrictMode,{children:k.jsx(HE,{})}))});export{Ul as R,Mp as a,s0 as b,qE as c,XE as d,xy as g,k as j,nt as r};
@@ -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-tRLZo2WT.mjs";
2
+ import { C as CapturedLogSchema, a as parseRequest, p as parseOpenAIResponse, I as InspectorResponseSchema } from "./router-MmnX-LYh.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";
@@ -2179,6 +2179,7 @@ function ProviderCard({
2179
2179
  provider,
2180
2180
  testResults,
2181
2181
  isTesting,
2182
+ testingTimeLeft,
2182
2183
  onEdit,
2183
2184
  onDelete,
2184
2185
  onTest
@@ -2245,7 +2246,7 @@ function ProviderCard({
2245
2246
  disabled: isTesting ?? false,
2246
2247
  children: [
2247
2248
  /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCw, { className: `size-3 ${isTesting ?? false ? "animate-spin" : ""}` }),
2248
- isTesting ?? false ? "Testing..." : "Test"
2249
+ isTesting ?? false ? testingTimeLeft !== void 0 ? `Testing (${testingTimeLeft}s)` : "Testing..." : "Test"
2249
2250
  ]
2250
2251
  }
2251
2252
  ),
@@ -2496,16 +2497,44 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
2496
2497
  ] })
2497
2498
  ] });
2498
2499
  }
2499
- function ProvidersPanel() {
2500
- const [providers, setProviders] = reactExports.useState([]);
2500
+ function ProvidersPanel({
2501
+ externalProviders,
2502
+ externalTestResults,
2503
+ externalTestingProviders,
2504
+ externalTestingTimeLeft,
2505
+ onProvidersChange,
2506
+ onTestResultsChange,
2507
+ onTestingProvidersChange,
2508
+ onTestingTimeLeftChange
2509
+ }) {
2510
+ const [internalProviders, setInternalProviders] = reactExports.useState([]);
2501
2511
  const [isLoading, setIsLoading] = reactExports.useState(true);
2502
2512
  const [showForm, setShowForm] = reactExports.useState(false);
2503
2513
  const [editingProvider, setEditingProvider] = reactExports.useState();
2504
2514
  const [error, setError] = reactExports.useState(null);
2505
- const [testResults, setTestResults] = reactExports.useState({});
2506
- const [testingProviders, setTestingProviders] = reactExports.useState(/* @__PURE__ */ new Set());
2515
+ const [internalTestResults, setInternalTestResults] = reactExports.useState({});
2516
+ const [internalTestingProviders, setInternalTestingProviders] = reactExports.useState(/* @__PURE__ */ new Set());
2517
+ const [internalTestingTimeLeft, setInternalTestingTimeLeft] = reactExports.useState(
2518
+ {}
2519
+ );
2507
2520
  const [configPath, setConfigPath] = reactExports.useState(null);
2508
2521
  const [configPathCopied, setConfigPathCopied] = reactExports.useState(false);
2522
+ const providers = externalProviders ?? internalProviders;
2523
+ const setProviders = onProvidersChange ?? setInternalProviders;
2524
+ const testResults = externalTestResults ?? internalTestResults;
2525
+ const testingProviders = externalTestingProviders ?? internalTestingProviders;
2526
+ const testingTimeLeft = externalTestingTimeLeft ?? internalTestingTimeLeft;
2527
+ const setTestingTimeLeft = onTestingTimeLeftChange ? (id, seconds) => onTestingTimeLeftChange(id, seconds) : (id, seconds) => {
2528
+ if (seconds === void 0) {
2529
+ setInternalTestingTimeLeft((prev) => {
2530
+ const next = { ...prev };
2531
+ delete next[id];
2532
+ return next;
2533
+ });
2534
+ } else {
2535
+ setInternalTestingTimeLeft((prev) => ({ ...prev, [id]: seconds }));
2536
+ }
2537
+ };
2509
2538
  const fetchProviders = reactExports.useCallback(async () => {
2510
2539
  try {
2511
2540
  const providersRes = await fetch("/api/providers");
@@ -2530,22 +2559,100 @@ function ProvidersPanel() {
2530
2559
  }
2531
2560
  })();
2532
2561
  }, [fetchProviders]);
2533
- const runTest = reactExports.useCallback(async (providerId) => {
2534
- setTestingProviders((prev) => new Set(prev).add(providerId));
2535
- try {
2536
- const res = await fetch(`/api/providers/${providerId}/test`, { method: "POST" });
2537
- if (res.ok) {
2538
- const results = await res.json();
2539
- setTestResults((prev) => ({ ...prev, [providerId]: results }));
2562
+ const TEST_TIMEOUT_SECONDS = 30;
2563
+ const runTest = reactExports.useCallback(
2564
+ async (providerId) => {
2565
+ if (onTestingProvidersChange) {
2566
+ onTestingProvidersChange(providerId, true);
2567
+ } else {
2568
+ setInternalTestingProviders((prev) => new Set(prev).add(providerId));
2540
2569
  }
2541
- } finally {
2542
- setTestingProviders((prev) => {
2543
- const next = new Set(prev);
2544
- next.delete(providerId);
2545
- return next;
2546
- });
2547
- }
2548
- }, []);
2570
+ const controller = new AbortController();
2571
+ let remaining = TEST_TIMEOUT_SECONDS;
2572
+ setTestingTimeLeft(providerId, remaining);
2573
+ const intervalId = setInterval(() => {
2574
+ remaining--;
2575
+ setTestingTimeLeft(providerId, remaining);
2576
+ if (remaining <= 0) {
2577
+ clearInterval(intervalId);
2578
+ controller.abort();
2579
+ }
2580
+ }, 1e3);
2581
+ try {
2582
+ const res = await fetch(`/api/providers/${providerId}/test`, {
2583
+ method: "POST",
2584
+ signal: controller.signal
2585
+ });
2586
+ if (res.ok) {
2587
+ const results = await res.json();
2588
+ if (onTestResultsChange) {
2589
+ onTestResultsChange(providerId, results);
2590
+ } else {
2591
+ setInternalTestResults((prev) => ({ ...prev, [providerId]: results }));
2592
+ }
2593
+ } else {
2594
+ const errorResult = {
2595
+ anthropic: {
2596
+ nonStreaming: {
2597
+ success: false,
2598
+ error: { message: `HTTP ${res.status}: ${res.statusText}`, type: "server_error" }
2599
+ },
2600
+ streaming: { notConfigured: true }
2601
+ },
2602
+ openai: {
2603
+ nonStreaming: {
2604
+ success: false,
2605
+ error: { message: `HTTP ${res.status}: ${res.statusText}`, type: "server_error" }
2606
+ },
2607
+ streaming: { notConfigured: true }
2608
+ }
2609
+ };
2610
+ if (onTestResultsChange) {
2611
+ onTestResultsChange(providerId, errorResult);
2612
+ } else {
2613
+ setInternalTestResults((prev) => ({ ...prev, [providerId]: errorResult }));
2614
+ }
2615
+ }
2616
+ } catch (err) {
2617
+ if (err instanceof Error && err.name === "AbortError") {
2618
+ const timeoutResult = {
2619
+ anthropic: {
2620
+ nonStreaming: {
2621
+ success: false,
2622
+ error: { message: "Request timed out", type: "timeout" }
2623
+ },
2624
+ streaming: { notConfigured: true }
2625
+ },
2626
+ openai: {
2627
+ nonStreaming: {
2628
+ success: false,
2629
+ error: { message: "Request timed out", type: "timeout" }
2630
+ },
2631
+ streaming: { notConfigured: true }
2632
+ }
2633
+ };
2634
+ if (onTestResultsChange) {
2635
+ onTestResultsChange(providerId, timeoutResult);
2636
+ } else {
2637
+ setInternalTestResults((prev) => ({ ...prev, [providerId]: timeoutResult }));
2638
+ }
2639
+ }
2640
+ } finally {
2641
+ clearInterval(intervalId);
2642
+ 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
+ });
2651
+ }
2652
+ }
2653
+ },
2654
+ [onTestingProvidersChange, onTestResultsChange, setTestingTimeLeft]
2655
+ );
2549
2656
  function handleAddProvider(data) {
2550
2657
  void (async () => {
2551
2658
  const payload = {
@@ -2675,6 +2782,7 @@ function ProvidersPanel() {
2675
2782
  provider,
2676
2783
  testResults: testResults[provider.id],
2677
2784
  isTesting: testingProviders.has(provider.id),
2785
+ testingTimeLeft: testingTimeLeft[provider.id],
2678
2786
  onEdit: (p) => setEditingProvider(p),
2679
2787
  onDelete: handleDeleteProvider,
2680
2788
  onTest: (id) => {
@@ -2688,6 +2796,40 @@ function ProvidersPanel() {
2688
2796
  function SettingsDialog() {
2689
2797
  const [open, setOpen] = reactExports.useState(false);
2690
2798
  const [activeTab, setActiveTab] = reactExports.useState("providers");
2799
+ const [providers, setProviders] = reactExports.useState([]);
2800
+ const [testResults, setTestResults] = reactExports.useState({});
2801
+ const [testingProviders, setTestingProviders] = reactExports.useState(/* @__PURE__ */ new Set());
2802
+ const [testingTimeLeft, setTestingTimeLeft] = reactExports.useState({});
2803
+ const handleTestResultsChange = reactExports.useCallback(
2804
+ (providerId, results) => {
2805
+ setTestResults((prev) => ({ ...prev, [providerId]: results }));
2806
+ },
2807
+ []
2808
+ );
2809
+ const handleTestingProvidersChange = reactExports.useCallback((providerId, isTesting) => {
2810
+ setTestingProviders((prev) => {
2811
+ const next = new Set(prev);
2812
+ if (isTesting) {
2813
+ next.add(providerId);
2814
+ } else {
2815
+ next.delete(providerId);
2816
+ }
2817
+ return next;
2818
+ });
2819
+ }, []);
2820
+ const handleTestingTimeLeftChange = reactExports.useCallback(
2821
+ (providerId, seconds) => {
2822
+ setTestingTimeLeft((prev) => {
2823
+ if (seconds === void 0) {
2824
+ const next = { ...prev };
2825
+ delete next[providerId];
2826
+ return next;
2827
+ }
2828
+ return { ...prev, [providerId]: seconds };
2829
+ });
2830
+ },
2831
+ []
2832
+ );
2691
2833
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(Dialog, { open, onOpenChange: setOpen, children: [
2692
2834
  /* @__PURE__ */ jsxRuntimeExports.jsx(DialogTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Button, { variant: "ghost", size: "icon", className: "size-8", children: [
2693
2835
  /* @__PURE__ */ jsxRuntimeExports.jsx(Settings, { className: "size-4" }),
@@ -2697,7 +2839,19 @@ function SettingsDialog() {
2697
2839
  /* @__PURE__ */ jsxRuntimeExports.jsx(DialogHeader, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(DialogTitle, { children: "Settings" }) }),
2698
2840
  /* @__PURE__ */ jsxRuntimeExports.jsxs(Tabs, { value: activeTab, onValueChange: setActiveTab, className: "flex-1 overflow-hidden", children: [
2699
2841
  /* @__PURE__ */ jsxRuntimeExports.jsx(TabsList, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "providers", children: "Providers" }) }),
2700
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-4 overflow-y-auto flex-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "providers", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ProvidersPanel, {}) }) })
2842
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-4 overflow-y-auto flex-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "providers", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2843
+ ProvidersPanel,
2844
+ {
2845
+ externalProviders: providers,
2846
+ externalTestResults: testResults,
2847
+ externalTestingProviders: testingProviders,
2848
+ externalTestingTimeLeft: testingTimeLeft,
2849
+ onProvidersChange: setProviders,
2850
+ onTestResultsChange: handleTestResultsChange,
2851
+ onTestingProvidersChange: handleTestingProvidersChange,
2852
+ onTestingTimeLeftChange: handleTestingTimeLeftChange
2853
+ }
2854
+ ) }) })
2701
2855
  ] })
2702
2856
  ] })
2703
2857
  ] });
@@ -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-_KPj4BcN.mjs");
200
+ const { tsrStartManifest } = await import("../_tanstack-start-manifest_v-CYKtU_9S.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-tRLZo2WT.mjs").then((n) => n.r);
769
+ const routerEntry = await import("./router-MmnX-LYh.mjs").then((n) => n.r);
770
770
  const startEntry = await import("./start-HYkvq4Ni.mjs");
771
771
  return { startEntry, routerEntry };
772
772
  }
@@ -65,7 +65,7 @@ function RootDocument({ children }) {
65
65
  ] })
66
66
  ] });
67
67
  }
68
- const $$splitComponentImporter = () => import("./index-DAvMem8_.mjs");
68
+ const $$splitComponentImporter = () => import("./index-hNquJMfH.mjs");
69
69
  const Route$d = createFileRoute("/")({
70
70
  component: lazyRouteComponent($$splitComponentImporter, "component")
71
71
  });
@@ -1,4 +1,4 @@
1
- const tsrStartManifest = () => ({ "routes": { "__root__": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/__root.tsx", "children": ["/", "/api/health", "/api/logs", "/api/models", "/api/providers", "/api/sessions", "/proxy/$", "/api/config/paths"], "preloads": ["/assets/main-B3Cmykkm.js"], "assets": [] }, "/": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/index.tsx", "assets": [], "preloads": ["/assets/index-BKkFFKAM.js"] }, "/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/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/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/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" } }, "clientEntry": "/assets/main-B3Cmykkm.js" });
1
+ const tsrStartManifest = () => ({ "routes": { "__root__": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/__root.tsx", "children": ["/", "/api/health", "/api/logs", "/api/models", "/api/providers", "/api/sessions", "/proxy/$", "/api/config/paths"], "preloads": ["/assets/main-Bxc5pKCu.js"], "assets": [] }, "/": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/index.tsx", "assets": [], "preloads": ["/assets/index-C8o6bEv6.js"] }, "/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/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/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/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" } }, "clientEntry": "/assets/main-Bxc5pKCu.js" });
2
2
  export {
3
3
  tsrStartManifest
4
4
  };
@@ -97,54 +97,54 @@ const headers = ((m) => function headersRouteRule(event) {
97
97
  }
98
98
  });
99
99
  const assets = {
100
- "/assets/alibaba-TTwafVwX.svg": {
101
- "type": "image/svg+xml",
102
- "etag": '"171b-6dyV5K8QjiaY35sN9qNprh9zDIs"',
103
- "mtime": "2026-06-03T11:15:10.948Z",
104
- "size": 5915,
105
- "path": "../public/assets/alibaba-TTwafVwX.svg"
106
- },
107
100
  "/assets/index-B3RwBPLW.css": {
108
101
  "type": "text/css; charset=utf-8",
109
102
  "etag": '"10c74-aXacU4DRFVsUwcC5jHnjoPRSlTA"',
110
- "mtime": "2026-06-03T11:15:10.948Z",
103
+ "mtime": "2026-06-03T12:12:14.334Z",
111
104
  "size": 68724,
112
105
  "path": "../public/assets/index-B3RwBPLW.css"
113
106
  },
107
+ "/assets/alibaba-TTwafVwX.svg": {
108
+ "type": "image/svg+xml",
109
+ "etag": '"171b-6dyV5K8QjiaY35sN9qNprh9zDIs"',
110
+ "mtime": "2026-06-03T12:12:14.334Z",
111
+ "size": 5915,
112
+ "path": "../public/assets/alibaba-TTwafVwX.svg"
113
+ },
114
114
  "/assets/minimax-BPMzvuL-.jpeg": {
115
115
  "type": "image/jpeg",
116
116
  "etag": '"1b06-IwivU89ko5UTMUM1/t7hn4sQK9A"',
117
- "mtime": "2026-06-03T11:15:10.948Z",
117
+ "mtime": "2026-06-03T12:12:14.334Z",
118
118
  "size": 6918,
119
119
  "path": "../public/assets/minimax-BPMzvuL-.jpeg"
120
120
  },
121
- "/assets/main-B3Cmykkm.js": {
121
+ "/assets/main-Bxc5pKCu.js": {
122
122
  "type": "text/javascript; charset=utf-8",
123
- "etag": '"4db57-BoW6SyNc1bhvI2SIfQuoPlQ7pjU"',
124
- "mtime": "2026-06-03T11:15:10.950Z",
123
+ "etag": '"4db57-Hyx+4GtcEuVuTT+HldUhPQTOcL8"',
124
+ "mtime": "2026-06-03T12:12:14.334Z",
125
125
  "size": 318295,
126
- "path": "../public/assets/main-B3Cmykkm.js"
127
- },
128
- "/assets/zhipuai-BPNAnxo-.svg": {
129
- "type": "image/svg+xml",
130
- "etag": '"2bf8-hNaLCTi89nOFCsIIfWpP/jrfo0s"',
131
- "mtime": "2026-06-03T11:15:10.948Z",
132
- "size": 11256,
133
- "path": "../public/assets/zhipuai-BPNAnxo-.svg"
126
+ "path": "../public/assets/main-Bxc5pKCu.js"
134
127
  },
135
128
  "/assets/qwen-CONDcHqt.png": {
136
129
  "type": "image/png",
137
130
  "etag": '"572c3-cdJAPaHdOvFCGzuaQjagdgOu6XE"',
138
- "mtime": "2026-06-03T11:15:10.950Z",
131
+ "mtime": "2026-06-03T12:12:14.334Z",
139
132
  "size": 357059,
140
133
  "path": "../public/assets/qwen-CONDcHqt.png"
141
134
  },
142
- "/assets/index-BKkFFKAM.js": {
135
+ "/assets/zhipuai-BPNAnxo-.svg": {
136
+ "type": "image/svg+xml",
137
+ "etag": '"2bf8-hNaLCTi89nOFCsIIfWpP/jrfo0s"',
138
+ "mtime": "2026-06-03T12:12:14.334Z",
139
+ "size": 11256,
140
+ "path": "../public/assets/zhipuai-BPNAnxo-.svg"
141
+ },
142
+ "/assets/index-C8o6bEv6.js": {
143
143
  "type": "text/javascript; charset=utf-8",
144
- "etag": '"83213-FJqU+YwY4BQ0ObBCm+z27w3dIRc"',
145
- "mtime": "2026-06-03T11:15:10.950Z",
146
- "size": 537107,
147
- "path": "../public/assets/index-BKkFFKAM.js"
144
+ "etag": '"83962-iIMvsvRc3BmqDsudqDxFOFuC3xg"',
145
+ "mtime": "2026-06-03T12:12:14.334Z",
146
+ "size": 538978,
147
+ "path": "../public/assets/index-C8o6bEv6.js"
148
148
  }
149
149
  };
150
150
  function readAsset(id) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tonyclaw/llm-inspector",
3
- "version": "1.7.6",
3
+ "version": "1.7.8",
4
4
  "type": "module",
5
5
  "description": "LLM API proxy inspector — captures and displays requests/responses from AI coding tools in a web UI",
6
6
  "license": "MIT",
@@ -63,6 +63,7 @@ type ProviderCardProps = {
63
63
  provider: ProviderConfig;
64
64
  testResults?: TestResults;
65
65
  isTesting?: boolean;
66
+ testingTimeLeft?: number;
66
67
  onEdit: (provider: ProviderConfig) => void;
67
68
  onDelete: (providerId: string) => void;
68
69
  onTest?: (providerId: string) => void;
@@ -152,6 +153,7 @@ export function ProviderCard({
152
153
  provider,
153
154
  testResults,
154
155
  isTesting,
156
+ testingTimeLeft,
155
157
  onEdit,
156
158
  onDelete,
157
159
  onTest,
@@ -233,7 +235,11 @@ export function ProviderCard({
233
235
  disabled={isTesting ?? false}
234
236
  >
235
237
  <RotateCw className={`size-3 ${(isTesting ?? false) ? "animate-spin" : ""}`} />
236
- {(isTesting ?? false) ? "Testing..." : "Test"}
238
+ {(isTesting ?? false)
239
+ ? testingTimeLeft !== undefined
240
+ ? `Testing (${testingTimeLeft}s)`
241
+ : "Testing..."
242
+ : "Test"}
237
243
  </Button>
238
244
  )}
239
245
  <Button