diff --git a/ui/litellm-dashboard/src/components/molecules/logo/Logo.test.tsx b/ui/litellm-dashboard/src/components/molecules/logo/Logo.test.tsx
index 95221f35b8d..c0b52e03a30 100644
--- a/ui/litellm-dashboard/src/components/molecules/logo/Logo.test.tsx
+++ b/ui/litellm-dashboard/src/components/molecules/logo/Logo.test.tsx
@@ -20,8 +20,8 @@ describe("Logo", () => {
expect(screen.queryByRole("img")).not.toBeInTheDocument();
});
- it("renders a dash avatar when neither provider nor src is given", () => {
- render();
+ it("renders a dash avatar when src is empty and the label has no characters", () => {
+ render();
expect(screen.getByText("-")).toBeInTheDocument();
expect(screen.queryByRole("img")).not.toBeInTheDocument();
});
diff --git a/ui/litellm-dashboard/src/components/molecules/logo/Logo.tsx b/ui/litellm-dashboard/src/components/molecules/logo/Logo.tsx
index 6f18c89ae8e..f5fb1f0805a 100644
--- a/ui/litellm-dashboard/src/components/molecules/logo/Logo.tsx
+++ b/ui/litellm-dashboard/src/components/molecules/logo/Logo.tsx
@@ -2,12 +2,10 @@ import React, { useState } from "react";
import { getProviderLogoAndName } from "@/components/provider_info_helpers";
import { resolveLogoSrc } from "@/lib/assetPaths";
-interface LogoProps {
- provider?: string;
- src?: string | null;
- label?: string;
- className?: string;
-}
+type LogoProps = { className?: string } & (
+ | { provider: string; src?: never; label?: string }
+ | { provider?: never; src: string | null | undefined; label: string }
+);
export const Logo: React.FC = ({ provider, src, label, className = "w-4 h-4" }) => {
const [erroredSrc, setErroredSrc] = useState(null);
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 966bd4e262c..777cdc62987 100644
--- a/ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx
+++ b/ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx
@@ -114,13 +114,35 @@ describe("provider_info_helpers", () => {
});
describe("provider logo bundled assets", () => {
- it("should expose every provider logo as a truthy bundled URL, never a raw /ui/assets path", () => {
- const logos = Object.values(providerLogoMap);
- expect(logos.length).toBeGreaterThan(0);
- logos.forEach((logo) => {
- expect(typeof logo).toBe("string");
- expect(logo.length).toBeGreaterThan(0);
- expect(logo.startsWith("/ui/assets/")).toBe(false);
+ it("should map every provider to a bundled logo except the known logoless set, never a raw /ui/assets path", () => {
+ const knownLogolessProviders = [
+ Providers.AUTO_ROUTER,
+ Providers.BYTEZ,
+ Providers.CLARIFAI,
+ Providers.COMPACTIFAI,
+ Providers.DATAROBOT,
+ Providers.DOCKER_MODEL_RUNNER,
+ Providers.DOTPROMPT,
+ Providers.EMPOWER,
+ Providers.GALADRIEL,
+ Providers.GradientAI,
+ Providers.HEROKU,
+ Providers.LEMONADE,
+ Providers.LLAMAFILE,
+ Providers.MARITALK,
+ Providers.NLP_CLOUD,
+ Providers.NSCALE,
+ Providers.OVHCLOUD,
+ Providers.PETALS,
+ Providers.PG_VECTOR,
+ Providers.PREDIBASE,
+ Providers.WANDB,
+ Providers.ZAI,
+ ];
+ const logolessProviders = Object.values(Providers).filter((provider) => !providerLogoMap[provider]);
+ expect([...logolessProviders].sort()).toEqual([...knownLogolessProviders].sort());
+ Object.values(providerLogoMap).forEach((logo) => {
+ expect(logo?.startsWith("/ui/assets/")).toBe(false);
});
});
diff --git a/ui/litellm-dashboard/src/components/provider_info_helpers.tsx b/ui/litellm-dashboard/src/components/provider_info_helpers.tsx
index f8be4511e3a..fa6b3c79230 100644
--- a/ui/litellm-dashboard/src/components/provider_info_helpers.tsx
+++ b/ui/litellm-dashboard/src/components/provider_info_helpers.tsx
@@ -283,7 +283,7 @@ export const provider_map: Record = {
const standaloneSubproviderSlugs = new Set(["bedrock_mantle"]);
-export const providerLogoMap: Record = {
+export const providerLogoMap: Partial> = {
[Providers.A2A_Agent]: a2aAgentLogo.src,
[Providers.AI21]: ai21Logo.src,
[Providers.AI21_CHAT]: ai21Logo.src,
@@ -395,7 +395,7 @@ export const getProviderLogoAndName = (providerValue: string): { logo: string; d
// Get the display name from Providers enum and logo from map
const displayName = Providers[enumKey as keyof typeof Providers];
- const logo = resolveLogoSrc(providerLogoMap[displayName as keyof typeof providerLogoMap]) ?? "";
+ const logo = resolveLogoSrc(providerLogoMap[displayName]) ?? "";
return { logo, displayName };
};
diff --git a/ui/litellm-dashboard/src/components/vector_store_providers.tsx b/ui/litellm-dashboard/src/components/vector_store_providers.tsx
index 03878d5d6f1..d8ee2eb4720 100644
--- a/ui/litellm-dashboard/src/components/vector_store_providers.tsx
+++ b/ui/litellm-dashboard/src/components/vector_store_providers.tsx
@@ -26,12 +26,12 @@ export const vectorStoreProviderMap: Record = {
};
export const vectorStoreProviderLogoMap: Record = {
- [VectorStoreProviders.Bedrock]: providerLogoMap[Providers.Bedrock],
+ [VectorStoreProviders.Bedrock]: providerLogoMap[Providers.Bedrock] ?? "",
[VectorStoreProviders.PgVector]: postgresqlLogo.src,
- [VectorStoreProviders.VertexRagEngine]: providerLogoMap[Providers.Vertex_AI],
- [VectorStoreProviders.VertexAiSearch]: providerLogoMap[Providers.Vertex_AI],
- [VectorStoreProviders.OpenAI]: providerLogoMap[Providers.OpenAI],
- [VectorStoreProviders.Azure]: providerLogoMap[Providers.Azure],
+ [VectorStoreProviders.VertexRagEngine]: providerLogoMap[Providers.Vertex_AI] ?? "",
+ [VectorStoreProviders.VertexAiSearch]: providerLogoMap[Providers.Vertex_AI] ?? "",
+ [VectorStoreProviders.OpenAI]: providerLogoMap[Providers.OpenAI] ?? "",
+ [VectorStoreProviders.Azure]: providerLogoMap[Providers.Azure] ?? "",
[VectorStoreProviders.Milvus]: milvusLogo.src,
[VectorStoreProviders.S3Vectors]: s3VectorLogo.src,
};
diff --git a/ui/litellm-dashboard/src/lib/assetPaths.test.ts b/ui/litellm-dashboard/src/lib/assetPaths.test.ts
index 0ca6c055a0a..adadef5d274 100644
--- a/ui/litellm-dashboard/src/lib/assetPaths.test.ts
+++ b/ui/litellm-dashboard/src/lib/assetPaths.test.ts
@@ -48,7 +48,7 @@ describe("resolveLogoSrc", () => {
expect(resolveLogoSrc("//cdn.example.com/x.svg")).toBe("//cdn.example.com/x.svg");
});
- it("passes bundled /_next/ asset URLs through untouched even under a sub-path mount", async () => {
+ it("passes bundled /_next/static/ asset URLs through untouched even under a sub-path mount", async () => {
const { resolveLogoSrc } = await importWithRoot("/litellm");
expect(resolveLogoSrc("/_next/static/media/openai_small.abc123.svg")).toBe(
"/_next/static/media/openai_small.abc123.svg",
@@ -56,6 +56,14 @@ describe("resolveLogoSrc", () => {
expect(resolveLogoSrc("/litellm-asset-prefix/_next/static/media/openai_small.abc123.svg")).toBe(
"/litellm-asset-prefix/_next/static/media/openai_small.abc123.svg",
);
+ expect(resolveLogoSrc("/litellm/_next/static/media/openai_small.abc123.svg")).toBe(
+ "/litellm/_next/static/media/openai_small.abc123.svg",
+ );
+ });
+
+ it("still prefixes a backend path that merely contains a /_next/ lookalike segment", async () => {
+ const { resolveLogoSrc } = await importWithRoot("/litellm");
+ expect(resolveLogoSrc("/ui/assets/logos/_next/logo.svg")).toBe("/litellm/ui/assets/logos/_next/logo.svg");
});
it("roots a local asset path using the live server root path", async () => {
diff --git a/ui/litellm-dashboard/src/lib/assetPaths.ts b/ui/litellm-dashboard/src/lib/assetPaths.ts
index ec359ff2d86..abce69127cb 100644
--- a/ui/litellm-dashboard/src/lib/assetPaths.ts
+++ b/ui/litellm-dashboard/src/lib/assetPaths.ts
@@ -23,7 +23,7 @@ export const withServerRoot = (path: string, root: string): string => {
export const resolveLogoSrc = (value: string | null | undefined, root: string = serverRootPath): string | undefined => {
if (!value) return undefined;
if (EXTERNAL_SRC.test(value)) return value;
- if (value.includes("/_next/")) return value;
+ if (value.includes("/_next/static/")) return value;
const prefix = normalizeRootPath(root);
if (prefix && (value === prefix || value.startsWith(`${prefix}/`))) return value;
return withServerRoot(value, root);