@tonyclaw/llm-inspector 1.13.0 → 1.14.1
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.
- package/.output/nitro.json +1 -1
- package/.output/public/assets/index-DEUddp_2.css +1 -0
- package/.output/public/assets/index-ax85pt2A.js +105 -0
- package/.output/public/assets/{main-C3tLo75s.js → main-BzK2SzIB.js} +3 -3
- package/.output/server/_libs/cfworker__json-schema.mjs +1 -0
- package/.output/server/_libs/lucide-react.mjs +60 -54
- package/.output/server/_libs/modelcontextprotocol__server.mjs +9738 -0
- package/.output/server/_libs/zod.mjs +79 -16
- package/.output/server/_ssr/{index-C8VC13EA.mjs → index-Cso39vJc.mjs} +715 -178
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-D5ccnemB.mjs → router-B8X3GXM2.mjs} +669 -39
- package/.output/server/_tanstack-start-manifest_v-vO4aM6jK.mjs +4 -0
- package/.output/server/index.mjs +29 -29
- package/README.md +98 -0
- package/package.json +3 -1
- package/src/components/providers/ProviderCard.tsx +28 -5
- package/src/components/providers/ProviderForm.tsx +172 -66
- package/src/components/providers/ProvidersPanel.tsx +73 -47
- package/src/components/proxy-viewer/CompareDrawer.tsx +583 -91
- package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +74 -4
- package/src/lib/mask.ts +4 -0
- package/src/lib/providerContract.ts +1 -0
- package/src/lib/serverPort.ts +41 -0
- package/src/mcp/loopback.ts +76 -0
- package/src/mcp/previewExtractor.ts +166 -0
- package/src/mcp/server.ts +320 -0
- package/src/mcp/toolHandlers.ts +259 -0
- package/src/proxy/formats/openai/schemas.ts +19 -0
- package/src/proxy/handler.ts +23 -2
- package/src/proxy/openaiOrphanToolStrip.ts +148 -0
- package/src/proxy/providers.ts +16 -4
- package/src/proxy/schemas.ts +1 -0
- package/src/routes/api/mcp.ts +25 -0
- package/src/routes/api/providers.$providerId.ts +1 -0
- package/src/routes/api/providers.ts +6 -4
- package/.output/public/assets/index-B0anmGQr.css +0 -1
- package/.output/public/assets/index-H_thmL2_.js +0 -105
- package/.output/server/_tanstack-start-manifest_v-DUbXa1lt.mjs +0 -4
|
@@ -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, P as ProviderConfigSchema, p as parseOpenAIResponse, I as InspectorResponseSchema, S as StreamingChunkSchema$1 } from "./router-
|
|
2
|
+
import { C as CapturedLogSchema, a as parseRequest, s as stripClaudeCodeBillingHeader, R as RuntimeConfigSchema, P as ProviderConfigSchema, p as parseOpenAIResponse, I as InspectorResponseSchema, S as StreamingChunkSchema$1 } from "./router-B8X3GXM2.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";
|
|
@@ -9,9 +9,10 @@ import { c as cva } from "../_libs/class-variance-authority.mjs";
|
|
|
9
9
|
import { d as diffLines, a as diffJson } from "../_libs/diff.mjs";
|
|
10
10
|
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";
|
|
11
11
|
import { R as Root2, T as Trigger, I as Icon, V as Value, P as Portal, C as Content2, a as Viewport, b as Item, c as ItemIndicator, d as ItemText, S as ScrollUpButton, e as ScrollDownButton } from "../_libs/radix-ui__react-select.mjs";
|
|
12
|
-
import
|
|
12
|
+
import "../_libs/modelcontextprotocol__server.mjs";
|
|
13
|
+
import { D as Download, L as LayoutGrid, a as List, G as GitCompareArrows, X, S as Settings, C as ChevronDown, b as Check, R as RotateCcw, 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, i as Globe, j as User, F as FileTerminal, k as Radio, l as Rows3, m as Columns2, n as Minus, o as Pencil, E as Equal, p as EyeOff, q as Eye, r as ExternalLink, s as RotateCw, T as Trash2, A as ArrowUp, t as ArrowDown, u as TriangleAlert, v as CircleCheckBig, w as CircleStop, x as CircleQuestionMark, y as Server, z as Gauge, B as Lock, H as Wifi, I as WifiOff, J as ChevronsUp, K as ChevronsDown, N as Brain, O as Terminal } from "../_libs/lucide-react.mjs";
|
|
13
14
|
import { M as Markdown } from "../_libs/react-markdown.mjs";
|
|
14
|
-
import { a as array,
|
|
15
|
+
import { a as array, b as string, u as union, d as object, l as literal, n as number, c as boolean, r as record, _ as _enum } from "../_libs/zod.mjs";
|
|
15
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";
|
|
16
17
|
import { S as Slot } from "../_libs/radix-ui__react-slot.mjs";
|
|
17
18
|
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";
|
|
@@ -275,7 +276,7 @@ async function exportLogsAsZip(logs) {
|
|
|
275
276
|
document.body.removeChild(anchor);
|
|
276
277
|
URL.revokeObjectURL(url);
|
|
277
278
|
}
|
|
278
|
-
const version = "1.
|
|
279
|
+
const version = "1.14.1";
|
|
279
280
|
const packageJson = {
|
|
280
281
|
version
|
|
281
282
|
};
|
|
@@ -1888,9 +1889,37 @@ function StructuredResponseViewAnthropic({
|
|
|
1888
1889
|
] })
|
|
1889
1890
|
] });
|
|
1890
1891
|
}
|
|
1892
|
+
function parseToolArguments(raw) {
|
|
1893
|
+
if (raw === void 0 || raw === "") return {};
|
|
1894
|
+
try {
|
|
1895
|
+
return JSON.parse(raw);
|
|
1896
|
+
} catch {
|
|
1897
|
+
return null;
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
function OpenAIToolCallBlock({ call }) {
|
|
1901
|
+
const [open, setOpen] = reactExports.useState(false);
|
|
1902
|
+
const name = call.function.name ?? "(unnamed tool)";
|
|
1903
|
+
const parsed = parseToolArguments(call.function.arguments);
|
|
1904
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(Collapsible, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-l-2 border-blue-500/40 my-1", children: [
|
|
1905
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(CollapsibleTrigger, { className: "flex items-center gap-1.5 px-3 py-1 w-full text-left cursor-pointer hover:bg-blue-500/5 transition-colors rounded-r-sm group", children: [
|
|
1906
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Terminal, { className: "size-3.5 text-blue-400 shrink-0" }),
|
|
1907
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", className: "text-[10px] font-mono px-1.5 py-0 h-4", children: name }),
|
|
1908
|
+
call.id !== void 0 && call.id !== "" && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-mono text-muted-foreground/60 truncate", children: call.id }),
|
|
1909
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1" }),
|
|
1910
|
+
open ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-3 text-muted-foreground" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-3 text-muted-foreground" })
|
|
1911
|
+
] }),
|
|
1912
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(CollapsibleContent, { children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 pb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollArea, { className: "max-h-[60vh]", children: parsed === null ? (
|
|
1913
|
+
// JSON.parse failed — show the raw string so the user can
|
|
1914
|
+
// still see what the model tried to call.
|
|
1915
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "font-mono text-xs whitespace-pre-wrap break-words text-rose-300/90", children: call.function.arguments })
|
|
1916
|
+
) : /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewer, { data: safeJsonValue(parsed), defaultExpandDepth: 2 }) }) }) })
|
|
1917
|
+
] }) });
|
|
1918
|
+
}
|
|
1891
1919
|
function OpenAIResponseView({ response }) {
|
|
1892
1920
|
const choice = response.choices[0];
|
|
1893
1921
|
const message = choice?.message;
|
|
1922
|
+
const toolCalls = message?.tool_calls ?? [];
|
|
1894
1923
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [
|
|
1895
1924
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
|
|
1896
1925
|
/* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "secondary", className: "text-[10px] px-1.5 py-0 h-5 font-mono", children: response.model }),
|
|
@@ -1927,6 +1956,10 @@ function OpenAIResponseView({ response }) {
|
|
|
1927
1956
|
remainingText.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "prose prose-sm dark:prose-invert max-w-none [&_pre]:bg-muted [&_pre]:text-foreground [&_code]:text-[0.8em] [&_p]:my-1 [&_ul]:my-1 [&_ol]:my-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Markdown, { children: remainingText }) })
|
|
1928
1957
|
] });
|
|
1929
1958
|
})(),
|
|
1959
|
+
toolCalls.map((call, i) => (
|
|
1960
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: tool_calls is the positionally stable list from the response
|
|
1961
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(OpenAIToolCallBlock, { call }, call.id ?? `tc-${i}`)
|
|
1962
|
+
)),
|
|
1930
1963
|
message?.function_call !== null && message?.function_call !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-blue-500/30 rounded-md p-3 bg-blue-500/5", children: [
|
|
1931
1964
|
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-blue-400 font-mono mb-1", children: "function_call" }),
|
|
1932
1965
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "font-mono text-xs", children: [
|
|
@@ -1938,7 +1971,7 @@ function OpenAIResponseView({ response }) {
|
|
|
1938
1971
|
] })
|
|
1939
1972
|
] })
|
|
1940
1973
|
] }),
|
|
1941
|
-
(message?.content === null || message?.content === void 0 || message.content.length === 0) && (message?.reasoning_content === null || message?.reasoning_content === void 0 || message.reasoning_content.length === 0) && (message?.function_call === null || message?.function_call === void 0) && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "Empty response content" })
|
|
1974
|
+
(message?.content === null || message?.content === void 0 || message.content.length === 0) && (message?.reasoning_content === null || message?.reasoning_content === void 0 || message.reasoning_content.length === 0) && (message?.function_call === null || message?.function_call === void 0) && toolCalls.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "Empty response content" })
|
|
1942
1975
|
] })
|
|
1943
1976
|
] });
|
|
1944
1977
|
}
|
|
@@ -2828,13 +2861,13 @@ function SelectScrollDownButton({
|
|
|
2828
2861
|
}
|
|
2829
2862
|
);
|
|
2830
2863
|
}
|
|
2831
|
-
const KNOWN_PROVIDER_DOCS = {
|
|
2832
|
-
deepseek: "https://api-docs.deepseek.com/zh-cn/"
|
|
2833
|
-
};
|
|
2834
2864
|
function maskApiKey(apiKey) {
|
|
2835
2865
|
if (apiKey.length <= 8) return "••••••••";
|
|
2836
2866
|
return apiKey.slice(0, 4) + "••••••••" + apiKey.slice(-4);
|
|
2837
2867
|
}
|
|
2868
|
+
const KNOWN_PROVIDER_DOCS = {
|
|
2869
|
+
deepseek: "https://api-docs.deepseek.com/zh-cn/"
|
|
2870
|
+
};
|
|
2838
2871
|
function hasSuccessField(result) {
|
|
2839
2872
|
return Object.prototype.hasOwnProperty.call(result, "success");
|
|
2840
2873
|
}
|
|
@@ -2962,6 +2995,13 @@ function ProviderCard({
|
|
|
2962
2995
|
onTest
|
|
2963
2996
|
}) {
|
|
2964
2997
|
const [showApiKey, setShowApiKey] = reactExports.useState(false);
|
|
2998
|
+
const [copied, setCopied] = reactExports.useState(false);
|
|
2999
|
+
function handleCopy() {
|
|
3000
|
+
navigator.clipboard.writeText(provider.apiKey).catch(() => {
|
|
3001
|
+
});
|
|
3002
|
+
setCopied(true);
|
|
3003
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
3004
|
+
}
|
|
2965
3005
|
const docsUrl = provider.apiDocsUrl ?? Object.entries(KNOWN_PROVIDER_DOCS).find(
|
|
2966
3006
|
([keyword]) => provider.name.toLowerCase().includes(keyword)
|
|
2967
3007
|
)?.[1];
|
|
@@ -2971,7 +3011,11 @@ function ProviderCard({
|
|
|
2971
3011
|
className: `border rounded-lg p-4 flex flex-col gap-3 bg-card transition-all duration-500 ${isHighlighted === true ? "ring-2 ring-primary shadow-md" : ""}`,
|
|
2972
3012
|
children: [
|
|
2973
3013
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start justify-between gap-2", children: [
|
|
2974
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
3014
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
|
|
3015
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium truncate", children: provider.model !== void 0 && provider.model !== "" ? `${provider.model} (${provider.name})` : provider.name }),
|
|
3016
|
+
provider.source === "company" && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs px-1.5 py-0.5 rounded bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400 shrink-0", children: "公司" }),
|
|
3017
|
+
provider.source === "personal" && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs px-1.5 py-0.5 rounded bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400 shrink-0", children: "个人" })
|
|
3018
|
+
] }),
|
|
2975
3019
|
docsUrl !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
2976
3020
|
"a",
|
|
2977
3021
|
{
|
|
@@ -2998,6 +3042,16 @@ function ProviderCard({
|
|
|
2998
3042
|
"aria-label": showApiKey ? "Hide API key" : "Show API key",
|
|
2999
3043
|
children: showApiKey ? /* @__PURE__ */ jsxRuntimeExports.jsx(EyeOff, { className: "size-4" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { className: "size-4" })
|
|
3000
3044
|
}
|
|
3045
|
+
),
|
|
3046
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3047
|
+
"button",
|
|
3048
|
+
{
|
|
3049
|
+
type: "button",
|
|
3050
|
+
onClick: handleCopy,
|
|
3051
|
+
className: "text-muted-foreground hover:text-foreground transition-colors p-1",
|
|
3052
|
+
"aria-label": "Copy API key",
|
|
3053
|
+
children: copied ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-4 text-green-500" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "size-4" })
|
|
3054
|
+
}
|
|
3001
3055
|
)
|
|
3002
3056
|
] }),
|
|
3003
3057
|
provider.anthropicBaseUrl !== void 0 && provider.anthropicBaseUrl !== "" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
|
|
@@ -3091,33 +3145,47 @@ const ALIBABA_MODELS = ["glm-5", "glm-5.1", "qwen3.6-plus", "qwen3.7-max"];
|
|
|
3091
3145
|
function ProviderForm({ provider, onSubmit, onCancel }) {
|
|
3092
3146
|
const [name, setName] = reactExports.useState(provider?.name ?? "Provider Name");
|
|
3093
3147
|
const [apiKey, setApiKey] = reactExports.useState(provider?.apiKey ?? "");
|
|
3148
|
+
const [showApiKey, setShowApiKey] = reactExports.useState(false);
|
|
3149
|
+
const [copied, setCopied] = reactExports.useState(false);
|
|
3094
3150
|
const [model, setModel] = reactExports.useState(provider?.model ?? "");
|
|
3095
|
-
const [
|
|
3096
|
-
const [
|
|
3151
|
+
const [activeTab, setActiveTab] = reactExports.useState("anthropic");
|
|
3152
|
+
const [anthropicBaseUrl, setAnthropicBaseUrl] = reactExports.useState(provider?.anthropicBaseUrl ?? "");
|
|
3153
|
+
const [openaiBaseUrl, setOpenaiBaseUrl] = reactExports.useState(provider?.openaiBaseUrl ?? "");
|
|
3097
3154
|
const [apiDocsUrl, setApiDocsUrl] = reactExports.useState(provider?.apiDocsUrl ?? "");
|
|
3155
|
+
const [source, setSource] = reactExports.useState(provider?.source);
|
|
3098
3156
|
const [errors, setErrors] = reactExports.useState({});
|
|
3099
3157
|
const [isSubmitting, setIsSubmitting] = reactExports.useState(false);
|
|
3100
|
-
const [
|
|
3158
|
+
const [manualAnthropicUrlOverride, setManualAnthropicUrlOverride] = reactExports.useState(false);
|
|
3159
|
+
const [manualOpenaiUrlOverride, setManualOpenaiUrlOverride] = reactExports.useState(false);
|
|
3101
3160
|
const isMiniMax = name.toLowerCase().includes("minimax");
|
|
3102
3161
|
const isAlibaba = name.toLowerCase().includes("alibaba");
|
|
3162
|
+
function handleCopy() {
|
|
3163
|
+
navigator.clipboard.writeText(apiKey).catch(() => {
|
|
3164
|
+
});
|
|
3165
|
+
setCopied(true);
|
|
3166
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
3167
|
+
}
|
|
3103
3168
|
reactExports.useEffect(() => {
|
|
3104
3169
|
if (provider) {
|
|
3105
3170
|
setName(provider.name);
|
|
3106
3171
|
setApiKey(provider.apiKey);
|
|
3107
3172
|
setModel(provider.model ?? "");
|
|
3108
|
-
|
|
3109
|
-
|
|
3173
|
+
setAnthropicBaseUrl(provider.anthropicBaseUrl ?? "");
|
|
3174
|
+
setOpenaiBaseUrl(provider.openaiBaseUrl ?? "");
|
|
3110
3175
|
setApiDocsUrl(provider.apiDocsUrl ?? "");
|
|
3111
|
-
|
|
3176
|
+
setSource(provider.source);
|
|
3177
|
+
setManualAnthropicUrlOverride(false);
|
|
3178
|
+
setManualOpenaiUrlOverride(false);
|
|
3112
3179
|
}
|
|
3113
3180
|
}, [provider]);
|
|
3114
3181
|
reactExports.useEffect(() => {
|
|
3115
3182
|
const lowerName = name.toLowerCase();
|
|
3116
3183
|
for (const [keyword, preset] of Object.entries(KNOWN_PROVIDER_PRESETS)) {
|
|
3117
3184
|
if (lowerName.includes(keyword)) {
|
|
3118
|
-
if (!
|
|
3119
|
-
|
|
3120
|
-
|
|
3185
|
+
if (preset.format === "anthropic" && !manualAnthropicUrlOverride) {
|
|
3186
|
+
setAnthropicBaseUrl(preset.baseUrl);
|
|
3187
|
+
} else if (preset.format === "openai" && !manualOpenaiUrlOverride) {
|
|
3188
|
+
setOpenaiBaseUrl(preset.baseUrl);
|
|
3121
3189
|
}
|
|
3122
3190
|
if (preset.apiDocsUrl !== void 0 && !apiDocsUrl) {
|
|
3123
3191
|
setApiDocsUrl(preset.apiDocsUrl);
|
|
@@ -3143,10 +3211,14 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
|
|
|
3143
3211
|
if (!model.trim()) {
|
|
3144
3212
|
newErrors.model = "Model is required";
|
|
3145
3213
|
}
|
|
3146
|
-
if (!
|
|
3147
|
-
newErrors.
|
|
3148
|
-
}
|
|
3149
|
-
|
|
3214
|
+
if (anthropicBaseUrl.trim() && !isValidUrl(anthropicBaseUrl.trim())) {
|
|
3215
|
+
newErrors.anthropicBaseUrl = "Invalid URL format";
|
|
3216
|
+
}
|
|
3217
|
+
if (openaiBaseUrl.trim() && !isValidUrl(openaiBaseUrl.trim())) {
|
|
3218
|
+
newErrors.openaiBaseUrl = "Invalid URL format";
|
|
3219
|
+
}
|
|
3220
|
+
if (!anthropicBaseUrl.trim() && !openaiBaseUrl.trim()) {
|
|
3221
|
+
newErrors.format = "At least one format URL (Anthropic or OpenAI) is required";
|
|
3150
3222
|
}
|
|
3151
3223
|
setErrors(newErrors);
|
|
3152
3224
|
return Object.keys(newErrors).length === 0;
|
|
@@ -3168,9 +3240,10 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
|
|
|
3168
3240
|
name: name.trim(),
|
|
3169
3241
|
apiKey: apiKey.trim(),
|
|
3170
3242
|
model: model.trim() || void 0,
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
apiDocsUrl: apiDocsUrl.trim() || void 0
|
|
3243
|
+
anthropicBaseUrl: anthropicBaseUrl.trim() || void 0,
|
|
3244
|
+
openaiBaseUrl: openaiBaseUrl.trim() || void 0,
|
|
3245
|
+
apiDocsUrl: apiDocsUrl.trim() || void 0,
|
|
3246
|
+
source
|
|
3174
3247
|
});
|
|
3175
3248
|
} finally {
|
|
3176
3249
|
setIsSubmitting(false);
|
|
@@ -3195,22 +3268,68 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
|
|
|
3195
3268
|
),
|
|
3196
3269
|
errors.name !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-destructive", children: errors.name })
|
|
3197
3270
|
] }),
|
|
3271
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
|
|
3272
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { htmlFor: "provider-source", className: "text-sm font-medium", children: "Token Source" }),
|
|
3273
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
3274
|
+
"select",
|
|
3275
|
+
{
|
|
3276
|
+
id: "provider-source",
|
|
3277
|
+
value: source ?? "",
|
|
3278
|
+
onChange: (e) => setSource(
|
|
3279
|
+
e.target.value === "company" || e.target.value === "personal" ? e.target.value : void 0
|
|
3280
|
+
),
|
|
3281
|
+
className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:border-ring focus-visible:outline-ring focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
|
3282
|
+
children: [
|
|
3283
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "—" }),
|
|
3284
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "personal", children: "个人 (Personal)" }),
|
|
3285
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "company", children: "公司 (Company)" })
|
|
3286
|
+
]
|
|
3287
|
+
}
|
|
3288
|
+
),
|
|
3289
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: "Label whether this API key is company-provided or personal." })
|
|
3290
|
+
] }),
|
|
3198
3291
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
|
|
3199
3292
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { htmlFor: "provider-apikey", className: "text-sm font-medium", children: [
|
|
3200
3293
|
"API Key ",
|
|
3201
3294
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-destructive", children: "*" })
|
|
3202
3295
|
] }),
|
|
3203
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3296
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
3297
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3298
|
+
"input",
|
|
3299
|
+
{
|
|
3300
|
+
id: "provider-apikey",
|
|
3301
|
+
type: "text",
|
|
3302
|
+
value: showApiKey || apiKey.length === 0 ? apiKey : maskApiKey(apiKey),
|
|
3303
|
+
onChange: (e) => setApiKey(e.target.value),
|
|
3304
|
+
onFocus: () => {
|
|
3305
|
+
if (!showApiKey && apiKey.length > 0) setShowApiKey(true);
|
|
3306
|
+
},
|
|
3307
|
+
placeholder: "sk-ant-...",
|
|
3308
|
+
readOnly: !showApiKey && apiKey.length > 0,
|
|
3309
|
+
className: "flex-1 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"
|
|
3310
|
+
}
|
|
3311
|
+
),
|
|
3312
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3313
|
+
"button",
|
|
3314
|
+
{
|
|
3315
|
+
type: "button",
|
|
3316
|
+
onClick: () => setShowApiKey((s) => !s),
|
|
3317
|
+
className: "text-muted-foreground hover:text-foreground transition-colors p-1 shrink-0",
|
|
3318
|
+
"aria-label": showApiKey ? "Hide API key" : "Show API key",
|
|
3319
|
+
children: showApiKey ? /* @__PURE__ */ jsxRuntimeExports.jsx(EyeOff, { className: "size-4" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { className: "size-4" })
|
|
3320
|
+
}
|
|
3321
|
+
),
|
|
3322
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3323
|
+
"button",
|
|
3324
|
+
{
|
|
3325
|
+
type: "button",
|
|
3326
|
+
onClick: handleCopy,
|
|
3327
|
+
className: "text-muted-foreground hover:text-foreground transition-colors p-1 shrink-0",
|
|
3328
|
+
"aria-label": "Copy API key",
|
|
3329
|
+
children: copied ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-4 text-green-500" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "size-4" })
|
|
3330
|
+
}
|
|
3331
|
+
)
|
|
3332
|
+
] }),
|
|
3214
3333
|
errors.apiKey !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-destructive", children: errors.apiKey })
|
|
3215
3334
|
] }),
|
|
3216
3335
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
|
|
@@ -3241,46 +3360,65 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
|
|
|
3241
3360
|
errors.model !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-destructive", children: errors.model })
|
|
3242
3361
|
] }),
|
|
3243
3362
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
|
|
3244
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("
|
|
3245
|
-
|
|
3246
|
-
|
|
3363
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1 border-b border-border", children: [
|
|
3364
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3365
|
+
"button",
|
|
3366
|
+
{
|
|
3367
|
+
type: "button",
|
|
3368
|
+
onClick: () => setActiveTab("anthropic"),
|
|
3369
|
+
className: `px-3 py-2 text-sm font-medium border-b-2 transition-colors ${activeTab === "anthropic" ? "border-primary text-primary" : "border-transparent text-muted-foreground hover:text-foreground"}`,
|
|
3370
|
+
children: "Anthropic Format"
|
|
3371
|
+
}
|
|
3372
|
+
),
|
|
3373
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3374
|
+
"button",
|
|
3375
|
+
{
|
|
3376
|
+
type: "button",
|
|
3377
|
+
onClick: () => setActiveTab("openai"),
|
|
3378
|
+
className: `px-3 py-2 text-sm font-medium border-b-2 transition-colors ${activeTab === "openai" ? "border-primary text-primary" : "border-transparent text-muted-foreground hover:text-foreground"}`,
|
|
3379
|
+
children: "OpenAI Format"
|
|
3380
|
+
}
|
|
3381
|
+
)
|
|
3247
3382
|
] }),
|
|
3248
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
3249
|
-
|
|
3383
|
+
errors.format !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-destructive", children: errors.format })
|
|
3384
|
+
] }),
|
|
3385
|
+
activeTab === "anthropic" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
|
|
3386
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { htmlFor: "provider-anthropic-base-url", className: "text-sm font-medium", children: "Anthropic Base URL" }),
|
|
3387
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3388
|
+
"input",
|
|
3250
3389
|
{
|
|
3251
|
-
id: "provider-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3390
|
+
id: "provider-anthropic-base-url",
|
|
3391
|
+
type: "text",
|
|
3392
|
+
value: anthropicBaseUrl,
|
|
3393
|
+
onChange: (e) => {
|
|
3394
|
+
setManualAnthropicUrlOverride(true);
|
|
3395
|
+
setAnthropicBaseUrl(e.target.value);
|
|
3396
|
+
},
|
|
3397
|
+
placeholder: "https://api.anthropic.com",
|
|
3398
|
+
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"
|
|
3259
3399
|
}
|
|
3260
3400
|
),
|
|
3261
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-
|
|
3401
|
+
errors.anthropicBaseUrl !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-destructive", children: errors.anthropicBaseUrl }),
|
|
3402
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: "Anthropic-compatible endpoint URL. Leave empty if this provider does not support Anthropic format." })
|
|
3262
3403
|
] }),
|
|
3263
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
|
|
3264
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
3265
|
-
"Base URL ",
|
|
3266
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-destructive", children: "*" })
|
|
3267
|
-
] }),
|
|
3404
|
+
activeTab === "openai" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
|
|
3405
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { htmlFor: "provider-openai-base-url", className: "text-sm font-medium", children: "OpenAI Base URL" }),
|
|
3268
3406
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3269
3407
|
"input",
|
|
3270
3408
|
{
|
|
3271
|
-
id: "provider-base-url",
|
|
3409
|
+
id: "provider-openai-base-url",
|
|
3272
3410
|
type: "text",
|
|
3273
|
-
value:
|
|
3411
|
+
value: openaiBaseUrl,
|
|
3274
3412
|
onChange: (e) => {
|
|
3275
|
-
|
|
3276
|
-
|
|
3413
|
+
setManualOpenaiUrlOverride(true);
|
|
3414
|
+
setOpenaiBaseUrl(e.target.value);
|
|
3277
3415
|
},
|
|
3278
|
-
placeholder:
|
|
3416
|
+
placeholder: "https://api.openai.com/v1",
|
|
3279
3417
|
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"
|
|
3280
3418
|
}
|
|
3281
3419
|
),
|
|
3282
|
-
errors.
|
|
3283
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: "
|
|
3420
|
+
errors.openaiBaseUrl !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-destructive", children: errors.openaiBaseUrl }),
|
|
3421
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: "OpenAI-compatible endpoint URL. Leave empty if this provider does not support OpenAI format." })
|
|
3284
3422
|
] }),
|
|
3285
3423
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
|
|
3286
3424
|
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { htmlFor: "provider-api-docs-url", className: "text-sm font-medium", children: "API Docs URL" }),
|
|
@@ -3297,7 +3435,7 @@ function ProviderForm({ provider, onSubmit, onCancel }) {
|
|
|
3297
3435
|
),
|
|
3298
3436
|
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: "Optional API documentation URL. If not set, uses known provider docs." })
|
|
3299
3437
|
] }),
|
|
3300
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2 justify-end pt-2", children: [
|
|
3438
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "sticky bottom-0 bg-card border-t flex gap-2 justify-end pt-2 pb-2", children: [
|
|
3301
3439
|
/* @__PURE__ */ jsxRuntimeExports.jsx(Button, { type: "button", variant: "outline", onClick: onCancel, disabled: isSubmitting, children: "Cancel" }),
|
|
3302
3440
|
/* @__PURE__ */ jsxRuntimeExports.jsx(Button, { type: "submit", disabled: isSubmitting, children: isSubmitting ? "Saving..." : provider ? "Update Provider" : "Add Provider" })
|
|
3303
3441
|
] })
|
|
@@ -3396,9 +3534,10 @@ function createProviderPayload(data) {
|
|
|
3396
3534
|
name: data.name,
|
|
3397
3535
|
apiKey: data.apiKey,
|
|
3398
3536
|
model: data.model,
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3537
|
+
anthropicBaseUrl: (data.anthropicBaseUrl?.length ?? 0) > 0 ? data.anthropicBaseUrl : void 0,
|
|
3538
|
+
openaiBaseUrl: (data.openaiBaseUrl?.length ?? 0) > 0 ? data.openaiBaseUrl : void 0,
|
|
3539
|
+
apiDocsUrl: (data.apiDocsUrl?.length ?? 0) > 0 ? data.apiDocsUrl : void 0,
|
|
3540
|
+
source: data.source
|
|
3402
3541
|
};
|
|
3403
3542
|
}
|
|
3404
3543
|
function ProvidersPanel({
|
|
@@ -3423,9 +3562,11 @@ function ProvidersPanel({
|
|
|
3423
3562
|
const [configPath, setConfigPath] = reactExports.useState(null);
|
|
3424
3563
|
const [configPathCopied, setConfigPathCopied] = reactExports.useState(false);
|
|
3425
3564
|
const [highlightedProviderId, setHighlightedProviderId] = reactExports.useState(null);
|
|
3565
|
+
const [sourceFilter, setSourceFilter] = reactExports.useState("all");
|
|
3426
3566
|
const listScrollRef = reactExports.useRef(null);
|
|
3427
3567
|
const highlightTimeoutRef = reactExports.useRef(null);
|
|
3428
3568
|
const providers = externalProviders ?? [];
|
|
3569
|
+
const filteredProviders = sourceFilter === "all" ? providers : providers.filter((p) => p.source === sourceFilter);
|
|
3429
3570
|
const testResults = externalTestResults ?? internalTestResults;
|
|
3430
3571
|
const testingProviders = externalTestingProviders ?? internalTestingProviders;
|
|
3431
3572
|
const testingTimeLeft = externalTestingTimeLeft ?? internalTestingTimeLeft;
|
|
@@ -3553,51 +3694,50 @@ function ProvidersPanel({
|
|
|
3553
3694
|
);
|
|
3554
3695
|
function handleAddProvider(data) {
|
|
3555
3696
|
void (async () => {
|
|
3556
|
-
let res;
|
|
3557
3697
|
try {
|
|
3558
|
-
res = await fetch("/api/providers", {
|
|
3698
|
+
const res = await fetch("/api/providers", {
|
|
3559
3699
|
method: "POST",
|
|
3560
3700
|
headers: { "Content-Type": "application/json" },
|
|
3561
3701
|
body: JSON.stringify(createProviderPayload(data))
|
|
3562
3702
|
});
|
|
3703
|
+
if (!res.ok) {
|
|
3704
|
+
setError(await readApiError(res, "Failed to add provider"));
|
|
3705
|
+
return;
|
|
3706
|
+
}
|
|
3707
|
+
const newProvider = await parseJsonResponse(res, ProviderConfigSchema);
|
|
3708
|
+
setShowForm(false);
|
|
3709
|
+
triggerHighlight(newProvider.id);
|
|
3710
|
+
refreshProviders();
|
|
3711
|
+
await runTest(newProvider.id);
|
|
3563
3712
|
} catch {
|
|
3564
3713
|
setError(NETWORK_ERROR_MESSAGE);
|
|
3565
|
-
return;
|
|
3566
3714
|
}
|
|
3567
|
-
if (!res.ok) {
|
|
3568
|
-
setError(await readApiError(res, "Failed to add provider"));
|
|
3569
|
-
return;
|
|
3570
|
-
}
|
|
3571
|
-
const newProvider = await parseJsonResponse(res, ProviderConfigSchema);
|
|
3572
|
-
setShowForm(false);
|
|
3573
|
-
triggerHighlight(newProvider.id);
|
|
3574
|
-
refreshProviders();
|
|
3575
|
-
await runTest(newProvider.id);
|
|
3576
3715
|
})();
|
|
3577
3716
|
}
|
|
3578
3717
|
function handleUpdateProvider(data) {
|
|
3579
3718
|
if (!editingProvider) return;
|
|
3580
3719
|
void (async () => {
|
|
3581
|
-
let res;
|
|
3582
3720
|
try {
|
|
3583
|
-
res = await fetch(`/api/providers/${editingProvider.id}`, {
|
|
3721
|
+
const res = await fetch(`/api/providers/${editingProvider.id}`, {
|
|
3584
3722
|
method: "PUT",
|
|
3585
3723
|
headers: { "Content-Type": "application/json" },
|
|
3586
3724
|
body: JSON.stringify(createProviderPayload(data))
|
|
3587
3725
|
});
|
|
3726
|
+
if (!res.ok) {
|
|
3727
|
+
setError(await readApiError(res, "Failed to update provider"));
|
|
3728
|
+
return;
|
|
3729
|
+
}
|
|
3730
|
+
const updated = await parseJsonResponse(res, ProviderConfigSchema);
|
|
3731
|
+
setEditingProvider(void 0);
|
|
3732
|
+
triggerHighlight(updated.id);
|
|
3733
|
+
refreshProviders();
|
|
3734
|
+
const criticalFieldsChanged = (data.apiKey ?? "") !== (editingProvider.apiKey ?? "") || (data.model ?? "") !== (editingProvider.model ?? "") || (data.anthropicBaseUrl ?? "") !== (editingProvider.anthropicBaseUrl ?? "") || (data.openaiBaseUrl ?? "") !== (editingProvider.openaiBaseUrl ?? "");
|
|
3735
|
+
if (criticalFieldsChanged) {
|
|
3736
|
+
await runTest(updated.id);
|
|
3737
|
+
}
|
|
3588
3738
|
} catch {
|
|
3589
3739
|
setError(NETWORK_ERROR_MESSAGE);
|
|
3590
|
-
return;
|
|
3591
|
-
}
|
|
3592
|
-
if (!res.ok) {
|
|
3593
|
-
setError(await readApiError(res, "Failed to update provider"));
|
|
3594
|
-
return;
|
|
3595
3740
|
}
|
|
3596
|
-
const updated = await parseJsonResponse(res, ProviderConfigSchema);
|
|
3597
|
-
setEditingProvider(void 0);
|
|
3598
|
-
triggerHighlight(updated.id);
|
|
3599
|
-
refreshProviders();
|
|
3600
|
-
await runTest(updated.id);
|
|
3601
3741
|
})();
|
|
3602
3742
|
}
|
|
3603
3743
|
function handleDeleteProvider(providerId) {
|
|
@@ -3766,22 +3906,34 @@ function ProvidersPanel({
|
|
|
3766
3906
|
/* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { className: "size-4" }),
|
|
3767
3907
|
"Add Your First Provider"
|
|
3768
3908
|
] })
|
|
3769
|
-
] }) : /* @__PURE__ */ jsxRuntimeExports.
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3909
|
+
] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [
|
|
3910
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex gap-1 border-b border-border", children: ["all", "personal", "company"].map((tab) => /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3911
|
+
"button",
|
|
3912
|
+
{
|
|
3913
|
+
type: "button",
|
|
3914
|
+
onClick: () => setSourceFilter(tab),
|
|
3915
|
+
className: `px-3 py-2 text-sm font-medium border-b-2 transition-colors ${sourceFilter === tab ? "border-primary text-primary" : "border-transparent text-muted-foreground hover:text-foreground"}`,
|
|
3916
|
+
children: tab === "all" ? "All" : tab === "personal" ? "Personal" : "Company"
|
|
3917
|
+
},
|
|
3918
|
+
tab
|
|
3919
|
+
)) }),
|
|
3920
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: listScrollRef, className: "space-y-3", children: filteredProviders.map((provider) => /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
3921
|
+
ProviderCard,
|
|
3922
|
+
{
|
|
3923
|
+
provider,
|
|
3924
|
+
testResults: testResults[provider.id],
|
|
3925
|
+
isTesting: testingProviders.has(provider.id),
|
|
3926
|
+
testingTimeLeft: testingTimeLeft[provider.id],
|
|
3927
|
+
isHighlighted: provider.id === highlightedProviderId,
|
|
3928
|
+
onEdit: (p) => setEditingProvider(p),
|
|
3929
|
+
onDelete: handleDeleteProvider,
|
|
3930
|
+
onTest: (id) => {
|
|
3931
|
+
void runTest(id);
|
|
3932
|
+
}
|
|
3933
|
+
},
|
|
3934
|
+
provider.id
|
|
3935
|
+
)) })
|
|
3936
|
+
] })
|
|
3785
3937
|
] });
|
|
3786
3938
|
}
|
|
3787
3939
|
async function fetcher(url) {
|
|
@@ -4192,6 +4344,36 @@ function groupContiguousEquals(ops) {
|
|
|
4192
4344
|
}
|
|
4193
4345
|
return out;
|
|
4194
4346
|
}
|
|
4347
|
+
const KIND_VISUAL = {
|
|
4348
|
+
added: {
|
|
4349
|
+
icon: Plus,
|
|
4350
|
+
accent: "text-emerald-600 dark:text-emerald-400",
|
|
4351
|
+
bg: "bg-emerald-500/5 hover:bg-emerald-500/10",
|
|
4352
|
+
border: "border-l-emerald-500",
|
|
4353
|
+
label: "ADDED"
|
|
4354
|
+
},
|
|
4355
|
+
removed: {
|
|
4356
|
+
icon: Minus,
|
|
4357
|
+
accent: "text-rose-600 dark:text-rose-400",
|
|
4358
|
+
bg: "bg-rose-500/5 hover:bg-rose-500/10",
|
|
4359
|
+
border: "border-l-rose-500",
|
|
4360
|
+
label: "REMOVED"
|
|
4361
|
+
},
|
|
4362
|
+
changed: {
|
|
4363
|
+
icon: Pencil,
|
|
4364
|
+
accent: "text-amber-600 dark:text-amber-400",
|
|
4365
|
+
bg: "bg-amber-500/5 hover:bg-amber-500/10",
|
|
4366
|
+
border: "border-l-amber-500",
|
|
4367
|
+
label: "CHANGED"
|
|
4368
|
+
},
|
|
4369
|
+
equal: {
|
|
4370
|
+
icon: Equal,
|
|
4371
|
+
accent: "text-muted-foreground/70",
|
|
4372
|
+
bg: "bg-muted/20 hover:bg-muted/30",
|
|
4373
|
+
border: "border-l-muted-foreground/20",
|
|
4374
|
+
label: "EQUAL"
|
|
4375
|
+
}
|
|
4376
|
+
};
|
|
4195
4377
|
function EqualRunRow({
|
|
4196
4378
|
ops,
|
|
4197
4379
|
expanded,
|
|
@@ -4200,19 +4382,20 @@ function EqualRunRow({
|
|
|
4200
4382
|
const first = ops[0];
|
|
4201
4383
|
const last = ops[ops.length - 1];
|
|
4202
4384
|
if (first === void 0 || last === void 0) {
|
|
4203
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "
|
|
4385
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-muted-foreground/40 text-xs", children: "—" });
|
|
4204
4386
|
}
|
|
4205
4387
|
const firstPath = first.path;
|
|
4206
4388
|
const lastPath = last.path;
|
|
4207
4389
|
const label = ops.length === 1 ? firstPath : `${firstPath} … ${lastPath}`;
|
|
4208
4390
|
const summary = first.value.kind === "array" ? `${ops.length} equal arrays` : first.value.kind === "object" ? `${ops.length} equal objects` : "equal";
|
|
4209
|
-
|
|
4391
|
+
const v = KIND_VISUAL.equal;
|
|
4392
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cn("border-l-4 rounded-sm", v.border, v.bg), children: [
|
|
4210
4393
|
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4211
4394
|
"button",
|
|
4212
4395
|
{
|
|
4213
4396
|
type: "button",
|
|
4214
4397
|
onClick: onToggle,
|
|
4215
|
-
className: "w-full text-left flex items-center gap-2 px-
|
|
4398
|
+
className: "w-full text-left flex items-center gap-2 px-3 py-1.5 text-xs text-muted-foreground cursor-pointer",
|
|
4216
4399
|
children: [
|
|
4217
4400
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4218
4401
|
ChevronRight,
|
|
@@ -4220,7 +4403,9 @@ function EqualRunRow({
|
|
|
4220
4403
|
className: cn("size-3 transition-transform shrink-0", expanded && "rotate-90")
|
|
4221
4404
|
}
|
|
4222
4405
|
),
|
|
4223
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4406
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(v.icon, { className: cn("size-3 shrink-0", v.accent) }),
|
|
4407
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono truncate flex-1", title: `${firstPath} … ${lastPath}`, children: label }),
|
|
4408
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: cn("text-[10px] uppercase tracking-wider shrink-0", v.accent), children: v.label }),
|
|
4224
4409
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground/60 shrink-0", children: [
|
|
4225
4410
|
"(",
|
|
4226
4411
|
summary,
|
|
@@ -4229,37 +4414,252 @@ function EqualRunRow({
|
|
|
4229
4414
|
]
|
|
4230
4415
|
}
|
|
4231
4416
|
),
|
|
4232
|
-
expanded && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ml-5 mt-1 mb-2 space-y-2", children: ops.map((op) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-border/50 rounded p-2 bg-muted/20", children: [
|
|
4417
|
+
expanded && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ml-5 mt-1 mb-2 space-y-2 pr-2", children: ops.map((op) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-border/50 rounded p-2 bg-muted/20", children: [
|
|
4233
4418
|
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-xs text-muted-foreground mb-1", children: op.path }),
|
|
4234
4419
|
/* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: nodeToJsonString(op.value), defaultExpandDepth: 2 })
|
|
4235
4420
|
] }, op.path)) })
|
|
4236
4421
|
] });
|
|
4237
4422
|
}
|
|
4238
|
-
function
|
|
4423
|
+
function UnifiedOpRow({
|
|
4239
4424
|
op,
|
|
4240
|
-
|
|
4425
|
+
idx,
|
|
4426
|
+
copiedPath,
|
|
4427
|
+
onCopyPath,
|
|
4428
|
+
expanded,
|
|
4429
|
+
onToggle
|
|
4241
4430
|
}) {
|
|
4242
|
-
const
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
previewNode(op.
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4431
|
+
const v = KIND_VISUAL[op.kind];
|
|
4432
|
+
const Icon2 = v.icon;
|
|
4433
|
+
const isExpandable2 = op.kind === "added" || op.kind === "removed" ? op.value.kind === "object" || op.value.kind === "array" : op.kind === "changed" ? op.left.kind === "object" || op.left.kind === "array" || op.right.kind === "object" || op.right.kind === "array" : false;
|
|
4434
|
+
const preview = op.kind === "changed" ? [
|
|
4435
|
+
{
|
|
4436
|
+
text: previewNode(op.left, 400),
|
|
4437
|
+
tone: "text-rose-700 dark:text-rose-300 line-through"
|
|
4438
|
+
},
|
|
4439
|
+
{ text: previewNode(op.right, 400), tone: "text-emerald-700 dark:text-emerald-300" }
|
|
4440
|
+
] : op.kind === "removed" ? [
|
|
4441
|
+
{
|
|
4442
|
+
text: previewNode(op.value, 400),
|
|
4443
|
+
tone: "text-rose-700 dark:text-rose-300 line-through"
|
|
4444
|
+
}
|
|
4445
|
+
] : op.kind === "added" ? [{ text: previewNode(op.value, 400), tone: "text-emerald-700 dark:text-emerald-300" }] : [{ text: previewNode(op.value, 400), tone: "text-muted-foreground" }];
|
|
4446
|
+
const justCopied = copiedPath === op.path && op.path !== "";
|
|
4447
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4448
|
+
"div",
|
|
4449
|
+
{
|
|
4450
|
+
"data-diff-idx": idx,
|
|
4451
|
+
"data-diff-kind": op.kind,
|
|
4452
|
+
className: cn("border-l-4 rounded-sm px-3 py-2 my-0.5 transition-colors", v.border, v.bg),
|
|
4453
|
+
children: [
|
|
4454
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4455
|
+
"button",
|
|
4456
|
+
{
|
|
4457
|
+
type: "button",
|
|
4458
|
+
onClick: onToggle,
|
|
4459
|
+
disabled: !isExpandable2,
|
|
4460
|
+
className: cn(
|
|
4461
|
+
"w-full flex items-center gap-2 text-xs text-left rounded-sm",
|
|
4462
|
+
isExpandable2 ? "cursor-pointer" : "cursor-default"
|
|
4463
|
+
),
|
|
4464
|
+
"aria-expanded": isExpandable2 ? expanded : void 0,
|
|
4465
|
+
"aria-label": isExpandable2 ? expanded ? `Collapse ${op.path || "root"}` : `Expand ${op.path || "root"}` : void 0,
|
|
4466
|
+
children: [
|
|
4467
|
+
isExpandable2 ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4468
|
+
ChevronRight,
|
|
4469
|
+
{
|
|
4470
|
+
className: cn(
|
|
4471
|
+
"size-3 shrink-0 transition-transform",
|
|
4472
|
+
v.accent,
|
|
4473
|
+
expanded && "rotate-90"
|
|
4474
|
+
)
|
|
4475
|
+
}
|
|
4476
|
+
) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "size-3 shrink-0", "aria-hidden": "true" }),
|
|
4477
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Icon2, { className: cn("size-3.5 shrink-0", v.accent), strokeWidth: 2.5 }),
|
|
4478
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono truncate flex-1 min-w-0", title: op.path || "(root)", children: op.path === "" ? "(root)" : op.path }),
|
|
4479
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4480
|
+
"span",
|
|
4481
|
+
{
|
|
4482
|
+
className: cn(
|
|
4483
|
+
"text-[9px] font-bold uppercase tracking-wider shrink-0 px-1.5 py-0.5 rounded",
|
|
4484
|
+
v.accent,
|
|
4485
|
+
op.kind === "equal" ? "bg-muted/40" : "bg-background/60"
|
|
4486
|
+
),
|
|
4487
|
+
children: v.label
|
|
4488
|
+
}
|
|
4489
|
+
),
|
|
4490
|
+
op.path !== "" && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4491
|
+
"span",
|
|
4492
|
+
{
|
|
4493
|
+
role: "button",
|
|
4494
|
+
tabIndex: 0,
|
|
4495
|
+
onClick: (e) => {
|
|
4496
|
+
e.stopPropagation();
|
|
4497
|
+
onCopyPath(op.path);
|
|
4498
|
+
},
|
|
4499
|
+
onKeyDown: (e) => {
|
|
4500
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
4501
|
+
e.stopPropagation();
|
|
4502
|
+
e.preventDefault();
|
|
4503
|
+
onCopyPath(op.path);
|
|
4504
|
+
}
|
|
4505
|
+
},
|
|
4506
|
+
className: cn(
|
|
4507
|
+
"shrink-0 p-1 rounded transition-colors cursor-pointer inline-flex items-center justify-center",
|
|
4508
|
+
justCopied ? "text-emerald-500" : "text-muted-foreground/50 hover:text-foreground hover:bg-muted"
|
|
4509
|
+
),
|
|
4510
|
+
"aria-label": justCopied ? "Copied" : "Copy path",
|
|
4511
|
+
title: justCopied ? "Copied!" : "Copy path",
|
|
4512
|
+
children: justCopied ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "size-3" })
|
|
4513
|
+
}
|
|
4514
|
+
)
|
|
4515
|
+
]
|
|
4516
|
+
}
|
|
4517
|
+
),
|
|
4518
|
+
preview.map((p, i) => (
|
|
4519
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: preview list is rebuilt on every render and is positional
|
|
4520
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: cn("font-mono text-xs mt-1 break-all pl-5", p.tone), children: p.text }, i)
|
|
4521
|
+
)),
|
|
4522
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4523
|
+
"div",
|
|
4524
|
+
{
|
|
4525
|
+
className: "overflow-hidden transition-all duration-200",
|
|
4526
|
+
style: { maxHeight: expanded && isExpandable2 ? "2000px" : "0" },
|
|
4527
|
+
"aria-hidden": !expanded,
|
|
4528
|
+
children: expanded && isExpandable2 && op.kind !== "equal" ? /* @__PURE__ */ jsxRuntimeExports.jsx(ExpandedSubtree, { op }) : null
|
|
4529
|
+
}
|
|
4530
|
+
)
|
|
4531
|
+
]
|
|
4532
|
+
}
|
|
4533
|
+
);
|
|
4253
4534
|
}
|
|
4254
|
-
function
|
|
4255
|
-
|
|
4256
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4535
|
+
function ExpandedSubtree({ op }) {
|
|
4536
|
+
if (op.kind === "added" || op.kind === "removed") {
|
|
4537
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pl-5 mt-2 border border-border/50 rounded p-2 bg-muted/20", children: /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: nodeToJsonString(op.value), defaultExpandDepth: 2 }) });
|
|
4538
|
+
}
|
|
4539
|
+
const leftIsStructured = op.left.kind === "object" || op.left.kind === "array";
|
|
4540
|
+
const rightIsStructured = op.right.kind === "object" || op.right.kind === "array";
|
|
4541
|
+
if (!leftIsStructured && !rightIsStructured) {
|
|
4542
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pl-5 mt-2 text-xs text-muted-foreground/70 italic", children: "Primitive values are shown inline above." });
|
|
4543
|
+
}
|
|
4544
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pl-5 mt-2 grid grid-cols-1 md:grid-cols-2 gap-2", children: [
|
|
4545
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-rose-500/30 rounded p-2 bg-rose-500/5", children: [
|
|
4546
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] uppercase tracking-wider text-rose-500 mb-1", children: "Old" }),
|
|
4547
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: nodeToJsonString(op.left), defaultExpandDepth: 2 })
|
|
4548
|
+
] }),
|
|
4549
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-emerald-500/30 rounded p-2 bg-emerald-500/5", children: [
|
|
4550
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] uppercase tracking-wider text-emerald-500 mb-1", children: "New" }),
|
|
4551
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: nodeToJsonString(op.right), defaultExpandDepth: 2 })
|
|
4260
4552
|
] })
|
|
4261
4553
|
] });
|
|
4262
4554
|
}
|
|
4555
|
+
function SummaryChips({
|
|
4556
|
+
counts,
|
|
4557
|
+
onJumpTo
|
|
4558
|
+
}) {
|
|
4559
|
+
const total = counts.added + counts.removed + counts.changed;
|
|
4560
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-2 border-b border-border bg-muted/20 flex items-center gap-2 text-xs flex-wrap", children: [
|
|
4561
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground font-medium", children: [
|
|
4562
|
+
total,
|
|
4563
|
+
" ",
|
|
4564
|
+
total === 1 ? "change" : "changes"
|
|
4565
|
+
] }),
|
|
4566
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4567
|
+
"button",
|
|
4568
|
+
{
|
|
4569
|
+
type: "button",
|
|
4570
|
+
onClick: () => onJumpTo("removed"),
|
|
4571
|
+
disabled: counts.removed === 0,
|
|
4572
|
+
className: cn(
|
|
4573
|
+
"inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",
|
|
4574
|
+
counts.removed > 0 ? "border-rose-500/40 text-rose-600 dark:text-rose-400 bg-rose-500/10 hover:bg-rose-500/20" : "border-border text-muted-foreground/40 cursor-not-allowed"
|
|
4575
|
+
),
|
|
4576
|
+
title: counts.removed > 0 ? "Jump to first removed" : "No removals",
|
|
4577
|
+
children: [
|
|
4578
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Minus, { className: "size-3" }),
|
|
4579
|
+
counts.removed,
|
|
4580
|
+
" removed"
|
|
4581
|
+
]
|
|
4582
|
+
}
|
|
4583
|
+
),
|
|
4584
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4585
|
+
"button",
|
|
4586
|
+
{
|
|
4587
|
+
type: "button",
|
|
4588
|
+
onClick: () => onJumpTo("added"),
|
|
4589
|
+
disabled: counts.added === 0,
|
|
4590
|
+
className: cn(
|
|
4591
|
+
"inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",
|
|
4592
|
+
counts.added > 0 ? "border-emerald-500/40 text-emerald-600 dark:text-emerald-400 bg-emerald-500/10 hover:bg-emerald-500/20" : "border-border text-muted-foreground/40 cursor-not-allowed"
|
|
4593
|
+
),
|
|
4594
|
+
title: counts.added > 0 ? "Jump to first added" : "No additions",
|
|
4595
|
+
children: [
|
|
4596
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { className: "size-3" }),
|
|
4597
|
+
counts.added,
|
|
4598
|
+
" added"
|
|
4599
|
+
]
|
|
4600
|
+
}
|
|
4601
|
+
),
|
|
4602
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4603
|
+
"button",
|
|
4604
|
+
{
|
|
4605
|
+
type: "button",
|
|
4606
|
+
onClick: () => onJumpTo("changed"),
|
|
4607
|
+
disabled: counts.changed === 0,
|
|
4608
|
+
className: cn(
|
|
4609
|
+
"inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",
|
|
4610
|
+
counts.changed > 0 ? "border-amber-500/40 text-amber-600 dark:text-amber-400 bg-amber-500/10 hover:bg-amber-500/20" : "border-border text-muted-foreground/40 cursor-not-allowed"
|
|
4611
|
+
),
|
|
4612
|
+
title: counts.changed > 0 ? "Jump to first changed" : "No changes",
|
|
4613
|
+
children: [
|
|
4614
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Pencil, { className: "size-3" }),
|
|
4615
|
+
counts.changed,
|
|
4616
|
+
" changed"
|
|
4617
|
+
]
|
|
4618
|
+
}
|
|
4619
|
+
)
|
|
4620
|
+
] });
|
|
4621
|
+
}
|
|
4622
|
+
function ModeToggle({
|
|
4623
|
+
mode,
|
|
4624
|
+
onChange
|
|
4625
|
+
}) {
|
|
4626
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "inline-flex rounded-md border border-border overflow-hidden", children: [
|
|
4627
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4628
|
+
"button",
|
|
4629
|
+
{
|
|
4630
|
+
type: "button",
|
|
4631
|
+
onClick: () => onChange("unified"),
|
|
4632
|
+
"aria-pressed": mode === "unified",
|
|
4633
|
+
className: cn(
|
|
4634
|
+
"flex items-center gap-1 px-2 py-1 text-xs transition-colors cursor-pointer",
|
|
4635
|
+
mode === "unified" ? "bg-muted text-foreground" : "hover:bg-muted/50 text-muted-foreground"
|
|
4636
|
+
),
|
|
4637
|
+
title: "Unified view (single column, emphasized diffs)",
|
|
4638
|
+
children: [
|
|
4639
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Rows3, { className: "size-3" }),
|
|
4640
|
+
"Unified"
|
|
4641
|
+
]
|
|
4642
|
+
}
|
|
4643
|
+
),
|
|
4644
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4645
|
+
"button",
|
|
4646
|
+
{
|
|
4647
|
+
type: "button",
|
|
4648
|
+
onClick: () => onChange("split"),
|
|
4649
|
+
"aria-pressed": mode === "split",
|
|
4650
|
+
className: cn(
|
|
4651
|
+
"flex items-center gap-1 px-2 py-1 text-xs transition-colors border-l border-border cursor-pointer",
|
|
4652
|
+
mode === "split" ? "bg-muted text-foreground" : "hover:bg-muted/50 text-muted-foreground"
|
|
4653
|
+
),
|
|
4654
|
+
title: "Split view (path | left | right)",
|
|
4655
|
+
children: [
|
|
4656
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Columns2, { className: "size-3" }),
|
|
4657
|
+
"Split"
|
|
4658
|
+
]
|
|
4659
|
+
}
|
|
4660
|
+
)
|
|
4661
|
+
] });
|
|
4662
|
+
}
|
|
4263
4663
|
function SideSummary({ log, side }) {
|
|
4264
4664
|
const conversationId = getConversationId(log);
|
|
4265
4665
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0 space-y-1 text-xs", children: [
|
|
@@ -4305,6 +4705,26 @@ function CompareDrawer({ left, right, onClose }) {
|
|
|
4305
4705
|
return diffTrees(l, r);
|
|
4306
4706
|
}, [left.rawRequestBody, right.rawRequestBody]);
|
|
4307
4707
|
const grouped = reactExports.useMemo(() => groupContiguousEquals(ops), [ops]);
|
|
4708
|
+
const counts = reactExports.useMemo(() => {
|
|
4709
|
+
let added = 0;
|
|
4710
|
+
let removed = 0;
|
|
4711
|
+
let changed = 0;
|
|
4712
|
+
for (const g of grouped) {
|
|
4713
|
+
if (g.kind !== "single") continue;
|
|
4714
|
+
switch (g.op.kind) {
|
|
4715
|
+
case "added":
|
|
4716
|
+
added++;
|
|
4717
|
+
break;
|
|
4718
|
+
case "removed":
|
|
4719
|
+
removed++;
|
|
4720
|
+
break;
|
|
4721
|
+
case "changed":
|
|
4722
|
+
changed++;
|
|
4723
|
+
break;
|
|
4724
|
+
}
|
|
4725
|
+
}
|
|
4726
|
+
return { added, removed, changed };
|
|
4727
|
+
}, [grouped]);
|
|
4308
4728
|
const [expandedRuns, setExpandedRuns] = reactExports.useState(/* @__PURE__ */ new Set());
|
|
4309
4729
|
const toggleRun = (idx) => {
|
|
4310
4730
|
setExpandedRuns((prev) => {
|
|
@@ -4314,6 +4734,44 @@ function CompareDrawer({ left, right, onClose }) {
|
|
|
4314
4734
|
return next;
|
|
4315
4735
|
});
|
|
4316
4736
|
};
|
|
4737
|
+
const [expandedRows, setExpandedRows] = reactExports.useState(/* @__PURE__ */ new Set());
|
|
4738
|
+
const toggleRow = (idx) => {
|
|
4739
|
+
setExpandedRows((prev) => {
|
|
4740
|
+
const next = new Set(prev);
|
|
4741
|
+
if (next.has(idx)) next.delete(idx);
|
|
4742
|
+
else next.add(idx);
|
|
4743
|
+
return next;
|
|
4744
|
+
});
|
|
4745
|
+
};
|
|
4746
|
+
reactExports.useEffect(() => {
|
|
4747
|
+
setExpandedRows(/* @__PURE__ */ new Set());
|
|
4748
|
+
}, [left.id, right.id]);
|
|
4749
|
+
const [mode, setMode] = reactExports.useState("unified");
|
|
4750
|
+
const bodyRef = reactExports.useRef(null);
|
|
4751
|
+
const [copiedPath, setCopiedPath] = reactExports.useState(null);
|
|
4752
|
+
const copyResetTimer = reactExports.useRef(null);
|
|
4753
|
+
const onCopyPath = (path) => {
|
|
4754
|
+
void window.navigator.clipboard.writeText(path).then(() => {
|
|
4755
|
+
setCopiedPath(path);
|
|
4756
|
+
if (copyResetTimer.current !== null) clearTimeout(copyResetTimer.current);
|
|
4757
|
+
copyResetTimer.current = setTimeout(() => setCopiedPath(null), 1500);
|
|
4758
|
+
});
|
|
4759
|
+
};
|
|
4760
|
+
reactExports.useEffect(() => {
|
|
4761
|
+
return () => {
|
|
4762
|
+
if (copyResetTimer.current !== null) clearTimeout(copyResetTimer.current);
|
|
4763
|
+
};
|
|
4764
|
+
}, []);
|
|
4765
|
+
const jumpToKind = (kind) => {
|
|
4766
|
+
const idx = grouped.findIndex((g) => g.kind === "single" && g.op.kind === kind);
|
|
4767
|
+
if (idx === -1) return;
|
|
4768
|
+
const root = bodyRef.current;
|
|
4769
|
+
if (root === null) return;
|
|
4770
|
+
const el = root.querySelector(`[data-diff-idx="${idx}"]`);
|
|
4771
|
+
if (el !== null) {
|
|
4772
|
+
el.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
4773
|
+
}
|
|
4774
|
+
};
|
|
4317
4775
|
reactExports.useEffect(() => {
|
|
4318
4776
|
const onKey = (e) => {
|
|
4319
4777
|
if (e.key === "Escape") onClose();
|
|
@@ -4361,33 +4819,24 @@ function CompareDrawer({ left, right, onClose }) {
|
|
|
4361
4819
|
/* @__PURE__ */ jsxRuntimeExports.jsx(SideSummary, { log: left, side: "left" }),
|
|
4362
4820
|
/* @__PURE__ */ jsxRuntimeExports.jsx(SideSummary, { log: right, side: "right" })
|
|
4363
4821
|
] }),
|
|
4364
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4822
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [
|
|
4823
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(ModeToggle, { mode, onChange: setMode }),
|
|
4824
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4825
|
+
"button",
|
|
4826
|
+
{
|
|
4827
|
+
type: "button",
|
|
4828
|
+
onClick: onClose,
|
|
4829
|
+
"aria-label": "Close",
|
|
4830
|
+
className: "p-1 rounded text-muted-foreground hover:text-foreground hover:bg-muted cursor-pointer",
|
|
4831
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { className: "size-4" })
|
|
4832
|
+
}
|
|
4833
|
+
)
|
|
4834
|
+
] })
|
|
4374
4835
|
] }),
|
|
4375
4836
|
!sameSession && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 py-1.5 text-xs text-amber-400 bg-amber-500/10 border-b border-border", children: "Heads up: the two selected logs are from different sessions." }),
|
|
4376
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 overflow-y-auto
|
|
4377
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
4378
|
-
|
|
4379
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
|
|
4380
|
-
"Left (Log #",
|
|
4381
|
-
left.id,
|
|
4382
|
-
")"
|
|
4383
|
-
] }),
|
|
4384
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
|
|
4385
|
-
"Right (Log #",
|
|
4386
|
-
right.id,
|
|
4387
|
-
")"
|
|
4388
|
-
] })
|
|
4389
|
-
] }),
|
|
4390
|
-
grouped.map((g, i) => {
|
|
4837
|
+
allEqual ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 overflow-y-auto flex items-center justify-center text-muted-foreground text-sm", children: "The two Request payloads are identical." }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
4838
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(SummaryChips, { counts, onJumpTo: jumpToKind }),
|
|
4839
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: bodyRef, className: "flex-1 min-h-0 overflow-y-auto", children: mode === "unified" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 py-2 space-y-0.5", children: grouped.map((g, i) => {
|
|
4391
4840
|
if (g.kind === "equal-run") {
|
|
4392
4841
|
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4393
4842
|
EqualRunRow,
|
|
@@ -4400,29 +4849,20 @@ function CompareDrawer({ left, right, onClose }) {
|
|
|
4400
4849
|
);
|
|
4401
4850
|
}
|
|
4402
4851
|
const op = g.op;
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
if (op.kind === "added") {
|
|
4418
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx(AddOrRemoveRow, { op, kind: "added" }, `a${i}`);
|
|
4419
|
-
}
|
|
4420
|
-
if (op.kind === "removed") {
|
|
4421
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx(AddOrRemoveRow, { op, kind: "removed" }, `r${i}`);
|
|
4422
|
-
}
|
|
4423
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx(ChangedRow, { op }, `c${i}`);
|
|
4424
|
-
})
|
|
4425
|
-
] }) })
|
|
4852
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
4853
|
+
UnifiedOpRow,
|
|
4854
|
+
{
|
|
4855
|
+
op,
|
|
4856
|
+
idx: i,
|
|
4857
|
+
copiedPath,
|
|
4858
|
+
onCopyPath,
|
|
4859
|
+
expanded: expandedRows.has(i),
|
|
4860
|
+
onToggle: () => toggleRow(i)
|
|
4861
|
+
},
|
|
4862
|
+
`o${i}`
|
|
4863
|
+
);
|
|
4864
|
+
}) }) : /* @__PURE__ */ jsxRuntimeExports.jsx(SplitBody, { grouped, left, right }) })
|
|
4865
|
+
] })
|
|
4426
4866
|
]
|
|
4427
4867
|
}
|
|
4428
4868
|
)
|
|
@@ -4430,6 +4870,103 @@ function CompareDrawer({ left, right, onClose }) {
|
|
|
4430
4870
|
}
|
|
4431
4871
|
);
|
|
4432
4872
|
}
|
|
4873
|
+
function SplitBody({
|
|
4874
|
+
grouped,
|
|
4875
|
+
left,
|
|
4876
|
+
right
|
|
4877
|
+
}) {
|
|
4878
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-[200px_1fr_1fr] gap-x-2 gap-y-0.5 px-3 py-2 text-xs", children: [
|
|
4879
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-[200px_1fr_1fr] gap-x-2 col-span-3 pb-2 mb-2 border-b border-border text-[10px] uppercase tracking-wider text-muted-foreground", children: [
|
|
4880
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Path" }),
|
|
4881
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
|
|
4882
|
+
"Left (Log #",
|
|
4883
|
+
left.id,
|
|
4884
|
+
")"
|
|
4885
|
+
] }),
|
|
4886
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
|
|
4887
|
+
"Right (Log #",
|
|
4888
|
+
right.id,
|
|
4889
|
+
")"
|
|
4890
|
+
] })
|
|
4891
|
+
] }),
|
|
4892
|
+
grouped.map((g, i) => {
|
|
4893
|
+
if (g.kind === "equal-run") {
|
|
4894
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4895
|
+
"div",
|
|
4896
|
+
{
|
|
4897
|
+
className: "col-span-3 px-2 py-1 text-xs text-muted-foreground/60",
|
|
4898
|
+
children: [
|
|
4899
|
+
g.ops.length,
|
|
4900
|
+
" equal siblings collapsed — switch to Unified to expand"
|
|
4901
|
+
]
|
|
4902
|
+
},
|
|
4903
|
+
i
|
|
4904
|
+
);
|
|
4905
|
+
}
|
|
4906
|
+
const op = g.op;
|
|
4907
|
+
if (op.kind === "equal") {
|
|
4908
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4909
|
+
"div",
|
|
4910
|
+
{
|
|
4911
|
+
className: "col-span-3 grid grid-cols-[200px_1fr_1fr] gap-x-2 px-2 py-0.5 text-muted-foreground",
|
|
4912
|
+
children: [
|
|
4913
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-xs truncate", title: op.path, children: op.path }),
|
|
4914
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-xs break-all opacity-60", children: previewNode(op.value, 200) }),
|
|
4915
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-xs break-all opacity-60", children: previewNode(op.value, 200) })
|
|
4916
|
+
]
|
|
4917
|
+
},
|
|
4918
|
+
i
|
|
4919
|
+
);
|
|
4920
|
+
}
|
|
4921
|
+
if (op.kind === "added") {
|
|
4922
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4923
|
+
"div",
|
|
4924
|
+
{
|
|
4925
|
+
className: "col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-emerald-400/70 bg-emerald-500/5",
|
|
4926
|
+
children: [
|
|
4927
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-xs text-muted-foreground mb-0.5", children: op.path }),
|
|
4928
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "font-mono break-all text-emerald-300/90", children: [
|
|
4929
|
+
"+ ",
|
|
4930
|
+
previewNode(op.value, 400)
|
|
4931
|
+
] })
|
|
4932
|
+
]
|
|
4933
|
+
},
|
|
4934
|
+
i
|
|
4935
|
+
);
|
|
4936
|
+
}
|
|
4937
|
+
if (op.kind === "removed") {
|
|
4938
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4939
|
+
"div",
|
|
4940
|
+
{
|
|
4941
|
+
className: "col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-rose-400/70 bg-rose-500/5",
|
|
4942
|
+
children: [
|
|
4943
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-xs text-muted-foreground mb-0.5", children: op.path }),
|
|
4944
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "font-mono break-all text-rose-300/90 line-through", children: [
|
|
4945
|
+
"− ",
|
|
4946
|
+
previewNode(op.value, 400)
|
|
4947
|
+
] })
|
|
4948
|
+
]
|
|
4949
|
+
},
|
|
4950
|
+
i
|
|
4951
|
+
);
|
|
4952
|
+
}
|
|
4953
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
4954
|
+
"div",
|
|
4955
|
+
{
|
|
4956
|
+
className: "col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-amber-400/70 bg-amber-500/5",
|
|
4957
|
+
children: [
|
|
4958
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-xs text-muted-foreground mb-1", children: op.path }),
|
|
4959
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
|
|
4960
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-rose-300/90 break-all line-through", children: previewNode(op.left, 400) }),
|
|
4961
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-emerald-300/90 break-all", children: previewNode(op.right, 400) })
|
|
4962
|
+
] })
|
|
4963
|
+
]
|
|
4964
|
+
},
|
|
4965
|
+
i
|
|
4966
|
+
);
|
|
4967
|
+
})
|
|
4968
|
+
] });
|
|
4969
|
+
}
|
|
4433
4970
|
function truncateSessionId(id) {
|
|
4434
4971
|
if (id.length <= 30) return id;
|
|
4435
4972
|
return id.slice(0, 12) + "…" + id.slice(-12);
|