7 ? provider.color : provider.color + "15"}` }}
+ style={{
+ backgroundColor: `${provider.color?.length > 7 ? provider.color : provider.color + "15"}`,
+ }}
>
- {imgError ? (
-
- {provider.textIcon || provider.id.slice(0, 2).toUpperCase()}
-
- ) : (
-
setImgError(true)}
- />
- )}
+
-
- {provider.name}
-
+
{provider.name}
{allDisabled ? (
- pause_circle
+
+ pause_circle
+
Disabled
) : (
<>
{getStatusDisplay(connected, error, errorCode)}
- {errorTime && {errorTime}}
+ {errorTime && (
+ {errorTime}
+ )}
>
)}
@@ -497,7 +564,7 @@ function ProviderCard({ providerId, provider, stats, authType, onToggle }) {
{ }}
+ onChange={() => {}}
title={allDisabled ? "Enable provider" : "Disable provider"}
/>
@@ -527,11 +594,18 @@ ProviderCard.propTypes = {
onToggle: PropTypes.func,
};
-function ApiKeyProviderCard({ providerId, provider, stats, authType, onToggle }) {
+function ApiKeyProviderCard({
+ providerId,
+ provider,
+ stats,
+ authType,
+ onToggle,
+}) {
const { connected, error, errorCode, errorTime, allDisabled } = stats;
const isCompatible = providerId.startsWith(OPENAI_COMPATIBLE_PREFIX);
- const isAnthropicCompatible = providerId.startsWith(ANTHROPIC_COMPATIBLE_PREFIX);
- const [imgError, setImgError] = useState(false);
+ const isAnthropicCompatible = providerId.startsWith(
+ ANTHROPIC_COMPATIBLE_PREFIX,
+ );
const dotColors = {
free: "bg-green-500",
@@ -539,10 +613,18 @@ function ApiKeyProviderCard({ providerId, provider, stats, authType, onToggle })
apikey: "bg-amber-500",
compatible: "bg-orange-500",
};
- const dotLabels = { free: "Free", oauth: "OAuth", apikey: "API Key", compatible: "Compatible" };
+ const dotLabels = {
+ free: "Free",
+ oauth: "OAuth",
+ apikey: "API Key",
+ compatible: "Compatible",
+ };
const getIconPath = () => {
- if (isCompatible) return provider.apiType === "responses" ? "/providers/oai-r.png" : "/providers/oai-cc.png";
+ if (isCompatible)
+ return provider.apiType === "responses"
+ ? "/providers/oai-r.png"
+ : "/providers/oai-cc.png";
if (isAnthropicCompatible) return "/providers/anthropic-m.png";
return `/providers/${provider.id}.png`;
};
@@ -557,33 +639,30 @@ function ApiKeyProviderCard({ providerId, provider, stats, authType, onToggle })
7 ? provider.color : provider.color + "15"}` }}
+ style={{
+ backgroundColor: `${provider.color?.length > 7 ? provider.color : provider.color + "15"}`,
+ }}
>
- {imgError ? (
-
- {provider.textIcon || provider.id.slice(0, 2).toUpperCase()}
-
- ) : (
-
setImgError(true)}
- />
- )}
+
-
- {provider.name}
-
+
{provider.name}
{allDisabled ? (
- pause_circle
+
+ pause_circle
+
Disabled
@@ -592,13 +671,19 @@ function ApiKeyProviderCard({ providerId, provider, stats, authType, onToggle })
{getStatusDisplay(connected, error, errorCode)}
{isCompatible && (
- {provider.apiType === "responses" ? "Responses" : "Chat"}
+ {provider.apiType === "responses"
+ ? "Responses"
+ : "Chat"}
)}
{isAnthropicCompatible && (
- Messages
+
+ Messages
+
+ )}
+ {errorTime && (
+ {errorTime}
)}
- {errorTime && {errorTime}}
>
)}
@@ -617,7 +702,7 @@ function ApiKeyProviderCard({ providerId, provider, stats, authType, onToggle })
{ }}
+ onChange={() => {}}
title={allDisabled ? "Enable provider" : "Disable provider"}
/>
@@ -672,7 +757,12 @@ function AddOpenAICompatibleModal({ isOpen, onClose, onCreated }) {
}, [formData.apiType]);
const handleSubmit = async () => {
- if (!formData.name.trim() || !formData.prefix.trim() || !formData.baseUrl.trim()) return;
+ if (
+ !formData.name.trim() ||
+ !formData.prefix.trim() ||
+ !formData.baseUrl.trim()
+ )
+ return;
setSubmitting(true);
try {
const res = await fetch("/api/provider-nodes", {
@@ -689,7 +779,12 @@ function AddOpenAICompatibleModal({ isOpen, onClose, onCreated }) {
const data = await res.json();
if (res.ok) {
onCreated(data.node);
- setFormData({ name: "", prefix: "", apiType: "chat", baseUrl: "https://api.openai.com/v1" });
+ setFormData({
+ name: "",
+ prefix: "",
+ apiType: "chat",
+ baseUrl: "https://api.openai.com/v1",
+ });
setCheckKey("");
setValidationResult(null);
}
@@ -710,7 +805,7 @@ function AddOpenAICompatibleModal({ isOpen, onClose, onCreated }) {
baseUrl: formData.baseUrl,
apiKey: checkKey,
type: "openai-compatible",
- modelId: checkModelId.trim() || undefined
+ modelId: checkModelId.trim() || undefined,
}),
});
const data = await res.json();
@@ -731,7 +826,11 @@ function AddOpenAICompatibleModal({ isOpen, onClose, onCreated }) {
return (
<>
Valid
- {method === "chat" &&
(via inference test)}
+ {method === "chat" && (
+
+ (via inference test)
+
+ )}
>
);
}
@@ -764,12 +863,16 @@ function AddOpenAICompatibleModal({ isOpen, onClose, onCreated }) {
label="API Type"
options={apiTypeOptions}
value={formData.apiType}
- onChange={(e) => setFormData({ ...formData, apiType: e.target.value })}
+ onChange={(e) =>
+ setFormData({ ...formData, apiType: e.target.value })
+ }
/>
setFormData({ ...formData, baseUrl: e.target.value })}
+ onChange={(e) =>
+ setFormData({ ...formData, baseUrl: e.target.value })
+ }
placeholder="https://api.openai.com/v1"
hint="Use the base URL (ending in /v1) for your OpenAI-compatible API."
/>
@@ -787,16 +890,31 @@ function AddOpenAICompatibleModal({ isOpen, onClose, onCreated }) {
hint="If provider lacks /models endpoint, enter a model ID to validate via chat/completions instead."
/>
-
-
+
{submitting ? "Creating..." : "Create"}
- Cancel
+
+ Cancel
+
@@ -830,7 +948,12 @@ function AddAnthropicCompatibleModal({ isOpen, onClose, onCreated }) {
}, [isOpen]);
const handleSubmit = async () => {
- if (!formData.name.trim() || !formData.prefix.trim() || !formData.baseUrl.trim()) return;
+ if (
+ !formData.name.trim() ||
+ !formData.prefix.trim() ||
+ !formData.baseUrl.trim()
+ )
+ return;
setSubmitting(true);
try {
const res = await fetch("/api/provider-nodes", {
@@ -846,7 +969,11 @@ function AddAnthropicCompatibleModal({ isOpen, onClose, onCreated }) {
const data = await res.json();
if (res.ok) {
onCreated(data.node);
- setFormData({ name: "", prefix: "", baseUrl: "https://api.anthropic.com/v1" });
+ setFormData({
+ name: "",
+ prefix: "",
+ baseUrl: "https://api.anthropic.com/v1",
+ });
setCheckKey("");
setValidationResult(null);
}
@@ -867,7 +994,7 @@ function AddAnthropicCompatibleModal({ isOpen, onClose, onCreated }) {
baseUrl: formData.baseUrl,
apiKey: checkKey,
type: "anthropic-compatible",
- modelId: checkModelId.trim() || undefined
+ modelId: checkModelId.trim() || undefined,
}),
});
const data = await res.json();
@@ -888,7 +1015,11 @@ function AddAnthropicCompatibleModal({ isOpen, onClose, onCreated }) {
return (
<>
Valid
- {method === "chat" &&
(via inference test)}
+ {method === "chat" && (
+
+ (via inference test)
+
+ )}
>
);
}
@@ -920,7 +1051,9 @@ function AddAnthropicCompatibleModal({ isOpen, onClose, onCreated }) {
setFormData({ ...formData, baseUrl: e.target.value })}
+ onChange={(e) =>
+ setFormData({ ...formData, baseUrl: e.target.value })
+ }
placeholder="https://api.anthropic.com/v1"
hint="Use the base URL (ending in /v1) for your Anthropic-compatible API. The system will append /messages."
/>
@@ -938,16 +1071,31 @@ function AddAnthropicCompatibleModal({ isOpen, onClose, onCreated }) {
hint="If provider lacks /models endpoint, enter a model ID to validate via chat/completions instead."
/>
-
+
{validating ? "Checking..." : "Check"}
{renderValidationResult()}
-
+
{submitting ? "Creating..." : "Create"}
- Cancel
+
+ Cancel
+
@@ -964,7 +1112,9 @@ function ProviderTestResultsView({ results }) {
if (results.error && !results.results) {
return (
@@ -987,7 +1144,9 @@ function ProviderTestResultsView({ results }) {
{summary.failed} failed
)}
- {summary.total} tested
+
+ {summary.total} tested
+
)}
{items.map((r, i) => (
@@ -995,7 +1154,9 @@ function ProviderTestResultsView({ results }) {
key={r.connectionId || i}
className="flex items-center gap-2 text-xs px-3 py-2 rounded-lg bg-black/[0.03] dark:bg-white/[0.03]"
>
-