@tonyclaw/llm-inspector 1.14.6 → 1.14.7

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.
@@ -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, s as stripClaudeCodeBillingHeader, R as RuntimeConfigSchema, c as createPendingProviderTestResults, P as ProviderTestResultsSchema, b as createFailedProviderTestResults, d as ProviderConfigSchema, p as parseOpenAIResponse, I as InspectorResponseSchema } from "./router-DCfjmmJu.mjs";
2
+ import { C as CapturedLogSchema, a as parseRequest, s as stripClaudeCodeBillingHeader, R as RuntimeConfigSchema, c as createPendingProviderTestResults, P as ProviderTestResultsSchema, b as createFailedProviderTestResults, d as ProviderConfigSchema, p as parseOpenAIResponse, I as InspectorResponseSchema } from "./router-lUOA8pi6.mjs";
3
3
  import { u as useSWR, a as useSWRConfig } from "../_libs/swr.mjs";
4
4
  import { u as useVirtualizer } from "../_libs/tanstack__react-virtual.mjs";
5
5
  import { J as JSZip } from "../_libs/jszip.mjs";
@@ -14,8 +14,8 @@ import { D as Download, L as LayoutGrid, a as List, G as GitCompareArrows, X, S
14
14
  import { M as Markdown } from "../_libs/react-markdown.mjs";
15
15
  import { a as array, b as string, u as union, d as object, l as literal, n as number, c as boolean, _ as _enum } from "../_libs/zod.mjs";
16
16
  import { R as Root2$1, L as List$1, T as Trigger$2, C as Content$1 } from "../_libs/radix-ui__react-tabs.mjs";
17
- import { S as Slot } from "../_libs/radix-ui__react-slot.mjs";
18
17
  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";
18
+ import { S as Slot } from "../_libs/radix-ui__react-slot.mjs";
19
19
  import { R as Root$1 } from "../_libs/radix-ui__react-separator.mjs";
20
20
  import { R as Root$2, C as CollapsibleTrigger$1, a as CollapsibleContent$1 } from "../_libs/radix-ui__react-collapsible.mjs";
21
21
  import { R as Root$3, V as Viewport$1, C as Corner, S as ScrollAreaScrollbar, a as ScrollAreaThumb } from "../_libs/radix-ui__react-scroll-area.mjs";
@@ -276,7 +276,7 @@ async function exportLogsAsZip(logs) {
276
276
  document.body.removeChild(anchor);
277
277
  URL.revokeObjectURL(url);
278
278
  }
279
- const version = "1.14.6";
279
+ const version = "1.14.7";
280
280
  const packageJson = {
281
281
  version
282
282
  };
@@ -3015,6 +3015,10 @@ function ImportWizardDialog({
3015
3015
  return;
3016
3016
  }
3017
3017
  const result = parsed.data;
3018
+ setProviders(
3019
+ (prev) => prev.map((p, i) => selected.has(i) ? { ...p, alreadyExists: true } : p)
3020
+ );
3021
+ setSelected(/* @__PURE__ */ new Set());
3018
3022
  if (result.errors !== void 0 && result.errors.length > 0 && result.message !== void 0) {
3019
3023
  setImportResult(result.message);
3020
3024
  } else {
@@ -3109,6 +3113,35 @@ function ImportWizardDialog({
3109
3113
  ] })
3110
3114
  ] }) });
3111
3115
  }
