Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions apps/desktop-tauri/src/components/providers/providerIcons.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,100 @@
import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";
import { TEST_PROVIDER_CATALOG } from "../../test/providerCatalog";
import { PROVIDER_ICON_REGISTRY } from "./providerIcons";

// Repository root, from apps/desktop-tauri/src/components/providers.
const REPO_ROOT = import.meta.dirname + "/../../../../../";

/**
* The `ProviderId::X => "#RRGGBB"` rows of the Rust `brand_color` table in
* `rust/src/core/provider.rs`, keyed by the lowercased variant name (which
* is the registry id for every provider).
*/
function rustBrandColors(): Map<string, string> {
const source = readFileSync(REPO_ROOT + "rust/src/core/provider.rs", "utf8");
const start = source.indexOf("pub fn brand_color(id: ProviderId)");
const end = source.indexOf("\n}\n", start);
const rows = new Map<string, string>();
if (start < 0 || end < 0) return rows;
for (const match of source
.slice(start, end)
.matchAll(/ProviderId::(\w+) => "(#[0-9A-Fa-f]{6})"/g)) {
rows.set(match[1].toLowerCase(), match[2].toLowerCase());
}
return rows;
}

/** `--chart-<id>: rgb(r, g, b);` provider tokens from styles.css, as hex. */
function chartTokens(): Array<[string, string]> {
const css = readFileSync(import.meta.dirname + "/../../styles.css", "utf8");
const tokens: Array<[string, string]> = [];
for (const match of css.matchAll(
/--chart-([a-z0-9]+): rgb\((\d+), (\d+), (\d+)\);/g,
)) {
const hex = [match[2], match[3], match[4]]
.map((channel) => Number(channel).toString(16).padStart(2, "0"))
.join("");
tokens.push([match[1], `#${hex}`]);
}
return tokens;
}

// Upstream 0.70.0 palette audit (#4075, docs/provider-palette.md): the 16
// accents it adopted. The Rust side pins the same values and checks their
// contrast against the previous Windows accents.
const ADOPTED_ACCENTS: Array<[string, string]> = [
["abacus", "#814ee8"],
["amp", "#f34e3f"],
["augment", "#1aa049"],
["bedrock", "#01a88d"],
["clinepass", "#5487c8"],
["codebuff", "#00ff95"],
["commandcode", "#8c4edd"],
["cursor", "#f54e00"],
["deepseek", "#4d6bfe"],
["devin", "#317cff"],
["kiro", "#9046ff"],
["longcat", "#29e154"],
["mistral", "#ff5229"],
["neuralwatt", "#d55934"],
["sub2api", "#14b8a6"],
["venice", "#3c8fdd"],
];

