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
This commit is contained in:
ZacharyZhang-NY committed 2026-07-10 20:18:41 -04:00
1 parent 177e25aab5
commit a4021b2029
22 files changed
+106 -39

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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

+4
View File
@@ -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" }}>
+76
View File
@@ -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>
);
}