3116
+ function ConfirmDialog({
3117
+ open,
3118
+ onOpenChange,
3119
+ title,
3120
+ description,
3121
+ confirmLabel = "Confirm",
3122
+ variant = "default",
3123
+ onConfirm
3124
+ }) {
3125
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(DialogContent, { children: [
3126
+ /* @__PURE__ */ jsxRuntimeExports.jsx(DialogHeader, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(DialogTitle, { children: title }) }),
3127
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-muted-foreground", children: description }),
3128
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-2 pt-2", children: [
3129
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { variant: "outline", size: "sm", onClick: () => onOpenChange(false), children: "Cancel" }),
3130
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
3131
+ Button,
3132
+ {
3133
+ variant,
3134
+ size: "sm",
3135
+ onClick: () => {
3136
+ onConfirm();
3137
+ onOpenChange(false);
3138
+ },
3139
+ children: confirmLabel
3140
+ }
3141
+ )
3142
+ ] })
3143
+ ] }) });
3144
+ }
3112
3145
  function maskApiKey(apiKey) {
3113
3146
  if (apiKey.length <= 8) return "••••••••";
3114
3147
  return apiKey.slice(0, 4) + "••••••••" + apiKey.slice(-4);
@@ -3900,6 +3933,8 @@ function ProvidersPanel({
3900
3933
  const [configPathCopied, setConfigPathCopied] = reactExports.useState(false);
3901
3934
  const [highlightedProviderId, setHighlightedProviderId] = reactExports.useState(null);
3902
3935
  const [showImportWizard, setShowImportWizard] = reactExports.useState(false);
3936
+ const [confirmOpen, setConfirmOpen] = reactExports.useState(false);
3937
+ const [deletingProviderId, setDeletingProviderId] = reactExports.useState(null);
3903
3938
  const [sourceFilter, setSourceFilter] = reactExports.useState("all");
3904
3939
  const listScrollRef = reactExports.useRef(null);
3905
3940
  const highlightTimeoutRef = reactExports.useRef(null);
@@ -4088,7 +4123,12 @@ function ProvidersPanel({
4088
4123
  })();
4089
4124
  }
4090
4125
  function handleDeleteProvider(providerId) {
4091
- if (!window.confirm("Are you sure you want to delete this provider?")) return;
4126
+ setDeletingProviderId(providerId);
4127
+ setConfirmOpen(true);
4128
+ }
4129
+ function executeDelete() {
4130
+ const providerId = deletingProviderId;
4131
+ if (providerId === null) return;
4092
4132
  void (async () => {
4093
4133
  let res;
4094
4134
  try {
@@ -4218,19 +4258,22 @@ function ProvidersPanel({
4218
4258
  style: { display: "none" }
4219
4259
  }
4220
4260
  ),
4221
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
4222
- Button,
4223
- {
4224
- variant: "outline",
4225
- size: "sm",
4226
- onClick: () => setShowImportWizard(true),
4227
- className: "gap-1",
4228
- children: [
4229
- /* @__PURE__ */ jsxRuntimeExports.jsx(Scan, { className: "size-3" }),
4230
- "Scan"
4231
- ]
4232
- }
4233
- ),
4261
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
4262
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
4263
+ Button,
4264
+ {
4265
+ variant: "outline",
4266
+ size: "sm",
4267
+ onClick: () => setShowImportWizard(true),
4268
+ className: "gap-1",
4269
+ children: [
4270
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Scan, { className: "size-3" }),
4271
+ "Scan"
4272
+ ]
4273
+ }
4274
+ ) }),
4275
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Detect and import provider configs from Claude Code and OpenCode" })
4276
+ ] }) }),
4234
4277
  /* @__PURE__ */ jsxRuntimeExports.jsxs(Button, { onClick: () => setShowForm(true), size: "sm", className: "gap-1", children: [
4235
4278
  /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { className: "size-4" }),
4236
4279
  "Add Provider"
@@ -4305,6 +4348,18 @@ function ProvidersPanel({
4305
4348
  }
4306
4349
  }
4307
4350
  }
4351
+ ),
4352
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
4353
+ ConfirmDialog,
4354
+ {
4355
+ open: confirmOpen,
4356
+ onOpenChange: setConfirmOpen,
4357
+ title: "Delete Provider",
4358
+ description: "Are you sure you want to delete this provider? This action cannot be undone.",
4359
+ confirmLabel: "Delete",
4360
+ variant: "destructive",
4361
+ onConfirm: executeDelete
4362
+ }
4308
4363
  )
4309
4364
  ] });
4310
4365
  }
@@ -198,7 +198,7 @@ function getResponse() {
198
198
  return event.res;
199
199
  }