describe("provider icon registry", () => {
it("has explicit icon metadata for every provider in the catalog", () => {
for (const [id] of TEST_PROVIDER_CATALOG) {
expect(PROVIDER_ICON_REGISTRY[id], id).toBeDefined();
}
});

it("uses the accents adopted by the upstream 0.70.0 palette audit", () => {
for (const [id, color] of ADOPTED_ACCENTS) {
expect(PROVIDER_ICON_REGISTRY[id]?.brandColor.toLowerCase(), id).toBe(color);
}
});

it("matches the Rust brand_color table for every provider", () => {
const rust = rustBrandColors();
expect(rust.size).toBeGreaterThan(0);
expect([...rust.keys()].sort()).toEqual(Object.keys(PROVIDER_ICON_REGISTRY).sort());
for (const [id, color] of rust) {
expect(PROVIDER_ICON_REGISTRY[id]?.brandColor.toLowerCase(), id).toBe(color);
}
});

it("keeps provider chart tokens on the brand color", () => {
// Grok's chart token has been pure black since before the registry
// existed (xAI shares it), so it is the one intentional exception.
const checked = chartTokens().filter(
([id]) => id !== "grok" && PROVIDER_ICON_REGISTRY[id] !== undefined,
);
expect(checked.map(([id]) => id)).toEqual(
expect.arrayContaining(["amp", "augment", "codebuff", "cursor", "deepseek", "kiro", "mistral"]),
);
for (const [id, color] of checked) {
expect(color, `--chart-${id}`).toBe(PROVIDER_ICON_REGISTRY[id].brandColor.toLowerCase());
}
});
});
32 changes: 16 additions & 16 deletions apps/desktop-tauri/src/components/providers/providerIcons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,26 +162,26 @@ const RAW: Record<string, string> = {
export const PROVIDER_ICON_REGISTRY: Record<string, ProviderIcon> = {
alibaba: { id: "alibaba", brandColor: "#ff6a00", fallbackLetter: "阿", svgPath: RAW.alibaba },
alibabatokenplan: { id: "alibabatokenplan", brandColor: "#ff6a00", fallbackLetter: "阿", svgPath: RAW.alibaba },
amp: { id: "amp", brandColor: "#dc2626", fallbackLetter: "⚡", svgPath: RAW.amp },
amp: { id: "amp", brandColor: "#f34e3f", fallbackLetter: "⚡", svgPath: RAW.amp },
antigravity: { id: "antigravity", brandColor: "#60ba7e", fallbackLetter: "◉", svgPath: RAW.antigravity },
augment: { id: "augment", brandColor: "#6366f1", fallbackLetter: "A", svgPath: RAW.augment },
augment: { id: "augment", brandColor: "#1aa049", fallbackLetter: "A", svgPath: RAW.augment },
claude: { id: "claude", brandColor: "#cc7c5e", fallbackLetter: "◈", svgPath: RAW.claude },
pi: { id: "pi", brandColor: "#7c3aed", fallbackLetter: "P" },
codebuff: { id: "codebuff", brandColor: "#44ff00", fallbackLetter: "B", svgPath: RAW.codebuff },
codebuff: { id: "codebuff", brandColor: "#00ff95", fallbackLetter: "B", svgPath: RAW.codebuff },
coderabbit: { id: "coderabbit", brandColor: "#ff5c35", fallbackLetter: "C", svgPath: RAW.coderabbit },
codex: { id: "codex", brandColor: "#49a3b0", fallbackLetter: "◆", svgPath: RAW.codex },
copilot: { id: "copilot", brandColor: "#a855f7", fallbackLetter: "⬡", svgPath: RAW.copilot },
cursor: { id: "cursor", brandColor: "#00bfa5", fallbackLetter: "▸", svgPath: RAW.cursor },
cursor: { id: "cursor", brandColor: "#f54e00", fallbackLetter: "▸", svgPath: RAW.cursor },
deepgram: { id: "deepgram", brandColor: "#13ef93", fallbackLetter: "D", svgPath: RAW.deepgram },
deepinfra: { id: "deepinfra", brandColor: "#2a3275", fallbackLetter: "D", svgPath: RAW.deepinfra },
fireworks: { id: "fireworks", brandColor: "#f25b1c", fallbackLetter: "F", svgPath: RAW.fireworks },
aiand: { id: "aiand", brandColor: "#e25c2b", fallbackLetter: "&", svgPath: RAW.aiand },
clinepass: { id: "clinepass", brandColor: "#61a3fa", fallbackLetter: "C", svgPath: RAW.clinepass },
longcat: { id: "longcat", brandColor: "#ffd100", fallbackLetter: "L", svgPath: RAW.longcat },
neuralwatt: { id: "neuralwatt", brandColor: "#38d98c", fallbackLetter: "N", svgPath: RAW.neuralwatt },
clinepass: { id: "clinepass", brandColor: "#5487c8", fallbackLetter: "C", svgPath: RAW.clinepass },
longcat: { id: "longcat", brandColor: "#29e154", fallbackLetter: "L", svgPath: RAW.longcat },
neuralwatt: { id: "neuralwatt", brandColor: "#d55934", fallbackLetter: "N", svgPath: RAW.neuralwatt },
zoommate: { id: "zoommate", brandColor: "#0B5CFF", fallbackLetter: "Z", svgPath: RAW.zoommate },
zenmux: { id: "zenmux", brandColor: "#6c5ce7", fallbackLetter: "Z", svgPath: RAW.zenmux },
deepseek: { id: "deepseek", brandColor: "#527df0", fallbackLetter: "D", svgPath: RAW.deepseek },
deepseek: { id: "deepseek", brandColor: "#4d6bfe", fallbackLetter: "D", svgPath: RAW.deepseek },
elevenlabs: { id: "elevenlabs", brandColor: "#111827", fallbackLetter: "E", svgPath: RAW.elevenlabs },
factory: { id: "factory", brandColor: "#ff6b35", fallbackLetter: "◎", svgPath: RAW.factory },
gemini: { id: "gemini", brandColor: "#ab87ea", fallbackLetter: "✦", svgPath: RAW.gemini },
Expand All @@ -193,13 +193,13 @@ export const PROVIDER_ICON_REGISTRY: Record<string, ProviderIcon> = {
typesafe: { id: "typesafe", brandColor: "#2563eb", fallbackLetter: "T" },
jetbrains: { id: "jetbrains", brandColor: "#ff3399", fallbackLetter: "J", svgPath: RAW.jetbrains },
kilo: { id: "kilo", brandColor: "#5d87ff", fallbackLetter: "K", svgPath: RAW.kilo },
bedrock: { id: "bedrock", brandColor: "#ff9900", fallbackLetter: "B", svgPath: RAW.bedrock },
bedrock: { id: "bedrock", brandColor: "#01a88d", fallbackLetter: "B", svgPath: RAW.bedrock },
kimi: { id: "kimi", brandColor: "#fe603c", fallbackLetter: "☽", svgPath: RAW.kimi },
kimik2: { id: "kimik2", brandColor: "#4c00ff", fallbackLetter: "☽", svgPath: RAW.kimi },
kiro: { id: "kiro", brandColor: "#ff9900", fallbackLetter: "K", svgPath: RAW.kiro },
kiro: { id: "kiro", brandColor: "#9046ff", fallbackLetter: "K", svgPath: RAW.kiro },
llmproxy: { id: "llmproxy", brandColor: "#4f46e5", fallbackLetter: "L", svgPath: RAW.llmproxy },
minimax: { id: "minimax", brandColor: "#fe603c", fallbackLetter: "M", svgPath: RAW.minimax },
mistral: { id: "mistral", brandColor: "#ff500f", fallbackLetter: "M", svgPath: RAW.mistral },
mistral: { id: "mistral", brandColor: "#ff5229", fallbackLetter: "M", svgPath: RAW.mistral },
muse: { id: "muse", brandColor: "#0668e1", fallbackLetter: "M", svgPath: RAW.muse },
ollama: { id: "ollama", brandColor: "#8b95b0", fallbackLetter: "○", svgPath: RAW.ollama },
azureopenai: { id: "azureopenai", brandColor: "#0078d4", fallbackLetter: "A" },
Expand All @@ -216,25 +216,25 @@ export const PROVIDER_ICON_REGISTRY: Record<string, ProviderIcon> = {
// Aliases / Rust-side normalizations without their own SVG.
nanogpt: { id: "nanogpt", brandColor: "#687fa1", fallbackLetter: "N" },
infini: { id: "infini", brandColor: "#687fa1", fallbackLetter: "I" },
abacus: { id: "abacus", brandColor: "#7c3aed", fallbackLetter: "A", svgPath: RAW.abacus },
abacus: { id: "abacus", brandColor: "#814ee8", fallbackLetter: "A", svgPath: RAW.abacus },
manus: { id: "manus", brandColor: "#34322d", fallbackLetter: "M", svgPath: RAW.manus },
mimo: { id: "mimo", brandColor: "#ff6900", fallbackLetter: "M", svgPath: RAW.mimo },
doubao: { id: "doubao", brandColor: "#2563eb", fallbackLetter: "D", svgPath: RAW.doubao },
commandcode: { id: "commandcode", brandColor: "#44ff00", fallbackLetter: "C", svgPath: RAW.commandcode },
commandcode: { id: "commandcode", brandColor: "#8c4edd", fallbackLetter: "C", svgPath: RAW.commandcode },
crof: { id: "crof", brandColor: "#7c3aed", fallbackLetter: "C", svgPath: RAW.crof },
crossmodel: { id: "crossmodel", brandColor: "#c084fc", fallbackLetter: "X", svgPath: RAW.crossmodel },
qoder: { id: "qoder", brandColor: "#2563eb", fallbackLetter: "Q", svgPath: RAW.qoder },
replicate: { id: "replicate", brandColor: "#000000", fallbackLetter: "R", svgPath: RAW.replicate },
codebuddy: { id: "codebuddy", brandColor: "#0052d9", fallbackLetter: "C" },
sakana: { id: "sakana", brandColor: "#0ea5e9", fallbackLetter: "S", svgPath: RAW.sakana },
stepfun: { id: "stepfun", brandColor: "#999999", fallbackLetter: "S", svgPath: RAW.stepfun },
sub2api: { id: "sub2api", brandColor: "#2dc6d8", fallbackLetter: "S", svgPath: RAW.sub2api },
venice: { id: "venice", brandColor: "#111827", fallbackLetter: "V", svgPath: RAW.venice },
sub2api: { id: "sub2api", brandColor: "#14b8a6", fallbackLetter: "S", svgPath: RAW.sub2api },
venice: { id: "venice", brandColor: "#3c8fdd", fallbackLetter: "V", svgPath: RAW.venice },
openaiapi: { id: "openaiapi", brandColor: "#10a37f", fallbackLetter: "O" },
chutes: { id: "chutes", brandColor: "#ff5c35", fallbackLetter: "C" },
litellm: { id: "litellm", brandColor: "#0ea5e9", fallbackLetter: "L" },
poe: { id: "poe", brandColor: "#5d5fef", fallbackLetter: "P" },
devin: { id: "devin", brandColor: "#111827", fallbackLetter: "D" },
devin: { id: "devin", brandColor: "#317cff", fallbackLetter: "D" },
zed: { id: "zed", brandColor: "#084ccf", fallbackLetter: "Z" },
qwencloud: { id: "qwencloud", brandColor: "#615CED", fallbackLetter: "Q" },
notion: { id: "notion", brandColor: "#337EA9", fallbackLetter: "N", svgPath: RAW.notion },
Expand Down
14 changes: 7 additions & 7 deletions apps/desktop-tauri/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -96,26 +96,26 @@
--chart-claude: rgb(204, 124, 94);
--chart-codex: rgb(73, 163, 176);
--chart-gemini: rgb(171, 135, 234);
--chart-cursor: rgb(0, 191, 165);
--chart-cursor: rgb(245, 78, 0);
--chart-copilot: rgb(168, 85, 247);
--chart-jetbrains: rgb(255, 51, 153);
--chart-antigravity: rgb(96, 186, 126);
--chart-augment: rgb(99, 102, 241);
--chart-amp: rgb(220, 38, 38);
--chart-augment: rgb(26, 160, 73);
--chart-amp: rgb(243, 78, 63);
--chart-factory: rgb(255, 107, 53);
--chart-droid: rgb(255, 107, 53);
--chart-kimi: rgb(254, 96, 60);
--chart-kimik2: rgb(76, 0, 255);
--chart-kiro: rgb(255, 153, 0);
--chart-kiro: rgb(144, 70, 255);
--chart-opencode: rgb(59, 130, 246);
--chart-minimax: rgb(254, 96, 60);
--chart-vertexai: rgb(66, 133, 244);
--chart-zai: rgb(232, 90, 106);
--chart-alibaba: rgb(255, 106, 0);
--chart-nanogpt: rgb(104, 127, 161);
--chart-mistral: rgb(255, 80, 15);
--chart-codebuff: rgb(68, 255, 0);
--chart-deepseek: rgb(82, 125, 240);
--chart-mistral: rgb(255, 82, 41);
--chart-codebuff: rgb(0, 255, 149);
--chart-deepseek: rgb(77, 107, 254);
--chart-windsurf: rgb(34, 197, 94);
--chart-elevenlabs: rgb(17, 24, 39);
--chart-deepgram: rgb(19, 239, 147);
Expand Down
Loading