fix(ui): support model_info.logo_url in Model Management (#50056) (#50371)

This commit is contained in:
ishaan-j-berri 2026-11-06 17:01:56 +08:00 • committed by GitHub
parent e790d9bbb2
commit 14683b502b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 90 additions and 7 deletions

View file

@ -132,6 +132,22 @@ describe("AllModelsTable", () => {
expect(onModelIdClick).toHaveBeenCalledWith("model-1");
});
it("should display the model metadata logo for a gateway-routed model", () => {
const logoUrl = "https://cdn.example.com/gateway.svg";
render(
<AllModelsTable
{...baseProps}
data={[
makeModel({
model_info: { ...baseProps.data[0].model_info, logo_url: logoUrl },
}),
]}
/>,
);
expect(screen.getByRole("img", { name: "openai logo" })).toHaveAttribute("src", logoUrl);
});
it("opens the team detail from the team ID cell", async () => {
const user = userEvent.setup();
const onTeamIdClick = vi.fn();

View file

@ -70,7 +70,7 @@ function ModelInformationCell({ model, displayName }: { model: ModelData; displa
}
>
{model.provider ? (
<ProviderLogo provider={model.provider} className="size-6 shrink-0" />
<ProviderLogo provider={model.provider} logoUrl={model.model_info.logo_url} 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">
-
@ -88,7 +88,9 @@ function ModelInformationCell({ model, displayName }: { model: ModelData; displa
<HoverCardContent align="start" className="w-80">
<div className="flex flex-col gap-3">
<div className="flex items-center gap-2">
{model.provider ? <ProviderLogo provider={model.provider} className="size-4 shrink-0" /> : null}
{model.provider ? (
<ProviderLogo provider={model.provider} logoUrl={model.model_info.logo_url} className="size-4 shrink-0" />
) : null}
<span className="truncate text-xs text-muted-foreground">
{model.provider || t("modelsAndEndpoints.modelsTableColumns.unknownProvider")}
</span>

View file

@ -5,6 +5,7 @@ export interface PerSecondCostTier {
export interface ModelInfo {
id: string;
logo_url?: string | null;
created_at: string;
updated_at: string;
created_by: string;

View file

@ -205,6 +205,22 @@ describe("ModelInfoView", () => {
});
});
it("should display the model metadata logo in the provider details", async () => {
const logoUrl = "https://cdn.example.com/gateway.svg";
mockUseModelsInfo.mockReturnValue({
data: {
data: [{ ...defaultModelData, model_info: { ...defaultModelData.model_info, logo_url: logoUrl } }],
},
isLoading: false,
error: null,
});
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
expect(await screen.findByRole("img", { name: "openai logo" })).toHaveAttribute("src", logoUrl);
expect(screen.getByRole("heading", { name: "openai" })).toBeInTheDocument();
});
it("shows Kubernetes pods for discovery-enabled models", async () => {
mockUseModelsInfo.mockReturnValue({
data: {

View file

@ -45,7 +45,7 @@ import {
tagListCall,
testConnectionRequest,
} from "./networking";
import { Logo } from "@/components/molecules/logo/Logo";
import { ProviderLogo } from "@/components/molecules/models/ProviderLogo";
import { ModelPricingSummary } from "@/components/molecules/models/ModelPricingSummary";
import UpdateModelCredentialsModal from "./update_model_credentials_modal";
import ModelInfoEditForm, { type ModelEditSubmitValues, type TouchedPricingField } from "./ModelInfoEditForm";
@ -626,7 +626,13 @@ export default function ModelInfoView({
<Card className="block p-6">
<p className="text-sm">{t("modelInfoView.overview.provider")}</p>
<div className="mt-2 flex items-center space-x-2">
{modelData.provider && <Logo provider={modelData.provider} className="w-4 h-4" />}
{modelData.provider && (
<ProviderLogo
provider={modelData.provider}
logoUrl={modelData.model_info.logo_url}
className="w-4 h-4"
/>
)}
<h3 className="text-lg font-medium">{modelData.provider || t("modelInfoView.notSet")}</h3>
</div>
</Card>

View file

@ -5,10 +5,15 @@ import { ProviderLogo } from "./ProviderLogo";
import * as providerInfoHelpers from "../../provider_info_helpers";
vi.mock("../../provider_info_helpers");
vi.mock("@/lib/serverRootPath", () => ({ serverRootPath: "/litellm" }));
describe("ProviderLogo", () => {
beforeEach(() => {
vi.resetAllMocks();
vi.mocked(providerInfoHelpers.getProviderLogoAndName).mockReturnValue({
logo: "/assets/provider-logo.svg",
displayName: "Test Provider",
});
});
it("should render the component", () => {
@ -20,6 +25,39 @@ describe("ProviderLogo", () => {
expect(screen.getByText("t")).toBeInTheDocument();
});
it.each([
["https://cdn.example.com/gateway.svg", "https://cdn.example.com/gateway.svg"],
["/ui/assets/logos/gateway.svg", "/litellm/ui/assets/logos/gateway.svg"],
])("should display a custom logo from %s", (logoUrl, expectedSrc) => {
render(<ProviderLogo provider="test" logoUrl={logoUrl} />);
expect(screen.getByRole("img", { name: "test logo" })).toHaveAttribute("src", expectedSrc);
});
it.each([undefined, null, ""])("should use the provider logo when the override is %s", (logoUrl) => {
render(<ProviderLogo provider="test" logoUrl={logoUrl} />);
expect(screen.getByRole("img", { name: "test logo" })).toHaveAttribute("src", "/assets/provider-logo.svg");
});
it("should recover when a failed custom logo is replaced", () => {
const { rerender } = render(<ProviderLogo provider="test" logoUrl="https://cdn.example.com/broken.svg" />);
act(() => {
fireEvent.error(screen.getByRole("img", { name: "test logo" }));
});
expect(screen.queryByRole("img")).not.toBeInTheDocument();
expect(screen.getByText("t")).toBeInTheDocument();
rerender(<ProviderLogo provider="test" logoUrl="https://cdn.example.com/gateway.svg" />);
expect(screen.getByRole("img", { name: "test logo" })).toHaveAttribute(
"src",
"https://cdn.example.com/gateway.svg",
);
});
it("should show fallback when image fails to load", () => {
vi.mocked(providerInfoHelpers.getProviderLogoAndName).mockReturnValue({
logo: "/path/to/logo.png",

View file

@ -3,9 +3,13 @@ import { Logo } from "@/components/molecules/logo/Logo";
interface ProviderLogoProps {
provider: string;
logoUrl?: string | null;
className?: string;
}
export const ProviderLogo: React.FC<ProviderLogoProps> = ({ provider, className = "w-4 h-4" }) => (
<Logo provider={provider} className={className} />
);
export const ProviderLogo: React.FC<ProviderLogoProps> = ({ provider, logoUrl, className = "w-4 h-4" }) =>
logoUrl ? (
<Logo src={logoUrl} label={provider} className={className} />
) : (
<Logo provider={provider} className={className} />
);