200
200
  async function getStartManifest(matchedRoutes) {
201
- const { tsrStartManifest } = await import("../_tanstack-start-manifest_v-DupqJc5d.mjs");
201
+ const { tsrStartManifest } = await import("../_tanstack-start-manifest_v-XNH7fVPN.mjs");
202
202
  const startManifest = tsrStartManifest();
203
203
  const rootRoute = startManifest.routes[rootRouteId] = startManifest.routes[rootRouteId] || {};
204
204
  rootRoute.assets = rootRoute.assets || [];
@@ -767,7 +767,7 @@ let entriesPromise;
767
767
  let baseManifestPromise;
768
768
  let cachedFinalManifestPromise;
769
769
  async function loadEntries() {
770
- const routerEntry = await import("./router-DCfjmmJu.mjs").then((n) => n.r);
770
+ const routerEntry = await import("./router-lUOA8pi6.mjs").then((n) => n.r);
771
771
  const startEntry = await import("./start-HYkvq4Ni.mjs");
772
772
  return { startEntry, routerEntry };
773
773
  }
@@ -45,7 +45,7 @@ import "../_libs/debounce-fn.mjs";
45
45
  import "../_libs/mimic-function.mjs";
46
46
  import "../_libs/semver.mjs";
47
47
  import "../_libs/uint8array-extras.mjs";
48
- const appCss = "/assets/index-BFNoWwFI.css";
48
+ const appCss = "/assets/index-DXUNTCVh.css";
49
49
  const Route$k = createRootRoute({
50
50
  head: () => ({
51
51
  meta: [
@@ -69,7 +69,7 @@ function RootDocument({ children }) {
69
69
  ] })
70
70
  ] });
71
71
  }
72
- const $$splitComponentImporter = () => import("./index-D2yS8VvO.mjs");
72
+ const $$splitComponentImporter = () => import("./index-BvHLASu8.mjs");
73
73
  const Route$j = createFileRoute("/")({
74
74
  component: lazyRouteComponent($$splitComponentImporter, "component")
75
75
  });
@@ -1,4 +1,4 @@
1
- const tsrStartManifest = () => ({ "routes": { "__root__": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/__root.tsx", "children": ["/", "/api/config", "/api/health", "/api/logs", "/api/mcp", "/api/models", "/api/providers", "/api/sessions", "/proxy/$"], "preloads": ["/assets/main-Dp5657Eq.js"], "assets": [] }, "/": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/index.tsx", "assets": [], "preloads": ["/assets/index-LH-YtFEM.js"] }, "/api/config": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/config.ts", "children": ["/api/config/paths"] }, "/api/health": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/health.ts" }, "/api/logs": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.ts", "children": ["/api/logs/$id", "/api/logs/stream"] }, "/api/mcp": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/mcp.ts" }, "/api/models": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/models.ts" }, "/api/providers": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.ts", "children": ["/api/providers/$providerId", "/api/providers/export", "/api/providers/import", "/api/providers/scan"] }, "/api/sessions": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/sessions.ts" }, "/proxy/$": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/proxy/$.ts" }, "/api/config/paths": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/config.paths.ts" }, "/api/logs/$id": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.$id.ts", "children": ["/api/logs/$id/chunks", "/api/logs/$id/replay"] }, "/api/logs/stream": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.stream.ts" }, "/api/providers/$providerId": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.$providerId.ts", "children": ["/api/providers/$providerId/test"] }, "/api/providers/export": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.export.ts" }, "/api/providers/import": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.import.ts" }, "/api/providers/scan": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.scan.ts" }, "/api/logs/$id/chunks": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.$id.chunks.ts" }, "/api/logs/$id/replay": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.$id.replay.ts" }, "/api/providers/$providerId/test": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.$providerId.test.ts", "children": ["/api/providers/$providerId/test/log"] }, "/api/providers/$providerId/test/log": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.$providerId.test.log.ts" } }, "clientEntry": "/assets/main-Dp5657Eq.js" });
1
+ const tsrStartManifest = () => ({ "routes": { "__root__": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/__root.tsx", "children": ["/", "/api/config", "/api/health", "/api/logs", "/api/mcp", "/api/models", "/api/providers", "/api/sessions", "/proxy/$"], "preloads": ["/assets/main-BV7uNIIz.js"], "assets": [] }, "/": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/index.tsx", "assets": [], "preloads": ["/assets/index-Cmi8TfeU.js"] }, "/api/config": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/config.ts", "children": ["/api/config/paths"] }, "/api/health": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/health.ts" }, "/api/logs": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.ts", "children": ["/api/logs/$id", "/api/logs/stream"] }, "/api/mcp": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/mcp.ts" }, "/api/models": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/models.ts" }, "/api/providers": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.ts", "children": ["/api/providers/$providerId", "/api/providers/export", "/api/providers/import", "/api/providers/scan"] }, "/api/sessions": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/sessions.ts" }, "/proxy/$": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/proxy/$.ts" }, "/api/config/paths": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/config.paths.ts" }, "/api/logs/$id": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.$id.ts", "children": ["/api/logs/$id/chunks", "/api/logs/$id/replay"] }, "/api/logs/stream": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.stream.ts" }, "/api/providers/$providerId": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.$providerId.ts", "children": ["/api/providers/$providerId/test"] }, "/api/providers/export": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.export.ts" }, "/api/providers/import": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.import.ts" }, "/api/providers/scan": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.scan.ts" }, "/api/logs/$id/chunks": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.$id.chunks.ts" }, "/api/logs/$id/replay": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.$id.replay.ts" }, "/api/providers/$providerId/test": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.$providerId.test.ts", "children": ["/api/providers/$providerId/test/log"] }, "/api/providers/$providerId/test/log": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.$providerId.test.log.ts" } }, "clientEntry": "/assets/main-BV7uNIIz.js" });
2
2
  export {
3
3
  tsrStartManifest
4
4
  };
