Every provider wears its official mark: logo.dev art downloaded once, bundled forever
- 16 brand PNGs land in src/renderer/src/assets/provider-logos (img.logo.dev, size 128, same publishable token as connectors) with a README recording each domain and the refresh command - no runtime fetch, nothing to download again - shared/ProviderLogo.tsx maps catalog keys to the bundled art on a white tile (transparent marks stay legible in dark theme); custom endpoints keep the letter tile since no official brand exists - Settings provider rows/cards and the onboarding provider step now share the same component; old letter-tile ProviderLogo removed - Verified via Playwright screenshot captures: onboarding and Settings both render the real marks; full e2e green
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import { Fragment, useEffect, useState } from "react";
|
||||
import type { ProviderCatalogEntry } from "../../../shared/domain";
|
||||
import { ProviderLogo } from "../shared/ProviderLogo";
|
||||
import { ProviderOAuthConnect } from "../shared/ProviderOAuthConnect";
|
||||
import { Button, Field, Pill } from "../shared/ui";
|
||||
import type { Draft } from "./Onboarding";
|
||||
@@ -40,7 +41,8 @@ export function ProviderStep({
|
||||
providerBaseUrl: "",
|
||||
})}
|
||||
>
|
||||
<div className="row">
|
||||
<div className="row" style={{ gap: 8 }}>
|
||||
<ProviderLogo providerKey={provider.key} label={provider.label} size={22} />
|
||||
<div className="opt-title" style={{ flex: 1 }}>{provider.label}</div>
|
||||
<Pill tone="violet">{authBadge(provider.authType)}</Pill>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Provider logos
|
||||
|
||||
Official brand marks for the model-provider catalog, bundled at build time so
|
||||
the app never fetches them at runtime. Source: img.logo.dev (same source and
|
||||
publishable token as the connectors page), downloaded once per brand.
|
||||
|
||||
Refresh a logo (token lives in `src/main/services/composio/connector-brands.ts`):
|
||||
|
||||
```
|
||||
curl -o <provider-key>.png "https://img.logo.dev/<domain>?token=<publishable-token>&size=128&format=png&fallback=404"
|
||||
```
|
||||
|
||||
Domain per file: openai.com, anthropic.com, claude.ai (claude-pro), grok.com
|
||||
(xai), groq.com, mistral.ai, cerebras.ai, openrouter.ai, moonshot.ai (kimi;
|
||||
also used for kimi-code — kimi.com is not in logo.dev's index), moonshot.cn
|
||||
(kimi-cn), github.com (github-copilot), antigravity.google
|
||||
(google-antigravity), chatgpt.com (gpt-account), azure.microsoft.com
|
||||
(azure-openai), aws.amazon.com (aws-bedrock), cloud.google.com (vertex-ai).
|
||||
The custom-openai / custom-anthropic endpoints carry no official brand and
|
||||
render as letter tiles.
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 616 B |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
@@ -0,0 +1,4 @@
|
||||
declare module "*.png" {
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
const PROVIDER_BRAND: Record<string, [string, string]> = {
|
||||
"Anthropic": ["#cb9a5b", "A"],
|
||||
"OpenAI": ["#0a0a0b", "O"],
|
||||
"xAI Grok": ["#0a0a0b", "X"],
|
||||
"GitHub Copilot": ["#2da44e", "⌬"],
|
||||
"Google Antigravity": ["#3a7bd5", "G"],
|
||||
"GPT account": ["#10a37f", "◔"],
|
||||
"Azure OpenAI": ["#0078d4", "Az"],
|
||||
"AWS Bedrock": ["#e87722", "Bd"],
|
||||
"Vertex AI": ["#4285f4", "V"],
|
||||
"Groq": ["#f55036", "Q"],
|
||||
};
|
||||
|
||||
export function ProviderLogo({ label, size = 30 }: { label: string; size?: number }) {
|
||||
const entry = PROVIDER_BRAND[label] ?? ["#a1a1aa", label.slice(0, 1).toUpperCase()];
|
||||
const [background, glyph] = entry;
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
borderRadius: 7,
|
||||
background,
|
||||
color: "#fff",
|
||||
display: "grid",
|
||||
placeItems: "center",
|
||||
fontSize: Math.round(size * 0.42),
|
||||
fontWeight: 600,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
{glyph}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import type {
|
||||
ProviderRecord,
|
||||
} from "../../../../shared/domain";
|
||||
import { PROVIDER_DATA_POLICY } from "../../../../shared/domain";
|
||||
import { ProviderLogo } from "./ProviderLogo";
|
||||
import { ProviderLogo } from "../../shared/ProviderLogo";
|
||||
import { ProviderConnectFields, ProviderOAuthFields } from "./ProviderConnectionDialog";
|
||||
import { composeSecret } from "./provider-secret";
|
||||
import type { SnapshotActions } from "../../app/useSnapshot";
|
||||
@@ -281,7 +281,7 @@ function ProviderRow({
|
||||
borderRadius: 10,
|
||||
}}
|
||||
>
|
||||
<ProviderLogo label={provider.label} />
|
||||
<ProviderLogo providerKey={provider.providerKey} label={provider.label} />
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ fontSize: 13.5, fontWeight: 540, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{provider.label}</div>
|
||||
<div style={{ fontSize: 12, color: "var(--text-3)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
||||
@@ -321,7 +321,7 @@ function ProviderCard({ provider, onConnect }: { provider: ProviderRecord; onCon
|
||||
minWidth: 0,
|
||||
}}
|
||||
>
|
||||
<ProviderLogo label={provider.label} />
|
||||
<ProviderLogo providerKey={provider.providerKey} label={provider.label} />
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ fontSize: 13.5, fontWeight: 540, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{provider.label}</div>
|
||||
<div style={{ fontSize: 12, color: "var(--text-3)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import openaiLogo from "../assets/provider-logos/openai.png";
|
||||
import anthropicLogo from "../assets/provider-logos/anthropic.png";
|
||||
import claudeProLogo from "../assets/provider-logos/claude-pro.png";
|
||||
import xaiLogo from "../assets/provider-logos/xai.png";
|
||||
import groqLogo from "../assets/provider-logos/groq.png";
|
||||
import mistralLogo from "../assets/provider-logos/mistral.png";
|
||||
import cerebrasLogo from "../assets/provider-logos/cerebras.png";
|
||||
import openrouterLogo from "../assets/provider-logos/openrouter.png";
|
||||
import kimiLogo from "../assets/provider-logos/kimi.png";
|
||||
import kimiCnLogo from "../assets/provider-logos/kimi-cn.png";
|
||||
import githubCopilotLogo from "../assets/provider-logos/github-copilot.png";
|
||||
import googleAntigravityLogo from "../assets/provider-logos/google-antigravity.png";
|
||||
import gptAccountLogo from "../assets/provider-logos/gpt-account.png";
|
||||
import azureOpenAiLogo from "../assets/provider-logos/azure-openai.png";
|
||||
import awsBedrockLogo from "../assets/provider-logos/aws-bedrock.png";
|
||||
import vertexAiLogo from "../assets/provider-logos/vertex-ai.png";
|
||||
|
||||
/**
|
||||
* Official provider marks bundled at build time (assets/provider-logos —
|
||||
* downloaded once from img.logo.dev, see the README there). Catalog keys
|
||||
* without an official brand (the custom endpoints) fall back to a letter
|
||||
* tile. The white tile behind the image keeps transparent marks legible on
|
||||
* the dark theme; full-bleed logos cover it entirely.
|
||||
*/
|
||||
const PROVIDER_LOGOS: Record<string, string> = {
|
||||
openai: openaiLogo,
|
||||
anthropic: anthropicLogo,
|
||||
"claude-pro": claudeProLogo,
|
||||
xai: xaiLogo,
|
||||
groq: groqLogo,
|
||||
mistral: mistralLogo,
|
||||
cerebras: cerebrasLogo,
|
||||
openrouter: openrouterLogo,
|
||||
kimi: kimiLogo,
|
||||
"kimi-cn": kimiCnLogo,
|
||||
"kimi-code": kimiLogo,
|
||||
"github-copilot": githubCopilotLogo,
|
||||
"google-antigravity": googleAntigravityLogo,
|
||||
"gpt-account": gptAccountLogo,
|
||||
"azure-openai": azureOpenAiLogo,
|
||||
"aws-bedrock": awsBedrockLogo,
|
||||
"vertex-ai": vertexAiLogo,
|
||||
};
|
||||
|
||||
export function ProviderLogo({ providerKey, label, size = 30 }: { providerKey: string; label: string; size?: number }) {
|
||||
const logo = PROVIDER_LOGOS[providerKey];
|
||||
if (logo) {
|
||||
return (
|
||||
<img
|
||||
src={logo}
|
||||
alt=""
|
||||
width={size}
|
||||
height={size}
|
||||
style={{ width: size, height: size, borderRadius: 7, objectFit: "cover", background: "#fff", flexShrink: 0 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
borderRadius: 7,
|
||||
background: "#a1a1aa",
|
||||
color: "#fff",
|
||||
display: "grid",
|
||||
placeItems: "center",
|
||||
fontSize: Math.round(size * 0.42),
|
||||
fontWeight: 600,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
{label.slice(0, 1).toUpperCase()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||