feat(providers): add provider icons to dashboard
- Add provider logo icons for all supported providers - Update provider detail and list pages to display icons with text fallback - Target: providers UI
BIN
public/providers/anthropic.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/providers/gemini.png
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
public/providers/glm.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/providers/kimi.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
public/providers/minimax.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/providers/oai-cc.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
public/providers/oai-r.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
public/providers/openai.png
Normal file
|
After Width: | Height: | Size: 879 B |
BIN
public/providers/openrouter.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
@@ -23,6 +23,7 @@ export default function ProviderDetailPage() {
|
|||||||
const [showEditNodeModal, setShowEditNodeModal] = useState(false);
|
const [showEditNodeModal, setShowEditNodeModal] = useState(false);
|
||||||
const [selectedConnection, setSelectedConnection] = useState(null);
|
const [selectedConnection, setSelectedConnection] = useState(null);
|
||||||
const [modelAliases, setModelAliases] = useState({});
|
const [modelAliases, setModelAliases] = useState({});
|
||||||
|
const [headerImgError, setHeaderImgError] = useState(false);
|
||||||
const { copied, copy } = useCopyToClipboard();
|
const { copied, copy } = useCopyToClipboard();
|
||||||
|
|
||||||
const providerInfo = providerNode
|
const providerInfo = providerNode
|
||||||
@@ -298,7 +299,7 @@ export default function ProviderDetailPage() {
|
|||||||
<CardSkeleton />
|
<CardSkeleton />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!providerInfo) {
|
if (!providerInfo) {
|
||||||
return (
|
return (
|
||||||
@@ -311,6 +312,14 @@ export default function ProviderDetailPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine icon path: OpenAI Compatible providers use specialized icons
|
||||||
|
const getHeaderIconPath = () => {
|
||||||
|
if (isOpenAICompatible && providerInfo.apiType) {
|
||||||
|
return providerInfo.apiType === "responses" ? "/providers/oai-r.png" : "/providers/oai-cc.png";
|
||||||
|
}
|
||||||
|
return `/providers/${providerInfo.id}.png`;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-8">
|
<div className="flex flex-col gap-8">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
@@ -327,19 +336,19 @@ export default function ProviderDetailPage() {
|
|||||||
className="rounded-lg flex items-center justify-center"
|
className="rounded-lg flex items-center justify-center"
|
||||||
style={{ backgroundColor: `${providerInfo.color}15` }}
|
style={{ backgroundColor: `${providerInfo.color}15` }}
|
||||||
>
|
>
|
||||||
{providerInfo.textIcon ? (
|
{headerImgError ? (
|
||||||
<span className="text-sm font-bold" style={{ color: providerInfo.color }}>
|
<span className="text-sm font-bold" style={{ color: providerInfo.color }}>
|
||||||
{providerInfo.textIcon}
|
{providerInfo.textIcon || providerInfo.id.slice(0, 2).toUpperCase()}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<Image
|
<Image
|
||||||
src={`/providers/${providerInfo.id}.png`}
|
src={getHeaderIconPath()}
|
||||||
alt={providerInfo.name}
|
alt={providerInfo.name}
|
||||||
width={48}
|
width={48}
|
||||||
height={48}
|
height={48}
|
||||||
className="object-contain rounded-lg max-w-[48px] max-h-[48px]"
|
className="object-contain rounded-lg max-w-[48px] max-h-[48px]"
|
||||||
sizes="48px"
|
sizes="48px"
|
||||||
onError={(e) => { e.currentTarget.style.display = "none"; }}
|
onError={() => setHeaderImgError(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -233,10 +233,19 @@ ProviderCard.propTypes = {
|
|||||||
}).isRequired,
|
}).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
// API Key providers - only use textIcon, no image
|
// API Key providers - use image with textIcon fallback (same as OAuth providers)
|
||||||
function ApiKeyProviderCard({ providerId, provider, stats }) {
|
function ApiKeyProviderCard({ providerId, provider, stats }) {
|
||||||
const { connected, error, errorCode, errorTime } = stats;
|
const { connected, error, errorCode, errorTime } = stats;
|
||||||
const isCompatible = providerId.startsWith(OPENAI_COMPATIBLE_PREFIX);
|
const isCompatible = providerId.startsWith(OPENAI_COMPATIBLE_PREFIX);
|
||||||
|
const [imgError, setImgError] = useState(false);
|
||||||
|
|
||||||
|
// Determine icon path: OpenAI Compatible providers use specialized icons
|
||||||
|
const getIconPath = () => {
|
||||||
|
if (isCompatible) {
|
||||||
|
return provider.apiType === "responses" ? "/providers/oai-r.png" : "/providers/oai-cc.png";
|
||||||
|
}
|
||||||
|
return `/providers/${provider.id}.png`;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link href={`/dashboard/providers/${providerId}`} className="group">
|
<Link href={`/dashboard/providers/${providerId}`} className="group">
|
||||||
@@ -247,12 +256,24 @@ function ApiKeyProviderCard({ providerId, provider, stats }) {
|
|||||||
className="size-8 rounded-lg flex items-center justify-center"
|
className="size-8 rounded-lg flex items-center justify-center"
|
||||||
style={{ backgroundColor: `${provider.color}15` }}
|
style={{ backgroundColor: `${provider.color}15` }}
|
||||||
>
|
>
|
||||||
<span
|
{imgError ? (
|
||||||
className="text-xs font-bold"
|
<span
|
||||||
style={{ color: provider.color }}
|
className="text-xs font-bold"
|
||||||
>
|
style={{ color: provider.color }}
|
||||||
{provider.textIcon || provider.id.slice(0, 2).toUpperCase()}
|
>
|
||||||
</span>
|
{provider.textIcon || provider.id.slice(0, 2).toUpperCase()}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<Image
|
||||||
|
src={getIconPath()}
|
||||||
|
alt={provider.name}
|
||||||
|
width={32}
|
||||||
|
height={32}
|
||||||
|
className="object-contain rounded-lg max-w-[32px] max-h-[32px]"
|
||||||
|
sizes="32px"
|
||||||
|
onError={() => setImgError(true)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-semibold">{provider.name}</h3>
|
<h3 className="font-semibold">{provider.name}</h3>
|
||||||
|
|||||||