feat(ui): show the model family logo in the models table

This commit is contained in:
Devin AI 2026-07-29 15:08:32 +00:00
parent c274cf321c
commit 0917457b9b
5 changed files with 94 additions and 1 deletions

View file

@ -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 ? (
<ProviderLogo provider={model.provider} className="size-6 shrink-0" />
<ModelLogo model={model.litellm_model_name} provider={model.provider} className="size-6 shrink-0" />
) : (
<span className="flex size-6 shrink-0 items-center justify-center rounded-md bg-muted text-xs text-muted-foreground">
-

View file

@ -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(<ModelLogo model="scaleway/qwen3-235b-a22b-instruct-2507" provider="openrouter" />);
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(<ModelLogo model="openrouter/some-unknown-model" provider="openrouter" />);
const img = screen.getByRole("img", { name: "openrouter logo" });
expect(img.getAttribute("src")).toBe(getProviderLogoAndName("openrouter").logo);
});
});

View file

@ -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<ModelLogoProps> = ({ model, provider, className = "w-4 h-4" }) => (
<Logo src={getModelLogo(model, provider)} label={provider} className={className} />
);

View file

@ -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");

View file

@ -400,6 +400,26 @@ export const getProviderLogoAndName = (providerValue: string): { logo: string; d
return { logo, displayName };
};
const modelFamilyLogoMap: ReadonlyArray<readonly [RegExp, string]> = [
[/[/.:](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";