From 5b040b4c1c1bbbc467cc21b9c4abc0bf471e5826 Mon Sep 17 00:00:00 2001 From: Alex Schmitt Date: Tue, 25 Aug 2026 10:33:41 -0300 Subject: [PATCH] feat(dashboard): add provider logos with initial-avatar fallback ProviderAvatar now renders each provider's official brand logo (public/providers/.svg, sourced from @lobehub/icons-static-svg) when one is available, falling back to the existing hue-tinted initial for providers without one. scripts/add-provider-logo.mjs documents the process for adding a new logo. Closes #249 --- .../dashboard/routing/provider-avatar.tsx | 25 +++++++++-- .../dashboard/routing/provider-logos.test.ts | 13 ++++++ .../dashboard/routing/provider-logos.ts | 41 +++++++++++++++++++ package.json | 1 + pnpm-lock.yaml | 8 ++++ public/providers/bytepluscoding.svg | 1 + public/providers/cerebras.svg | 1 + public/providers/cloudflareworkersai.svg | 1 + public/providers/cohere.svg | 1 + public/providers/copilot.svg | 1 + public/providers/deepseek.svg | 1 + public/providers/fireworks.svg | 1 + public/providers/gateway.svg | 1 + public/providers/githubmodels.svg | 1 + public/providers/googleaistudio.svg | 1 + public/providers/groq.svg | 1 + public/providers/huggingface.svg | 1 + public/providers/mistral.svg | 1 + public/providers/moonshot.svg | 1 + public/providers/nvidianim.svg | 1 + public/providers/ollama.svg | 1 + public/providers/ollamacloud.svg | 1 + public/providers/openai.svg | 1 + public/providers/opencodego.svg | 1 + public/providers/opencodezen.svg | 1 + public/providers/openrouter.svg | 1 + public/providers/perplexity.svg | 1 + public/providers/pollinations.svg | 1 + public/providers/qwen.svg | 1 + public/providers/qwentoken.svg | 1 + public/providers/togetherai.svg | 1 + public/providers/zai.svg | 1 + public/providers/zaicoding.svg | 1 + scripts/add-provider-logo.mjs | 33 +++++++++++++++ 34 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 components/dashboard/routing/provider-logos.test.ts create mode 100644 components/dashboard/routing/provider-logos.ts create mode 100644 public/providers/bytepluscoding.svg create mode 100644 public/providers/cerebras.svg create mode 100644 public/providers/cloudflareworkersai.svg create mode 100644 public/providers/cohere.svg create mode 100644 public/providers/copilot.svg create mode 100644 public/providers/deepseek.svg create mode 100644 public/providers/fireworks.svg create mode 100644 public/providers/gateway.svg create mode 100644 public/providers/githubmodels.svg create mode 100644 public/providers/googleaistudio.svg create mode 100644 public/providers/groq.svg create mode 100644 public/providers/huggingface.svg create mode 100644 public/providers/mistral.svg create mode 100644 public/providers/moonshot.svg create mode 100644 public/providers/nvidianim.svg create mode 100644 public/providers/ollama.svg create mode 100644 public/providers/ollamacloud.svg create mode 100644 public/providers/openai.svg create mode 100644 public/providers/opencodego.svg create mode 100644 public/providers/opencodezen.svg create mode 100644 public/providers/openrouter.svg create mode 100644 public/providers/perplexity.svg create mode 100644 public/providers/pollinations.svg create mode 100644 public/providers/qwen.svg create mode 100644 public/providers/qwentoken.svg create mode 100644 public/providers/togetherai.svg create mode 100644 public/providers/zai.svg create mode 100644 public/providers/zaicoding.svg create mode 100644 scripts/add-provider-logo.mjs diff --git a/components/dashboard/routing/provider-avatar.tsx b/components/dashboard/routing/provider-avatar.tsx index a6c4d59..c4509e6 100644 --- a/components/dashboard/routing/provider-avatar.tsx +++ b/components/dashboard/routing/provider-avatar.tsx @@ -1,11 +1,12 @@ import { cn } from "@/lib/utils" +import { providerLogoSrc } from "./provider-logos" import { providerHue } from "./routing-utils" /** - * O repo não versiona logos de provedor, então o avatar é a inicial sobre um - * hue derivado do id — estável entre sessões e suficiente para o olho encontrar - * o mesmo provedor em lanes diferentes. + * Logo oficial quando existe (public/providers/.svg, ver provider-logos.ts); + * senão a inicial sobre um hue derivado do id — estável entre sessões e + * suficiente para o olho encontrar o mesmo provedor em lanes diferentes. */ export function ProviderAvatar({ className, @@ -18,6 +19,24 @@ export function ProviderAvatar({ providerId: string size?: number }) { + const logoSrc = providerLogoSrc(providerId) + + if (logoSrc) { + return ( + + {/* eslint-disable-next-line @next/next/no-img-element -- ícone estático pequeno, next/image não agrega nada aqui */} + + + ) + } + const hue = providerHue(providerId) return ( diff --git a/components/dashboard/routing/provider-logos.test.ts b/components/dashboard/routing/provider-logos.test.ts new file mode 100644 index 0000000..42ef019 --- /dev/null +++ b/components/dashboard/routing/provider-logos.test.ts @@ -0,0 +1,13 @@ +import { describe, expect, it } from "vitest" + +import { providerLogoSrc } from "./provider-logos" + +describe("providerLogoSrc", () => { + it("returns the static asset path for a provider with a logo", () => { + expect(providerLogoSrc("openai")).toBe("/providers/openai.svg") + }) + + it("returns undefined for a provider without a logo", () => { + expect(providerLogoSrc("puter")).toBeUndefined() + }) +}) diff --git a/components/dashboard/routing/provider-logos.ts b/components/dashboard/routing/provider-logos.ts new file mode 100644 index 0000000..e231f9c --- /dev/null +++ b/components/dashboard/routing/provider-logos.ts @@ -0,0 +1,41 @@ +/** + * Provedores com logo versionado em public/providers/.svg (issue #249). + * Origem: @lobehub/icons-static-svg (MIT, devDependency usada só como fonte). + * Para adicionar um novo: ver scripts/add-provider-logo.mjs. + */ +const PROVIDERS_WITH_LOGO = new Set([ + "bytepluscoding", + "cerebras", + "cloudflareworkersai", + "cohere", + "copilot", + "deepseek", + "fireworks", + "gateway", + "githubmodels", + "googleaistudio", + "groq", + "huggingface", + "mistral", + "moonshot", + "nvidianim", + "ollama", + "ollamacloud", + "opencodego", + "opencodezen", + "openai", + "openrouter", + "perplexity", + "pollinations", + "qwen", + "qwentoken", + "togetherai", + "zai", + "zaicoding", +]) + +export function providerLogoSrc(providerId: string): string | undefined { + return PROVIDERS_WITH_LOGO.has(providerId) + ? `/providers/${providerId}.svg` + : undefined +} diff --git a/package.json b/package.json index 69fd308..ba6544d 100644 --- a/package.json +++ b/package.json @@ -98,6 +98,7 @@ "zod": "^4.4.3" }, "devDependencies": { + "@lobehub/icons-static-svg": "^1.94.0", "@tailwindcss/postcss": "^4.3.3", "@types/babel__standalone": "^7.1.9", "@types/jsdom": "^30.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 867f243..08c9c1f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -149,6 +149,9 @@ importers: specifier: ^4.4.3 version: 4.4.3 devDependencies: + '@lobehub/icons-static-svg': + specifier: ^1.94.0 + version: 1.94.0 '@tailwindcss/postcss': specifier: ^4.3.3 version: 4.3.3 @@ -1180,6 +1183,9 @@ packages: '@lezer/markdown@1.7.2': resolution: {integrity: sha512-iTkYvoVcKt3WkeL7qUDyXHONZEwLio4wj8KTNi2dnjQEXBZKMV63BpQrPqfsM+OkvuRbiSTAcycYAsQzLhRNoQ==} + '@lobehub/icons-static-svg@1.94.0': + resolution: {integrity: sha512-Inx1TYkjLH6YeHOIHeVW9+OM/xxRnk8TmcQVKquFUDBmE3X9sUuRGt7kALrrDBNNAbrWz7Qq6fAiFj9E9Mmw9Q==} + '@marijn/find-cluster-break@1.0.3': resolution: {integrity: sha512-FY+MKLBoTsLNJF/eLWaOsXGdz6uh3Iu1axjPf6TUq92IYumcTcXWHoS747JARLkcdlJ/Waiaxc5wQfFO8jC6NA==} @@ -8426,6 +8432,8 @@ snapshots: '@lezer/common': 1.5.2 '@lezer/highlight': 1.2.3 + '@lobehub/icons-static-svg@1.94.0': {} + '@marijn/find-cluster-break@1.0.3': {} '@marsidev/react-turnstile@1.5.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': diff --git a/public/providers/bytepluscoding.svg b/public/providers/bytepluscoding.svg new file mode 100644 index 0000000..6921097 --- /dev/null +++ b/public/providers/bytepluscoding.svg @@ -0,0 +1 @@ +ByteDance \ No newline at end of file diff --git a/public/providers/cerebras.svg b/public/providers/cerebras.svg new file mode 100644 index 0000000..c8bab7b --- /dev/null +++ b/public/providers/cerebras.svg @@ -0,0 +1 @@ +Cerebras \ No newline at end of file diff --git a/public/providers/cloudflareworkersai.svg b/public/providers/cloudflareworkersai.svg new file mode 100644 index 0000000..d555b6f --- /dev/null +++ b/public/providers/cloudflareworkersai.svg @@ -0,0 +1 @@ +Cloudflare \ No newline at end of file diff --git a/public/providers/cohere.svg b/public/providers/cohere.svg new file mode 100644 index 0000000..94bcb82 --- /dev/null +++ b/public/providers/cohere.svg @@ -0,0 +1 @@ +Cohere \ No newline at end of file diff --git a/public/providers/copilot.svg b/public/providers/copilot.svg new file mode 100644 index 0000000..5426933 --- /dev/null +++ b/public/providers/copilot.svg @@ -0,0 +1 @@ +Copilot \ No newline at end of file diff --git a/public/providers/deepseek.svg b/public/providers/deepseek.svg new file mode 100644 index 0000000..3fc2302 --- /dev/null +++ b/public/providers/deepseek.svg @@ -0,0 +1 @@ +DeepSeek \ No newline at end of file diff --git a/public/providers/fireworks.svg b/public/providers/fireworks.svg new file mode 100644 index 0000000..a23445c --- /dev/null +++ b/public/providers/fireworks.svg @@ -0,0 +1 @@ +Fireworks \ No newline at end of file diff --git a/public/providers/gateway.svg b/public/providers/gateway.svg new file mode 100644 index 0000000..486cb95 --- /dev/null +++ b/public/providers/gateway.svg @@ -0,0 +1 @@ +Vercel \ No newline at end of file diff --git a/public/providers/githubmodels.svg b/public/providers/githubmodels.svg new file mode 100644 index 0000000..7a51b8e --- /dev/null +++ b/public/providers/githubmodels.svg @@ -0,0 +1 @@ +Github \ No newline at end of file diff --git a/public/providers/googleaistudio.svg b/public/providers/googleaistudio.svg new file mode 100644 index 0000000..62681df --- /dev/null +++ b/public/providers/googleaistudio.svg @@ -0,0 +1 @@ +Gemini \ No newline at end of file diff --git a/public/providers/groq.svg b/public/providers/groq.svg new file mode 100644 index 0000000..7294646 --- /dev/null +++ b/public/providers/groq.svg @@ -0,0 +1 @@ +Groq \ No newline at end of file diff --git a/public/providers/huggingface.svg b/public/providers/huggingface.svg new file mode 100644 index 0000000..dc1cf3f --- /dev/null +++ b/public/providers/huggingface.svg @@ -0,0 +1 @@ +HuggingFace \ No newline at end of file diff --git a/public/providers/mistral.svg b/public/providers/mistral.svg new file mode 100644 index 0000000..8e03e24 --- /dev/null +++ b/public/providers/mistral.svg @@ -0,0 +1 @@ +Mistral \ No newline at end of file diff --git a/public/providers/moonshot.svg b/public/providers/moonshot.svg new file mode 100644 index 0000000..fb56ac1 --- /dev/null +++ b/public/providers/moonshot.svg @@ -0,0 +1 @@ +MoonshotAI \ No newline at end of file diff --git a/public/providers/nvidianim.svg b/public/providers/nvidianim.svg new file mode 100644 index 0000000..a9683c2 --- /dev/null +++ b/public/providers/nvidianim.svg @@ -0,0 +1 @@ +Nvidia \ No newline at end of file diff --git a/public/providers/ollama.svg b/public/providers/ollama.svg new file mode 100644 index 0000000..cc887e3 --- /dev/null +++ b/public/providers/ollama.svg @@ -0,0 +1 @@ +Ollama \ No newline at end of file diff --git a/public/providers/ollamacloud.svg b/public/providers/ollamacloud.svg new file mode 100644 index 0000000..cc887e3 --- /dev/null +++ b/public/providers/ollamacloud.svg @@ -0,0 +1 @@ +Ollama \ No newline at end of file diff --git a/public/providers/openai.svg b/public/providers/openai.svg new file mode 100644 index 0000000..78caf4f --- /dev/null +++ b/public/providers/openai.svg @@ -0,0 +1 @@ +OpenAI \ No newline at end of file diff --git a/public/providers/opencodego.svg b/public/providers/opencodego.svg new file mode 100644 index 0000000..9c4cb89 --- /dev/null +++ b/public/providers/opencodego.svg @@ -0,0 +1 @@ +opencode \ No newline at end of file diff --git a/public/providers/opencodezen.svg b/public/providers/opencodezen.svg new file mode 100644 index 0000000..9c4cb89 --- /dev/null +++ b/public/providers/opencodezen.svg @@ -0,0 +1 @@ +opencode \ No newline at end of file diff --git a/public/providers/openrouter.svg b/public/providers/openrouter.svg new file mode 100644 index 0000000..07aca9a --- /dev/null +++ b/public/providers/openrouter.svg @@ -0,0 +1 @@ +OpenRouter \ No newline at end of file diff --git a/public/providers/perplexity.svg b/public/providers/perplexity.svg new file mode 100644 index 0000000..5f5a5ab --- /dev/null +++ b/public/providers/perplexity.svg @@ -0,0 +1 @@ +Perplexity \ No newline at end of file diff --git a/public/providers/pollinations.svg b/public/providers/pollinations.svg new file mode 100644 index 0000000..65b3b6d --- /dev/null +++ b/public/providers/pollinations.svg @@ -0,0 +1 @@ +Pollinations \ No newline at end of file diff --git a/public/providers/qwen.svg b/public/providers/qwen.svg new file mode 100644 index 0000000..f2d0ada --- /dev/null +++ b/public/providers/qwen.svg @@ -0,0 +1 @@ +Qwen \ No newline at end of file diff --git a/public/providers/qwentoken.svg b/public/providers/qwentoken.svg new file mode 100644 index 0000000..f2d0ada --- /dev/null +++ b/public/providers/qwentoken.svg @@ -0,0 +1 @@ +Qwen \ No newline at end of file diff --git a/public/providers/togetherai.svg b/public/providers/togetherai.svg new file mode 100644 index 0000000..8c9ec11 --- /dev/null +++ b/public/providers/togetherai.svg @@ -0,0 +1 @@ +together.ai \ No newline at end of file diff --git a/public/providers/zai.svg b/public/providers/zai.svg new file mode 100644 index 0000000..04ba2d9 --- /dev/null +++ b/public/providers/zai.svg @@ -0,0 +1 @@ +Z.ai \ No newline at end of file diff --git a/public/providers/zaicoding.svg b/public/providers/zaicoding.svg new file mode 100644 index 0000000..04ba2d9 --- /dev/null +++ b/public/providers/zaicoding.svg @@ -0,0 +1 @@ +Z.ai \ No newline at end of file diff --git a/scripts/add-provider-logo.mjs b/scripts/add-provider-logo.mjs new file mode 100644 index 0000000..49f416d --- /dev/null +++ b/scripts/add-provider-logo.mjs @@ -0,0 +1,33 @@ +#!/usr/bin/env node +// Copia o logo de um provedor de @lobehub/icons-static-svg (devDependency) para +// public/providers/.svg. Processo para adicionar o logo de um novo +// provedor (issue #249): +// 1. Achar o slug em node_modules/@lobehub/icons-static-svg/icons (nome da +// marca, ex.: "openai", "deepseek"). +// 2. node scripts/add-provider-logo.mjs +// 3. Adicionar ao Set em components/dashboard/routing/provider-logos.ts +// +// Prioriza a variante colorida ("-color.svg"); cai para a monocromática +// (".svg", usa fill="currentColor") quando não há versão colorida. +import { copyFileSync, existsSync, mkdirSync } from "node:fs"; +import { join } from "node:path"; + +const [providerId, slug] = process.argv.slice(2); +if (!providerId || !slug) { + console.error("Uso: node scripts/add-provider-logo.mjs "); + process.exit(1); +} + +const iconsDir = "node_modules/@lobehub/icons-static-svg/icons"; +const source = ["-color.svg", ".svg"] + .map((suffix) => join(iconsDir, `${slug}${suffix}`)) + .find(existsSync); + +if (!source) { + console.error(`Nenhum icone encontrado para "${slug}" em @lobehub/icons-static-svg`); + process.exit(1); +} + +mkdirSync("public/providers", { recursive: true }); +copyFileSync(source, join("public/providers", `${providerId}.svg`)); +console.log(`OK: ${source} -> public/providers/${providerId}.svg`);