From 0917457b9b1b1c6020f3bd2ce3f560291c6192eb Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 29 Jul 2026 15:08:32 +0000 Subject: [PATCH] feat(ui): show the model family logo in the models table --- .../components/ModelsTableColumns.tsx | 3 +- .../molecules/models/ModelLogo.test.tsx | 19 +++++++++ .../components/molecules/models/ModelLogo.tsx | 13 ++++++ .../components/provider_info_helpers.test.tsx | 40 +++++++++++++++++++ .../src/components/provider_info_helpers.tsx | 20 ++++++++++ 5 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 ui/litellm-dashboard/src/components/molecules/models/ModelLogo.test.tsx create mode 100644 ui/litellm-dashboard/src/components/molecules/models/ModelLogo.tsx diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/ModelsTableColumns.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/ModelsTableColumns.tsx index 93ee6d9f0ab..ada8c52d798 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/ModelsTableColumns.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/ModelsTableColumns.tsx @@ -3,6 +3,7 @@ import { ColumnDef } from "@tanstack/react-table"; import { Copy, Info, Loader2, Pencil, RefreshCw, Trash2 } from "lucide-react"; +import { ModelLogo } from "@/components/molecules/models/ModelLogo"; import { ProviderLogo } from "@/components/molecules/models/ProviderLogo"; import { ModelData } from "@/components/model_dashboard/types"; import { DataTableSortHeader } from "@/components/shared/DataTable"; @@ -52,7 +53,7 @@ function ModelInformationCell({ model, displayName }: { model: ModelData; displa } > {model.provider ? ( - + ) : ( - diff --git a/ui/litellm-dashboard/src/components/molecules/models/ModelLogo.test.tsx b/ui/litellm-dashboard/src/components/molecules/models/ModelLogo.test.tsx new file mode 100644 index 00000000000..fe6fa9da271 --- /dev/null +++ b/ui/litellm-dashboard/src/components/molecules/models/ModelLogo.test.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import { describe, it, expect } from "vitest"; +import { screen, render } from "@testing-library/react"; +import { ModelLogo } from "./ModelLogo"; +import { getProviderLogoAndName } from "../../provider_info_helpers"; + +describe("ModelLogo", () => { + it("renders the model family logo instead of the aggregating provider logo", () => { + render(); + const img = screen.getByRole("img", { name: "openrouter logo" }); + expect(img.getAttribute("src")).toContain("qwen"); + }); + + it("renders the provider logo when the model names no known family", () => { + render(); + const img = screen.getByRole("img", { name: "openrouter logo" }); + expect(img.getAttribute("src")).toBe(getProviderLogoAndName("openrouter").logo); + }); +}); diff --git a/ui/litellm-dashboard/src/components/molecules/models/ModelLogo.tsx b/ui/litellm-dashboard/src/components/molecules/models/ModelLogo.tsx new file mode 100644 index 00000000000..e661e4dfea0 --- /dev/null +++ b/ui/litellm-dashboard/src/components/molecules/models/ModelLogo.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import { Logo } from "@/components/molecules/logo/Logo"; +import { getModelLogo } from "@/components/provider_info_helpers"; + +interface ModelLogoProps { + model: string | undefined; + provider: string; + className?: string; +} + +export const ModelLogo: React.FC = ({ model, provider, className = "w-4 h-4" }) => ( + +); diff --git a/ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx b/ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx index 777cdc62987..4bc414de3f5 100644 --- a/ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx +++ b/ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx @@ -1,6 +1,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { Providers, + getModelLogo, getPlaceholder, getProviderLogoAndName, getProviderModels, @@ -152,6 +153,45 @@ describe("provider_info_helpers", () => { }); }); + describe("getModelLogo", () => { + it.each([ + ["scaleway/mistral-small-3.2-24b-instruct-2506", "mistral"], + ["scaleway/qwen3-235b-a22b-instruct-2507", "qwen"], + ["scaleway/gemma-3-27b-it", "google"], + ["scaleway/llama-3.3-70b-instruct", "meta_llama"], + ["scaleway/gpt-oss-120b", "openai_small"], + ["scaleway/whisper-large-v3", "openai_small"], + ["scaleway/deepseek-r1-distill-llama-70b", "deepseek"], + ["openrouter/anthropic/claude-sonnet-4.5", "anthropic"], + ["openrouter/moonshotai/kimi-k2", "moonshot"], + ["bedrock/mistral.mistral-large-2407-v1:0", "mistral"], + ["bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0", "anthropic"], + ["bedrock/meta.llama3-3-70b-instruct-v1:0", "meta_llama"], + ["together_ai/deepseek-ai/DeepSeek-V3", "deepseek"], + ["groq/openai/gpt-oss-20b", "openai_small"], + ])("resolves %s to the %s family logo", (model, expectedLogoFragment) => { + expect(getModelLogo(model, "openrouter")).toContain(expectedLogoFragment); + }); + + it("falls back to the provider logo when the model names no known family", () => { + expect(getModelLogo("scaleway/some-unknown-model", "openrouter")).toBe(getProviderLogoAndName("openrouter").logo); + }); + + it("falls back to the provider logo when there is no model", () => { + expect(getModelLogo(undefined, "bedrock")).toBe(getProviderLogoAndName("bedrock").logo); + }); + + it("keeps the provider logo for a bare deployment id, so azure deployments stay on the azure mark", () => { + const azureLogo = getProviderLogoAndName("azure").logo; + expect(getModelLogo("gpt-4o", "azure")).toBe(azureLogo); + expect(getModelLogo("my-deployment", "azure")).toBe(azureLogo); + }); + + it("keeps the provider logo for first-party providers whose models are unprefixed", () => { + expect(getModelLogo("claude-sonnet-4-5", "anthropic")).toBe(getProviderLogoAndName("anthropic").logo); + }); + }); + describe("getPlaceholder", () => { it("should return aiml placeholder for AIML provider", () => { expect(getPlaceholder(Providers.AIML)).toBe("aiml/flux-pro/v1.1"); diff --git a/ui/litellm-dashboard/src/components/provider_info_helpers.tsx b/ui/litellm-dashboard/src/components/provider_info_helpers.tsx index fa6b3c79230..cc42c17ae2a 100644 --- a/ui/litellm-dashboard/src/components/provider_info_helpers.tsx +++ b/ui/litellm-dashboard/src/components/provider_info_helpers.tsx @@ -400,6 +400,26 @@ export const getProviderLogoAndName = (providerValue: string): { logo: string; d return { logo, displayName }; }; +const modelFamilyLogoMap: ReadonlyArray = [ + [/[/.:](mistral|ministral|codestral|pixtral|devstral|voxtral|magistral)/i, mistralLogo.src], + [/[/.:](qwen|qwq)/i, qwenLogo.src], + [/[/.:](gemma|gemini)/i, googleLogo.src], + [/[/.:](llama)/i, metaLlamaLogo.src], + [/[/.:](deepseek)/i, deepseekLogo.src], + [/[/.:](claude)/i, anthropicLogo.src], + [/[/.:](gpt|whisper|o[134](-|$))/i, openaiSmallLogo.src], + [/[/.:](command)/i, cohereLogo.src], + [/[/.:](grok)/i, xaiLogo.src], + [/[/.:](kimi|moonshot)/i, moonshotLogo.src], + [/[/.:](minimax)/i, minimaxLogo.src], + [/[/.:](nova-)/i, bedrockLogo.src], +]; + +export const getModelLogo = (model: string | undefined, provider: string): string => { + const familyLogo = model ? modelFamilyLogoMap.find(([motif]) => motif.test(model))?.[1] : undefined; + return resolveLogoSrc(familyLogo) ?? getProviderLogoAndName(provider).logo; +}; + export const getPlaceholder = (selectedProvider: string): string => { if (selectedProvider === Providers.AIML) { return "aiml/flux-pro/v1.1";