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
This commit is contained in:
ramhaidar
2026-02-02 22:26:40 +07:00
parent f59571a18b
commit 60bd686fb0
11 changed files with 42 additions and 12 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
public/providers/gemini.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

BIN
public/providers/glm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
public/providers/kimi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
public/providers/oai-cc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

BIN
public/providers/oai-r.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

BIN
public/providers/openai.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 879 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@@ -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>

View File

@@ -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>