mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
548dc15acd
* feat(models): unify provider metadata around backend catalog - Move shared provider metadata and alias normalization into backend-owned provider catalog - Expose display, fetch, auth, and default model metadata through /api/models provider_options - Replace frontend static provider registry with catalog-driven selection, validation, grouping, and fallback rendering - Treat provider default api_base as placeholder and effective fetch/test base while keep submitted api_base separate from derived defaults - Add model page retry handling, touched locale updates, and provider metadata assertions in backend tests * fix(models): canonicalize backend provider aliases and common models * fix(models): restore deepseek common model recommendations
55 lines
1.7 KiB
TypeScript
55 lines
1.7 KiB
TypeScript
import { useMemo, useState } from "react"
|
|
|
|
import type { ProviderCatalogEntry } from "./provider-registry"
|
|
|
|
interface ProviderIconProps {
|
|
provider: Pick<ProviderCatalogEntry, "key" | "label" | "iconSlug" | "domain">
|
|
}
|
|
|
|
export function ProviderIcon({ provider }: ProviderIconProps) {
|
|
const [sourceIndex, setSourceIndex] = useState(0)
|
|
const [loadFailed, setLoadFailed] = useState(false)
|
|
const initial = provider.label.trim().charAt(0).toUpperCase() || "?"
|
|
const iconUrls = useMemo(() => {
|
|
const slug = provider.iconSlug
|
|
const domain = provider.domain
|
|
const urls: string[] = []
|
|
if (slug) {
|
|
urls.push(`https://cdn.simpleicons.org/${slug}`)
|
|
}
|
|
if (domain) {
|
|
urls.push(`https://www.google.com/s2/favicons?domain=${domain}&sz=64`)
|
|
}
|
|
return urls
|
|
}, [provider.domain, provider.iconSlug])
|
|
|
|
const iconUrl = iconUrls[sourceIndex]
|
|
|
|
if (!iconUrl || loadFailed) {
|
|
return (
|
|
<span className="inline-flex size-4 shrink-0 items-center justify-center rounded-sm border border-black/10 bg-white text-[9px] font-semibold text-black/70 dark:border-white/20 dark:text-white/70">
|
|
{initial}
|
|
</span>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<span className="inline-flex size-4 shrink-0 items-center justify-center overflow-hidden rounded-sm border border-black/10 bg-white p-0.5 dark:border-white/20">
|
|
<img
|
|
src={iconUrl}
|
|
alt={`${provider.label} logo`}
|
|
className="size-full object-contain"
|
|
loading="lazy"
|
|
referrerPolicy="no-referrer"
|
|
onError={() => {
|
|
if (sourceIndex < iconUrls.length - 1) {
|
|
setSourceIndex((idx) => idx + 1)
|
|
return
|
|
}
|
|
setLoadFailed(true)
|
|
}}
|
|
/>
|
|
</span>
|
|
)
|
|
}
|