| "use client";
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| import { createElement, memo, useState } from "react";
|
| import Image from "next/image";
|
|
|
| import { getLobeProviderIcon } from "./lobeProviderIcons";
|
|
|
| interface ProviderIconProps {
|
| providerId: string;
|
| size?: number;
|
| type?: "mono" | "color";
|
| className?: string;
|
| style?: React.CSSProperties;
|
| }
|
|
|
| function GenericProviderIcon({ size }: { size: number }) {
|
| return (
|
| <svg width={size} height={size} viewBox="0 0 24 24" fill="none" style={{ flex: "none" }}>
|
| <circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="1.5" opacity="0.4" />
|
| <path d="M8 12h8M12 8v8" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
| </svg>
|
| );
|
| }
|
|
|
| const KNOWN_PNGS = new Set([
|
| "agentrouter",
|
| "inner-ai",
|
| "aimlapi",
|
| "anthropic-m",
|
| "blackbox",
|
| "claude",
|
| "continue",
|
| "copilot",
|
| "cursor",
|
| "deepgram",
|
| "ironclaw",
|
| "kie",
|
| "nanobot",
|
| "oai-cc",
|
| "oai-r",
|
| "openclaw",
|
| "zeroclaw",
|
| "adapta-web",
|
| "blackbox-web",
|
| "cliproxyapi",
|
| "empower",
|
| "gigachat",
|
| "heroku",
|
| "lemonade",
|
| "linkup-search",
|
| "llamafile",
|
| "llamagate",
|
| "maritalk",
|
| "nanogpt",
|
| "nscale",
|
| "ovhcloud",
|
| "piapi",
|
| "predibase",
|
| "reka",
|
| ]);
|
| const KNOWN_SVGS = new Set([
|
| "apikey",
|
| "bazaarlink",
|
| "brave",
|
| "brave-search",
|
| "cartesia",
|
| "360ai",
|
| "huggingchat",
|
| "iflytek",
|
| "sparkdesk",
|
| "arcee-ai",
|
| "inclusionai",
|
| "krutrim",
|
| "liquid",
|
| "monsterapi",
|
| "nomic",
|
| "poolside",
|
| "clarifai",
|
| "command-code",
|
| "claude-web",
|
| "docker-model-runner",
|
| "droid",
|
| "gemini-cli",
|
| "gitlab",
|
| "gitlab-duo",
|
| "inworld",
|
| "kiro",
|
| "kilo-gateway",
|
| "kilocode",
|
| "modal",
|
| "nlpcloud",
|
| "oauth",
|
| "oci",
|
| "opencode",
|
| "playht",
|
| "puter",
|
| "qianfan",
|
| "sap",
|
| "scaleway",
|
| "serper-search",
|
| "searxng-search",
|
| "synthetic",
|
| "wandb",
|
| "youcom-search",
|
| ]);
|
|
|
| const ProviderIcon = memo(function ProviderIcon({
|
| providerId,
|
| size = 24,
|
| type = "color",
|
| className,
|
| style,
|
| }: ProviderIconProps) {
|
| const normalizedId = providerId.toLowerCase();
|
| const lobeIcon = getLobeProviderIcon(normalizedId, type);
|
| const hasPng = KNOWN_PNGS.has(normalizedId);
|
| const hasSvg = KNOWN_SVGS.has(normalizedId);
|
|
|
| const [failedAssets, setFailedAssets] = useState<Record<string, true>>({});
|
| const pngKey = `${normalizedId}:png`;
|
| const svgKey = `${normalizedId}:svg`;
|
| const usePng = !lobeIcon && hasPng && !failedAssets[pngKey];
|
| const useSvg = !lobeIcon && hasSvg && !failedAssets[svgKey] && (!hasPng || failedAssets[pngKey]);
|
|
|
| if (lobeIcon) {
|
| return (
|
| <span
|
| className={className}
|
| style={{ display: "inline-flex", alignItems: "center", ...style }}
|
| >
|
| {createElement(lobeIcon, {
|
| "aria-label": providerId,
|
| size,
|
| style: { flex: "none" },
|
| })}
|
| </span>
|
| );
|
| }
|
|
|
| if (usePng) {
|
| return (
|
| <span
|
| className={className}
|
| style={{ display: "inline-flex", alignItems: "center", ...style }}
|
| >
|
| <Image
|
| src={`/providers/${normalizedId}.png`}
|
| alt={providerId}
|
| width={size}
|
| height={size}
|
| style={{ objectFit: "contain" }}
|
| onError={() => {
|
| setFailedAssets((current) => ({ ...current, [pngKey]: true }));
|
| }}
|
| unoptimized
|
| />
|
| </span>
|
| );
|
| }
|
|
|
| if (useSvg) {
|
| return (
|
| <span
|
| className={className}
|
| style={{ display: "inline-flex", alignItems: "center", ...style }}
|
| >
|
| <Image
|
| src={`/providers/${normalizedId}.svg`}
|
| alt={providerId}
|
| width={size}
|
| height={size}
|
| style={{ objectFit: "contain" }}
|
| onError={() => setFailedAssets((current) => ({ ...current, [svgKey]: true }))}
|
| unoptimized
|
| />
|
| </span>
|
| );
|
| }
|
|
|
| return (
|
| <span className={className} style={{ display: "inline-flex", alignItems: "center", ...style }}>
|
| <GenericProviderIcon size={size} />
|
| </span>
|
| );
|
| });
|
|
|
| export default ProviderIcon;
|
| export type { ProviderIconProps };
|
|
|