@@ -35,54 +35,54 @@ const headers = ((m) => function headersRouteRule(event) {
35
35
  }
36
36
  });
37
37
  const assets = {
38
- "/assets/minimax-BPMzvuL-.jpeg": {
39
- "type": "image/jpeg",
40
- "etag": '"1b06-IwivU89ko5UTMUM1/t7hn4sQK9A"',
41
- "mtime": "2026-06-11T06:43:41.004Z",
42
- "size": 6918,
43
- "path": "../public/assets/minimax-BPMzvuL-.jpeg"
44
- },
45
38
  "/assets/alibaba-TTwafVwX.svg": {
46
39
  "type": "image/svg+xml",
47
40
  "etag": '"171b-6dyV5K8QjiaY35sN9qNprh9zDIs"',
48
- "mtime": "2026-06-11T06:43:41.001Z",
41
+ "mtime": "2026-06-11T07:46:13.437Z",
49
42
  "size": 5915,
50
43
  "path": "../public/assets/alibaba-TTwafVwX.svg"
51
44
  },
45
+ "/assets/minimax-BPMzvuL-.jpeg": {
46
+ "type": "image/jpeg",
47
+ "etag": '"1b06-IwivU89ko5UTMUM1/t7hn4sQK9A"',
48
+ "mtime": "2026-06-11T07:46:13.439Z",
49
+ "size": 6918,
50
+ "path": "../public/assets/minimax-BPMzvuL-.jpeg"
51
+ },
52
52
  "/assets/zhipuai-BPNAnxo-.svg": {
53
53
  "type": "image/svg+xml",
54
54
  "etag": '"2bf8-hNaLCTi89nOFCsIIfWpP/jrfo0s"',
55
- "mtime": "2026-06-11T06:43:41.004Z",
55
+ "mtime": "2026-06-11T07:46:13.439Z",
56
56
  "size": 11256,
57
57
  "path": "../public/assets/zhipuai-BPNAnxo-.svg"
58
58
  },
59
- "/assets/main-Dp5657Eq.js": {
60
- "type": "text/javascript; charset=utf-8",
61
- "etag": '"50599-OPDsrJIp7Frbq0SCr8D0WjaPodo"',
62
- "mtime": "2026-06-11T06:43:41.005Z",
63
- "size": 329113,
64
- "path": "../public/assets/main-Dp5657Eq.js"
65
- },
66
59
  "/assets/qwen-CONDcHqt.png": {
67
60
  "type": "image/png",
68
61
  "etag": '"572c3-cdJAPaHdOvFCGzuaQjagdgOu6XE"',
69
- "mtime": "2026-06-11T06:43:41.004Z",
62
+ "mtime": "2026-06-11T07:46:13.439Z",
70
63
  "size": 357059,
71
64
  "path": "../public/assets/qwen-CONDcHqt.png"
72
65
  },
73
- "/assets/index-BFNoWwFI.css": {
66
+ "/assets/main-BV7uNIIz.js": {
67
+ "type": "text/javascript; charset=utf-8",
68
+ "etag": '"50599-Hw6XWHNqYOzGgMQUjQUlo6V5ZGQ"',
69
+ "mtime": "2026-06-11T07:46:13.439Z",
70
+ "size": 329113,
71
+ "path": "../public/assets/main-BV7uNIIz.js"
72
+ },
73
+ "/assets/index-DXUNTCVh.css": {
74
74
  "type": "text/css; charset=utf-8",
75
- "etag": '"144e4-grvzhe/fAjYnQnpkCdqNtv4X0xU"',
76
- "mtime": "2026-06-11T06:43:41.004Z",
77
- "size": 83172,
78
- "path": "../public/assets/index-BFNoWwFI.css"
75
+ "etag": '"145d7-BpJnON+Y0T31X0nkA2icHh97eLY"',
76
+ "mtime": "2026-06-11T07:46:13.439Z",
77
+ "size": 83415,
78
+ "path": "../public/assets/index-DXUNTCVh.css"
79
79
  },
80
- "/assets/index-LH-YtFEM.js": {
80
+ "/assets/index-Cmi8TfeU.js": {
81
81
  "type": "text/javascript; charset=utf-8",
82
- "etag": '"93c58-GIupunlXKDH8orHkFZHzh2kZq3s"',
83
- "mtime": "2026-06-11T06:43:41.005Z",
84
- "size": 605272,
85
- "path": "../public/assets/index-LH-YtFEM.js"
82
+ "etag": '"94069-2UhstwPg+t7SdSjBWTNqX+kv980"',
83
+ "mtime": "2026-06-11T07:46:13.440Z",
84
+ "size": 606313,
85
+ "path": "../public/assets/index-Cmi8TfeU.js"
86
86
  }
87
87
  };
88
88
  function readAsset(id) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tonyclaw/llm-inspector",
3
- "version": "1.14.6",
3
+ "version": "1.14.7",
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",
@@ -180,6 +180,12 @@ export function ImportWizardDialog({
180
180
  return;
181
181
  }
182
182
  const result = parsed.data;
183
+ // Mark imported providers as already existing so they can't be re-selected
184
+ setProviders((prev) =>
185
+ prev.map((p, i) => (selected.has(i) ? { ...p, alreadyExists: true } : p)),
186
+ );
187
+ setSelected(new Set());
188
+
183
189
  if (
184
190
  result.errors !== undefined &&
185
191
  result.errors.length > 0 &&
@@ -1,8 +1,10 @@
1
1
  import { type JSX, useState, useEffect, useCallback, useMemo, useRef } from "react";
2
2
  import { z } from "zod";
3
3
  import { Button } from "../ui/button";
4
+ import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "../ui/tooltip";
4
5
  import { Plus, AlertCircle, Copy, Check, Download, Upload, Scan } from "lucide-react";
5
6
  import { ImportWizardDialog } from "./ImportWizardDialog";
7
+ import { ConfirmDialog } from "../ui/confirm-dialog";
6
8
  import { ProviderCard } from "./ProviderCard";
7
9
  import { ProviderForm } from "./ProviderForm";
8
10
  import { parseJsonResponse, readApiError } from "../../lib/apiClient";
@@ -87,6 +89,8 @@ export function ProvidersPanel({
87
89
  const [configPathCopied, setConfigPathCopied] = useState(false);
88
90
  const [highlightedProviderId, setHighlightedProviderId] = useState<string | null>(null);
89
91
  const [showImportWizard, setShowImportWizard] = useState(false);
92
+ const [confirmOpen, setConfirmOpen] = useState(false);
93
+ const [deletingProviderId, setDeletingProviderId] = useState<string | null>(null);
90
94
  const [sourceFilter, setSourceFilter] = useState<"all" | "personal" | "company">("all");
91
95
  const listScrollRef = useRef<HTMLDivElement>(null);
92
96
  const highlightTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
@@ -309,8 +313,13 @@ export function ProvidersPanel({
309
313
  }
310
314
 
311
315
  function handleDeleteProvider(providerId: string): void {
312
- // eslint-disable-next-line no-alert
313
- if (!window.confirm("Are you sure you want to delete this provider?")) return;
316
+ setDeletingProviderId(providerId);
317
+ setConfirmOpen(true);
318
+ }
319
+
320
+ function executeDelete(): void {
321
+ const providerId = deletingProviderId;
322
+ if (providerId === null) return;
314
323
  void (async () => {
315
324
  let res: Response;
316
325
  try {
@@ -450,15 +459,24 @@ export function ProvidersPanel({
450
459
  onChange={handleFileChange}
451
460
  style={{ display: "none" }}
452
461
  />
453
- <Button
454
- variant="outline"
455
- size="sm"
456
- onClick={() => setShowImportWizard(true)}
457
- className="gap-1"
458
- >
459
- <Scan className="size-3" />
460
- Scan
461
- </Button>
462
+ <TooltipProvider>
463
+ <Tooltip>
464
+ <TooltipTrigger asChild>
465
+ <Button
466
+ variant="outline"
467
+ size="sm"
468
+ onClick={() => setShowImportWizard(true)}
469
+ className="gap-1"
470
+ >
471
+ <Scan className="size-3" />
472
+ Scan
473
+ </Button>
474
+ </TooltipTrigger>
475
+ <TooltipContent>
476
+ Detect and import provider configs from Claude Code and OpenCode
477
+ </TooltipContent>
478
+ </Tooltip>
479
+ </TooltipProvider>
462
480
  <Button onClick={() => setShowForm(true)} size="sm" className="gap-1">
463
481
  <Plus className="size-4" />
464
482
  Add Provider
@@ -554,6 +572,16 @@ export function ProvidersPanel({
554
572
  }
555
573
  }}
556
574
  />
575
+
576
+ <ConfirmDialog
577
+ open={confirmOpen}
578
+ onOpenChange={setConfirmOpen}
579
+ title="Delete Provider"
580
+ description="Are you sure you want to delete this provider? This action cannot be undone."
581
+ confirmLabel="Delete"
582
+ variant="destructive"
583
+ onConfirm={executeDelete}
584
+ />
557
585
  </div>
558
586
  );
559
587
  }
@@ -0,0 +1,51 @@
1
+ import { type JSX } from "react";
2
+ import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog";
3
+ import { Button } from "../ui/button";
4
+
5
+ export type ConfirmDialogVariant = "default" | "destructive";
6
+
7
+ export type ConfirmDialogProps = {
8
+ open: boolean;
9
+ onOpenChange: (open: boolean) => void;
10
+ title: string;
11
+ description: string;
12
+ confirmLabel?: string;
13
+ variant?: ConfirmDialogVariant;
14
+ onConfirm: () => void;
15
+ };
16
+
17
+ export function ConfirmDialog({
18
+ open,
19
+ onOpenChange,
20
+ title,
21
+ description,
22
+ confirmLabel = "Confirm",
23
+ variant = "default",
24
+ onConfirm,
25
+ }: ConfirmDialogProps): JSX.Element {
26
+ return (
27
+ <Dialog open={open} onOpenChange={onOpenChange}>
28
+ <DialogContent>
29
+ <DialogHeader>
30
+ <DialogTitle>{title}</DialogTitle>
31
+ </DialogHeader>
32
+ <p className="text-sm text-muted-foreground">{description}</p>
33
+ <div className="flex justify-end gap-2 pt-2">
34
+ <Button variant="outline" size="sm" onClick={() => onOpenChange(false)}>
35
+ Cancel
36
+ </Button>
37
+ <Button
38
+ variant={variant}
39
+ size="sm"
40
+ onClick={() => {
41
+ onConfirm();
42
+ onOpenChange(false);
43
+ }}
44
+ >
45
+ {confirmLabel}
46
+ </Button>
47
+ </div>
48
+ </DialogContent>
49
+ </Dialog>
50
+ );
51
+ }