diff --git a/ui/litellm-dashboard/public/assets/logos/ai21.svg b/ui/litellm-dashboard/public/assets/logos/ai21.svg
index 7e62a9517af..3c8c75e6d6f 100644
--- a/ui/litellm-dashboard/public/assets/logos/ai21.svg
+++ b/ui/litellm-dashboard/public/assets/logos/ai21.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/ui/litellm-dashboard/public/assets/logos/soniox.svg b/ui/litellm-dashboard/public/assets/logos/soniox.svg
index 7b7408401c4..7a1376df1a8 100644
--- a/ui/litellm-dashboard/public/assets/logos/soniox.svg
+++ b/ui/litellm-dashboard/public/assets/logos/soniox.svg
@@ -1 +1 @@
-
+
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/add_agent_form.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/add_agent_form.test.tsx
new file mode 100644
index 00000000000..767e7c2ae5f
--- /dev/null
+++ b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/add_agent_form.test.tsx
@@ -0,0 +1,89 @@
+import React from "react";
+import { render, screen, fireEvent, within } from "@testing-library/react";
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import AddAgentForm from "./add_agent_form";
+import * as networking from "@/components/networking";
+import type { AgentCreateInfo } from "@/components/networking";
+
+vi.mock("@/components/networking", () => ({
+ createAgentCall: vi.fn(),
+ getAgentCreateMetadata: vi.fn(),
+ getAgentsList: vi.fn(),
+ keyCreateForAgentCall: vi.fn(),
+ keyListCall: vi.fn(),
+ keyUpdateCall: vi.fn(),
+ modelAvailableCall: vi.fn(),
+}));
+
+vi.mock("./agent_card_discovery", () => ({
+ default: () =>
,
+}));
+
+vi.mock("./agent_form_fields", () => ({
+ default: () => ,
+}));
+
+const a2aInfo: AgentCreateInfo = {
+ agent_type: "a2a",
+ agent_type_display_name: "A2A Agent",
+ description: "Agent-to-agent protocol",
+ logo_url: "/ui/assets/logos/a2a_agent.png",
+ credential_fields: [],
+ use_a2a_form_fields: true,
+};
+
+const renderForm = () =>
+ render();
+
+describe("AddAgentForm logos", () => {
+ beforeEach(() => {
+ vi.mocked(networking.getAgentCreateMetadata).mockReset().mockResolvedValue([a2aInfo]);
+ vi.mocked(networking.getAgentsList).mockReset().mockResolvedValue({ agents: [] });
+ vi.mocked(networking.keyListCall).mockReset().mockResolvedValue({ keys: [] });
+ vi.mocked(networking.modelAvailableCall).mockReset().mockResolvedValue({ data: [] });
+ });
+
+ it("renders the modal title and agent type selection logos as images from logo_url", async () => {
+ renderForm();
+
+ const titleLogo = await screen.findByAltText("Agent logo");
+ expect(titleLogo).toBeInstanceOf(HTMLImageElement);
+ expect(titleLogo).toHaveAttribute("src", expect.stringContaining("assets/logos/a2a_agent.png"));
+
+ const selectionLogo = await screen.findByAltText("A2A Agent logo");
+ expect(selectionLogo).toBeInstanceOf(HTMLImageElement);
+ expect(selectionLogo).toHaveAttribute("src", expect.stringContaining("assets/logos/a2a_agent.png"));
+ });
+
+ it("renders the option logo when the agent type dropdown is opened", async () => {
+ renderForm();
+
+ await screen.findByAltText("A2A Agent logo");
+ fireEvent.mouseDown(screen.getByRole("combobox"));
+
+ const optionLogos = await screen.findAllByAltText("A2A Agent logo");
+ expect(optionLogos.length).toBeGreaterThanOrEqual(2);
+ optionLogos.forEach((img) => {
+ expect(img).toHaveAttribute("src", expect.stringContaining("assets/logos/a2a_agent.png"));
+ });
+ });
+
+ it("swaps a failing logo for a letter avatar and warns with the url", async () => {
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
+ renderForm();
+
+ const titleLogo = await screen.findByAltText("Agent logo");
+ const header = screen.getByText("Add New Agent").parentElement!;
+ fireEvent.error(titleLogo);
+
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("assets/logos/a2a_agent.png"));
+ expect(screen.queryByAltText("Agent logo")).not.toBeInTheDocument();
+ expect(within(header).getByText("A")).toBeInTheDocument();
+
+ const selectionLogo = screen.getByAltText("A2A Agent logo");
+ fireEvent.error(selectionLogo);
+ expect(screen.queryByAltText("A2A Agent logo")).not.toBeInTheDocument();
+ expect(warnSpy).toHaveBeenCalledTimes(2);
+ warnSpy.mockRestore();
+ });
+});
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/add_agent_form.tsx b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/add_agent_form.tsx
index 8ca2b5afe16..e35388b78da 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/add_agent_form.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/add_agent_form.tsx
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from "react";
import { Modal, Form, Select, Input, Steps, Radio, Tag, Divider, Switch, InputNumber, Collapse } from "antd";
import MessageManager from "@/components/molecules/message_manager";
-import { resolveLogoSrc } from "@/lib/assetPaths";
+import { Logo } from "@/components/molecules/logo/Logo";
import { Button } from "@tremor/react";
import { CheckCircleFilled, KeyOutlined, RobotOutlined, AppstoreOutlined, InfoCircleOutlined } from "@ant-design/icons";
import CreatedKeyDisplay from "@/components/shared/CreatedKeyDisplay";
@@ -712,17 +712,13 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok
value={info.agent_type}
label={
-
)
+
{info.agent_type_display_name}
}
>
-
)
+
{info.agent_type_display_name}
{info.description &&
{info.description}
}
@@ -948,7 +944,7 @@ const AddAgentForm: React.FC
= ({ visible, onClose, accessTok
title={
{selectedLogo && currentStep < 1 && (
-
})
+
)}
Add New Agent
diff --git a/ui/litellm-dashboard/src/components/add_model/AddModelForm.tsx b/ui/litellm-dashboard/src/components/add_model/AddModelForm.tsx
index 73cfdce5263..a99f8048dca 100644
--- a/ui/litellm-dashboard/src/components/add_model/AddModelForm.tsx
+++ b/ui/litellm-dashboard/src/components/add_model/AddModelForm.tsx
@@ -10,7 +10,7 @@ import React, { useEffect, useMemo, useState } from "react";
import TeamDropdown from "../common_components/team_dropdown";
import type { Team } from "../key_team_helpers/key_list";
import { type CredentialItem, type ProviderCreateInfo, modelAvailableCall } from "../networking";
-import { Providers, providerLogoMap } from "../provider_info_helpers";
+import { Providers } from "../provider_info_helpers";
import { ProviderLogo } from "../molecules/models/ProviderLogo";
import AdvancedSettings from "./advanced_settings";
import ConditionalPublicModelName from "./conditional_public_model_name";
@@ -181,7 +181,6 @@ const AddModelForm: React.FC = ({
{sortedProviderMetadata.map((providerInfo) => {
const displayName = providerInfo.provider_display_name;
const providerKey = providerInfo.provider;
- const logoSrc = providerLogoMap[displayName] ?? "";
return (
diff --git a/ui/litellm-dashboard/src/components/molecules/logo/Logo.test.tsx b/ui/litellm-dashboard/src/components/molecules/logo/Logo.test.tsx
new file mode 100644
index 00000000000..95221f35b8d
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/molecules/logo/Logo.test.tsx
@@ -0,0 +1,73 @@
+import React from "react";
+import { describe, expect, it, vi } from "vitest";
+import { act, fireEvent, render, screen } from "@testing-library/react";
+import { Logo } from "./Logo";
+import { Providers, providerLogoMap } from "@/components/provider_info_helpers";
+
+vi.mock("@/lib/serverRootPath", () => ({ serverRootPath: "/litellm" }));
+
+describe("Logo", () => {
+ it("renders the bundled logo untouched by the server root path for a known provider", () => {
+ render();
+ const img = screen.getByRole("img", { name: "openai logo" });
+ expect(img.getAttribute("src")).toBe(providerLogoMap[Providers.OpenAI]);
+ expect(img.getAttribute("src")).toContain("openai_small");
+ });
+
+ it("renders a letter avatar and no img for an unknown provider", () => {
+ render();
+ expect(screen.getByText("u")).toBeInTheDocument();
+ expect(screen.queryByRole("img")).not.toBeInTheDocument();
+ });
+
+ it("renders a dash avatar when neither provider nor src is given", () => {
+ render();
+ expect(screen.getByText("-")).toBeInTheDocument();
+ expect(screen.queryByRole("img")).not.toBeInTheDocument();
+ });
+
+ it("resolves a backend asset path through the server root path in src mode", () => {
+ render();
+ const img = screen.getByRole("img", { name: "GitHub logo" });
+ expect(img.getAttribute("src")).toBe("/litellm/ui/assets/logos/github.svg");
+ });
+
+ it("passes an external https URL through untouched in src mode", () => {
+ render();
+ expect(screen.getByRole("img").getAttribute("src")).toBe("https://cdn.example.com/logo.png");
+ });
+
+ it("swaps to the letter avatar and warns with the failing URL on image error", () => {
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
+ render();
+ const img = screen.getByRole("img", { name: "GitHub logo" });
+
+ act(() => {
+ fireEvent.error(img);
+ });
+
+ expect(screen.queryByRole("img")).not.toBeInTheDocument();
+ expect(screen.getByText("G")).toBeInTheDocument();
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("/litellm/ui/assets/logos/github.svg"));
+ warnSpy.mockRestore();
+ });
+
+ it("retries with a new src after a previous src errored", () => {
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
+ const { rerender } = render();
+
+ act(() => {
+ fireEvent.error(screen.getByRole("img", { name: "Agent logo" }));
+ });
+ expect(screen.queryByRole("img")).not.toBeInTheDocument();
+
+ rerender();
+ const img = screen.getByRole("img", { name: "Agent logo" });
+ expect(img.getAttribute("src")).toBe("/litellm/ui/assets/logos/github.svg");
+
+ rerender();
+ expect(screen.queryByRole("img")).not.toBeInTheDocument();
+ expect(screen.getByText("A")).toBeInTheDocument();
+ warnSpy.mockRestore();
+ });
+});
diff --git a/ui/litellm-dashboard/src/components/molecules/logo/Logo.tsx b/ui/litellm-dashboard/src/components/molecules/logo/Logo.tsx
new file mode 100644
index 00000000000..6f18c89ae8e
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/molecules/logo/Logo.tsx
@@ -0,0 +1,36 @@
+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;
+}
+
+export const Logo: React.FC = ({ provider, src, label, className = "w-4 h-4" }) => {
+ const [erroredSrc, setErroredSrc] = useState(null);
+ const resolvedSrc = provider !== undefined ? getProviderLogoAndName(provider).logo : resolveLogoSrc(src) ?? "";
+ const name = label ?? provider ?? "";
+
+ if (erroredSrc === resolvedSrc || !resolvedSrc) {
+ return (
+
+ {name.charAt(0) || "-"}
+
+ );
+ }
+
+ return (
+
{
+ console.warn(`Logo failed to load: ${resolvedSrc}`);
+ setErroredSrc(resolvedSrc);
+ }}
+ />
+ );
+};
diff --git a/ui/litellm-dashboard/src/components/molecules/models/ProviderLogo.tsx b/ui/litellm-dashboard/src/components/molecules/models/ProviderLogo.tsx
index 4a9da15e333..4bc11126ae4 100644
--- a/ui/litellm-dashboard/src/components/molecules/models/ProviderLogo.tsx
+++ b/ui/litellm-dashboard/src/components/molecules/models/ProviderLogo.tsx
@@ -1,24 +1,11 @@
-import React, { useState } from "react";
-import { getProviderLogoAndName } from "../../provider_info_helpers";
+import React from "react";
+import { Logo } from "@/components/molecules/logo/Logo";
interface ProviderLogoProps {
provider: string;
className?: string;
}
-export const ProviderLogo: React.FC = ({ provider, className = "w-4 h-4" }) => {
- const [hasError, setHasError] = useState(false);
- const { logo } = getProviderLogoAndName(provider);
-
- const showFallback = hasError || !logo;
-
- if (showFallback) {
- return (
-
- {provider?.charAt(0) || "-"}
-
- );
- }
-
- return
setHasError(true)} />;
-};
+export const ProviderLogo: React.FC = ({ 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 6e19de4a6cc..966bd4e262c 100644
--- a/ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx
+++ b/ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx
@@ -113,24 +113,20 @@ describe("provider_info_helpers", () => {
});
});
- describe("provider logo asset paths", () => {
- // Regression: a relative "../ui/assets/logos/" base resolved to
- // "/ui/ui/assets/logos/..." (404) on the public model hub at
- // /ui/model_hub_table/, which sits a level below the /ui/ SPA. Root-absolute
- // paths resolve correctly at any route depth.
- it("should expose every provider logo as a root-absolute /ui path", () => {
+ 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(logo.startsWith("/ui/assets/logos/")).toBe(true);
- expect(logo).not.toContain("../");
+ expect(typeof logo).toBe("string");
+ expect(logo.length).toBeGreaterThan(0);
+ expect(logo.startsWith("/ui/assets/")).toBe(false);
});
});
- it("should resolve a provider logo to a root-absolute path via getProviderLogoAndName", () => {
+ it("should resolve a provider to its own bundled logo via getProviderLogoAndName", () => {
const { logo } = getProviderLogoAndName("openai");
- expect(logo.startsWith("/ui/assets/logos/")).toBe(true);
- expect(logo).not.toContain("../");
+ expect(logo).toContain("openai_small");
});
});
@@ -430,20 +426,19 @@ describe("getProviderLogoAndName under a custom server_root_path", () => {
vi.doUnmock("@/lib/serverRootPath");
});
- // Regression: under SERVER_ROOT_PATH=/litellm the logo must be requested at
- // /litellm/ui/assets/logos/... A bare /ui/... path is served off the root and
- // 404s behind the reverse proxy.
- it("prefixes the server root path onto the resolved logo", async () => {
+ it("returns the bundled logo URL untouched under a sub-path mount", async () => {
vi.resetModules();
vi.doMock("@/lib/serverRootPath", () => ({ serverRootPath: "/litellm" }));
- const { getProviderLogoAndName } = await import("./provider_info_helpers");
- expect(getProviderLogoAndName("openai").logo).toBe("/litellm/ui/assets/logos/openai_small.svg");
+ const helpers = await import("./provider_info_helpers");
+ const { logo } = helpers.getProviderLogoAndName("openai");
+ expect(logo).toBe(helpers.providerLogoMap[helpers.Providers.OpenAI]);
+ expect(logo.startsWith("/litellm")).toBe(false);
});
- it("leaves the logo at /ui/... when mounted at the root", async () => {
+ it("returns the bundled logo URL untouched at the root mount", async () => {
vi.resetModules();
vi.doMock("@/lib/serverRootPath", () => ({ serverRootPath: "/" }));
- const { getProviderLogoAndName } = await import("./provider_info_helpers");
- expect(getProviderLogoAndName("openai").logo).toBe("/ui/assets/logos/openai_small.svg");
+ const helpers = await import("./provider_info_helpers");
+ expect(helpers.getProviderLogoAndName("openai").logo).toBe(helpers.providerLogoMap[helpers.Providers.OpenAI]);
});
});
diff --git a/ui/litellm-dashboard/src/components/provider_info_helpers.tsx b/ui/litellm-dashboard/src/components/provider_info_helpers.tsx
index c955087b40a..f8be4511e3a 100644
--- a/ui/litellm-dashboard/src/components/provider_info_helpers.tsx
+++ b/ui/litellm-dashboard/src/components/provider_info_helpers.tsx
@@ -1,4 +1,67 @@
import { resolveLogoSrc } from "@/lib/assetPaths";
+import a2aAgentLogo from "../../public/assets/logos/a2a_agent.png";
+import ai21Logo from "../../public/assets/logos/ai21.svg";
+import aimlApiLogo from "../../public/assets/logos/aiml_api.svg";
+import anthropicLogo from "../../public/assets/logos/anthropic.svg";
+import assemblyaiSmallLogo from "../../public/assets/logos/assemblyai_small.png";
+import basetenLogo from "../../public/assets/logos/baseten.svg";
+import bedrockLogo from "../../public/assets/logos/bedrock.svg";
+import cerebrasLogo from "../../public/assets/logos/cerebras.svg";
+import cloudflareLogo from "../../public/assets/logos/cloudflare.svg";
+import cohereLogo from "../../public/assets/logos/cohere.svg";
+import cometapiLogo from "../../public/assets/logos/cometapi.svg";
+import cursorLogo from "../../public/assets/logos/cursor.svg";
+import databricksLogo from "../../public/assets/logos/databricks.svg";
+import deepgramLogo from "../../public/assets/logos/deepgram.png";
+import deepinfraLogo from "../../public/assets/logos/deepinfra.png";
+import deepseekLogo from "../../public/assets/logos/deepseek.svg";
+import elevenlabsLogo from "../../public/assets/logos/elevenlabs.png";
+import falAiLogo from "../../public/assets/logos/fal_ai.jpg";
+import featherlessLogo from "../../public/assets/logos/featherless.svg";
+import fireworksLogo from "../../public/assets/logos/fireworks.svg";
+import friendliLogo from "../../public/assets/logos/friendli.svg";
+import githubCopilotLogo from "../../public/assets/logos/github_copilot.svg";
+import googleLogo from "../../public/assets/logos/google.svg";
+import groqLogo from "../../public/assets/logos/groq.svg";
+import huggingfaceLogo from "../../public/assets/logos/huggingface.svg";
+import hyperbolicLogo from "../../public/assets/logos/hyperbolic.svg";
+import infinityLogo from "../../public/assets/logos/infinity.png";
+import jinaLogo from "../../public/assets/logos/jina.png";
+import lambdaLogo from "../../public/assets/logos/lambda.svg";
+import lmstudioLogo from "../../public/assets/logos/lmstudio.svg";
+import metaLlamaLogo from "../../public/assets/logos/meta_llama.svg";
+import microsoftAzureLogo from "../../public/assets/logos/microsoft_azure.svg";
+import minimaxLogo from "../../public/assets/logos/minimax.svg";
+import mistralLogo from "../../public/assets/logos/mistral.svg";
+import moonshotLogo from "../../public/assets/logos/moonshot.svg";
+import morphLogo from "../../public/assets/logos/morph.svg";
+import nebiusLogo from "../../public/assets/logos/nebius.svg";
+import novitaLogo from "../../public/assets/logos/novita.svg";
+import nvidiaNimLogo from "../../public/assets/logos/nvidia_nim.svg";
+import nvidiaTritonLogo from "../../public/assets/logos/nvidia_triton.png";
+import ollamaLogo from "../../public/assets/logos/ollama.svg";
+import openaiSmallLogo from "../../public/assets/logos/openai_small.svg";
+import openrouterLogo from "../../public/assets/logos/openrouter.svg";
+import oracleLogo from "../../public/assets/logos/oracle.svg";
+import perplexityAiLogo from "../../public/assets/logos/perplexity-ai.svg";
+import qwenLogo from "../../public/assets/logos/qwen.png";
+import recraftLogo from "../../public/assets/logos/recraft.svg";
+import replicateLogo from "../../public/assets/logos/replicate.svg";
+import runwayLogo from "../../public/assets/logos/runway.png";
+import sambanovaLogo from "../../public/assets/logos/sambanova.svg";
+import sapLogo from "../../public/assets/logos/sap.png";
+import snowflakeLogo from "../../public/assets/logos/snowflake.svg";
+import sonioxLogo from "../../public/assets/logos/soniox.svg";
+import togetheraiLogo from "../../public/assets/logos/togetherai.svg";
+import topazLogo from "../../public/assets/logos/topaz.svg";
+import v0Logo from "../../public/assets/logos/v0.svg";
+import vercelLogo from "../../public/assets/logos/vercel.svg";
+import vllmLogo from "../../public/assets/logos/vllm.png";
+import volcengineLogo from "../../public/assets/logos/volcengine.png";
+import voyageLogo from "../../public/assets/logos/voyage.webp";
+import watsonxLogo from "../../public/assets/logos/watsonx.svg";
+import xaiLogo from "../../public/assets/logos/xai.svg";
+import xinferenceLogo from "../../public/assets/logos/xinference.svg";
export enum Providers {
A2A_Agent = "A2A Agent",
@@ -220,94 +283,91 @@ export const provider_map: Record = {
const standaloneSubproviderSlugs = new Set(["bedrock_mantle"]);
-const asset_logos_folder = "/ui/assets/logos/";
-
export const providerLogoMap: Record = {
- [Providers.A2A_Agent]: `${asset_logos_folder}a2a_agent.png`,
- [Providers.AI21]: `${asset_logos_folder}ai21.svg`,
- [Providers.AI21_CHAT]: `${asset_logos_folder}ai21.svg`,
- [Providers.AIML]: `${asset_logos_folder}aiml_api.svg`,
- [Providers.AIOHTTP_OPENAI]: `${asset_logos_folder}openai_small.svg`,
- [Providers.Anthropic]: `${asset_logos_folder}anthropic.svg`,
- [Providers.ANTHROPIC_TEXT]: `${asset_logos_folder}anthropic.svg`,
- [Providers.AssemblyAI]: `${asset_logos_folder}assemblyai_small.png`,
- [Providers.Azure]: `${asset_logos_folder}microsoft_azure.svg`,
- [Providers.Azure_AI_Studio]: `${asset_logos_folder}microsoft_azure.svg`,
- [Providers.AZURE_TEXT]: `${asset_logos_folder}microsoft_azure.svg`,
- [Providers.BASETEN]: `${asset_logos_folder}baseten.svg`,
- [Providers.Bedrock]: `${asset_logos_folder}bedrock.svg`,
- [Providers.BedrockMantle]: `${asset_logos_folder}bedrock.svg`,
- [Providers.SageMaker]: `${asset_logos_folder}bedrock.svg`,
- [Providers.Cerebras]: `${asset_logos_folder}cerebras.svg`,
- [Providers.CLOUDFLARE]: `${asset_logos_folder}cloudflare.svg`,
- [Providers.CODESTRAL]: `${asset_logos_folder}mistral.svg`,
- [Providers.Cohere]: `${asset_logos_folder}cohere.svg`,
- [Providers.COHERE_CHAT]: `${asset_logos_folder}cohere.svg`,
- [Providers.COMETAPI]: `${asset_logos_folder}cometapi.svg`,
- [Providers.Cursor]: `${asset_logos_folder}cursor.svg`,
- [Providers.Databricks]: `${asset_logos_folder}databricks.svg`,
- [Providers.Dashscope]: `${asset_logos_folder}dashscope.svg`,
- [Providers.Deepseek]: `${asset_logos_folder}deepseek.svg`,
- [Providers.Deepgram]: `${asset_logos_folder}deepgram.png`,
- [Providers.DeepInfra]: `${asset_logos_folder}deepinfra.png`,
- [Providers.ElevenLabs]: `${asset_logos_folder}elevenlabs.png`,
- [Providers.FalAI]: `${asset_logos_folder}fal_ai.jpg`,
- [Providers.FEATHERLESS_AI]: `${asset_logos_folder}featherless.svg`,
- [Providers.FireworksAI]: `${asset_logos_folder}fireworks.svg`,
- [Providers.FRIENDLIAI]: `${asset_logos_folder}friendli.svg`,
- [Providers.GITHUB_COPILOT]: `${asset_logos_folder}github_copilot.svg`,
- [Providers.Google_AI_Studio]: `${asset_logos_folder}google.svg`,
- [Providers.GradientAI]: `${asset_logos_folder}gradientai.svg`,
- [Providers.Groq]: `${asset_logos_folder}groq.svg`,
- [Providers.Hosted_Vllm]: `${asset_logos_folder}vllm.png`,
- [Providers.HUGGINGFACE]: `${asset_logos_folder}huggingface.svg`,
- [Providers.HYPERBOLIC]: `${asset_logos_folder}hyperbolic.svg`,
- [Providers.Infinity]: `${asset_logos_folder}infinity.png`,
- [Providers.JinaAI]: `${asset_logos_folder}jina.png`,
- [Providers.LAMBDA_AI]: `${asset_logos_folder}lambda.svg`,
- [Providers.LM_STUDIO]: `${asset_logos_folder}lmstudio.svg`,
- [Providers.LLAMA]: `${asset_logos_folder}meta_llama.svg`,
- [Providers.MiniMax]: `${asset_logos_folder}minimax.svg`,
- [Providers.MistralAI]: `${asset_logos_folder}mistral.svg`,
- [Providers.MOONSHOT]: `${asset_logos_folder}moonshot.svg`,
- [Providers.MORPH]: `${asset_logos_folder}morph.svg`,
- [Providers.NEBIUS]: `${asset_logos_folder}nebius.svg`,
- [Providers.NOVITA]: `${asset_logos_folder}novita.svg`,
- [Providers.NVIDIA_NIM]: `${asset_logos_folder}nvidia_nim.svg`,
- [Providers.Ollama]: `${asset_logos_folder}ollama.svg`,
- [Providers.OLLAMA_CHAT]: `${asset_logos_folder}ollama.svg`,
- [Providers.OOBABOOGA]: `${asset_logos_folder}openai_small.svg`,
- [Providers.OpenAI]: `${asset_logos_folder}openai_small.svg`,
- [Providers.OPENAI_LIKE]: `${asset_logos_folder}openai_small.svg`,
- [Providers.OpenAI_Text]: `${asset_logos_folder}openai_small.svg`,
- [Providers.OpenAI_Text_Compatible]: `${asset_logos_folder}openai_small.svg`,
- [Providers.OpenAI_Compatible]: `${asset_logos_folder}openai_small.svg`,
- [Providers.Openrouter]: `${asset_logos_folder}openrouter.svg`,
- [Providers.Oracle]: `${asset_logos_folder}oracle.svg`,
- [Providers.Perplexity]: `${asset_logos_folder}perplexity-ai.svg`,
- [Providers.RECRAFT]: `${asset_logos_folder}recraft.svg`,
- [Providers.REPLICATE]: `${asset_logos_folder}replicate.svg`,
- [Providers.RunwayML]: `${asset_logos_folder}runwayml.png`,
- [Providers.SAGEMAKER_LEGACY]: `${asset_logos_folder}bedrock.svg`,
- [Providers.Sambanova]: `${asset_logos_folder}sambanova.svg`,
- [Providers.SAP]: `${asset_logos_folder}sap.png`,
- [Providers.Snowflake]: `${asset_logos_folder}snowflake.svg`,
- [Providers.Soniox]: `${asset_logos_folder}soniox.svg`,
- [Providers.TEXT_COMPLETION_CODESTRAL]: `${asset_logos_folder}mistral.svg`,
- [Providers.TogetherAI]: `${asset_logos_folder}togetherai.svg`,
- [Providers.TOPAZ]: `${asset_logos_folder}topaz.svg`,
- [Providers.Triton]: `${asset_logos_folder}nvidia_triton.png`,
- [Providers.V0]: `${asset_logos_folder}v0.svg`,
- [Providers.VERCEL_AI_GATEWAY]: `${asset_logos_folder}vercel.svg`,
- [Providers.Vertex_AI]: `${asset_logos_folder}google.svg`,
- [Providers.VERTEX_AI_BETA]: `${asset_logos_folder}google.svg`,
- [Providers.VLLM]: `${asset_logos_folder}vllm.png`,
- [Providers.VolcEngine]: `${asset_logos_folder}volcengine.png`,
- [Providers.Voyage]: `${asset_logos_folder}voyage.webp`,
- [Providers.WATSONX]: `${asset_logos_folder}watsonx.svg`,
- [Providers.WATSONX_TEXT]: `${asset_logos_folder}watsonx.svg`,
- [Providers.xAI]: `${asset_logos_folder}xai.svg`,
- [Providers.XINFERENCE]: `${asset_logos_folder}xinference.svg`,
+ [Providers.A2A_Agent]: a2aAgentLogo.src,
+ [Providers.AI21]: ai21Logo.src,
+ [Providers.AI21_CHAT]: ai21Logo.src,
+ [Providers.AIML]: aimlApiLogo.src,
+ [Providers.AIOHTTP_OPENAI]: openaiSmallLogo.src,
+ [Providers.Anthropic]: anthropicLogo.src,
+ [Providers.ANTHROPIC_TEXT]: anthropicLogo.src,
+ [Providers.AssemblyAI]: assemblyaiSmallLogo.src,
+ [Providers.Azure]: microsoftAzureLogo.src,
+ [Providers.Azure_AI_Studio]: microsoftAzureLogo.src,
+ [Providers.AZURE_TEXT]: microsoftAzureLogo.src,
+ [Providers.BASETEN]: basetenLogo.src,
+ [Providers.Bedrock]: bedrockLogo.src,
+ [Providers.BedrockMantle]: bedrockLogo.src,
+ [Providers.SageMaker]: bedrockLogo.src,
+ [Providers.Cerebras]: cerebrasLogo.src,
+ [Providers.CLOUDFLARE]: cloudflareLogo.src,
+ [Providers.CODESTRAL]: mistralLogo.src,
+ [Providers.Cohere]: cohereLogo.src,
+ [Providers.COHERE_CHAT]: cohereLogo.src,
+ [Providers.COMETAPI]: cometapiLogo.src,
+ [Providers.Cursor]: cursorLogo.src,
+ [Providers.Databricks]: databricksLogo.src,
+ [Providers.Dashscope]: qwenLogo.src,
+ [Providers.Deepseek]: deepseekLogo.src,
+ [Providers.Deepgram]: deepgramLogo.src,
+ [Providers.DeepInfra]: deepinfraLogo.src,
+ [Providers.ElevenLabs]: elevenlabsLogo.src,
+ [Providers.FalAI]: falAiLogo.src,
+ [Providers.FEATHERLESS_AI]: featherlessLogo.src,
+ [Providers.FireworksAI]: fireworksLogo.src,
+ [Providers.FRIENDLIAI]: friendliLogo.src,
+ [Providers.GITHUB_COPILOT]: githubCopilotLogo.src,
+ [Providers.Google_AI_Studio]: googleLogo.src,
+ [Providers.Groq]: groqLogo.src,
+ [Providers.Hosted_Vllm]: vllmLogo.src,
+ [Providers.HUGGINGFACE]: huggingfaceLogo.src,
+ [Providers.HYPERBOLIC]: hyperbolicLogo.src,
+ [Providers.Infinity]: infinityLogo.src,
+ [Providers.JinaAI]: jinaLogo.src,
+ [Providers.LAMBDA_AI]: lambdaLogo.src,
+ [Providers.LM_STUDIO]: lmstudioLogo.src,
+ [Providers.LLAMA]: metaLlamaLogo.src,
+ [Providers.MiniMax]: minimaxLogo.src,
+ [Providers.MistralAI]: mistralLogo.src,
+ [Providers.MOONSHOT]: moonshotLogo.src,
+ [Providers.MORPH]: morphLogo.src,
+ [Providers.NEBIUS]: nebiusLogo.src,
+ [Providers.NOVITA]: novitaLogo.src,
+ [Providers.NVIDIA_NIM]: nvidiaNimLogo.src,
+ [Providers.Ollama]: ollamaLogo.src,
+ [Providers.OLLAMA_CHAT]: ollamaLogo.src,
+ [Providers.OOBABOOGA]: openaiSmallLogo.src,
+ [Providers.OpenAI]: openaiSmallLogo.src,
+ [Providers.OPENAI_LIKE]: openaiSmallLogo.src,
+ [Providers.OpenAI_Text]: openaiSmallLogo.src,
+ [Providers.OpenAI_Text_Compatible]: openaiSmallLogo.src,
+ [Providers.OpenAI_Compatible]: openaiSmallLogo.src,
+ [Providers.Openrouter]: openrouterLogo.src,
+ [Providers.Oracle]: oracleLogo.src,
+ [Providers.Perplexity]: perplexityAiLogo.src,
+ [Providers.RECRAFT]: recraftLogo.src,
+ [Providers.REPLICATE]: replicateLogo.src,
+ [Providers.RunwayML]: runwayLogo.src,
+ [Providers.SAGEMAKER_LEGACY]: bedrockLogo.src,
+ [Providers.Sambanova]: sambanovaLogo.src,
+ [Providers.SAP]: sapLogo.src,
+ [Providers.Snowflake]: snowflakeLogo.src,
+ [Providers.Soniox]: sonioxLogo.src,
+ [Providers.TEXT_COMPLETION_CODESTRAL]: mistralLogo.src,
+ [Providers.TogetherAI]: togetheraiLogo.src,
+ [Providers.TOPAZ]: topazLogo.src,
+ [Providers.Triton]: nvidiaTritonLogo.src,
+ [Providers.V0]: v0Logo.src,
+ [Providers.VERCEL_AI_GATEWAY]: vercelLogo.src,
+ [Providers.Vertex_AI]: googleLogo.src,
+ [Providers.VERTEX_AI_BETA]: googleLogo.src,
+ [Providers.VLLM]: vllmLogo.src,
+ [Providers.VolcEngine]: volcengineLogo.src,
+ [Providers.Voyage]: voyageLogo.src,
+ [Providers.WATSONX]: watsonxLogo.src,
+ [Providers.WATSONX_TEXT]: watsonxLogo.src,
+ [Providers.xAI]: xaiLogo.src,
+ [Providers.XINFERENCE]: xinferenceLogo.src,
};
export const getProviderLogoAndName = (providerValue: string): { logo: string; displayName: string } => {
diff --git a/ui/litellm-dashboard/src/lib/assetPaths.test.ts b/ui/litellm-dashboard/src/lib/assetPaths.test.ts
index cf1b86f39e3..35b5ca3f907 100644
--- a/ui/litellm-dashboard/src/lib/assetPaths.test.ts
+++ b/ui/litellm-dashboard/src/lib/assetPaths.test.ts
@@ -48,6 +48,16 @@ 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 () => {
+ const { resolveLogoSrc } = await importWithRoot("/litellm");
+ expect(resolveLogoSrc("/_next/static/media/openai_small.abc123.svg")).toBe(
+ "/_next/static/media/openai_small.abc123.svg",
+ );
+ expect(resolveLogoSrc("/litellm-asset-prefix/_next/static/media/openai_small.abc123.svg")).toBe(
+ "/litellm-asset-prefix/_next/static/media/openai_small.abc123.svg",
+ );
+ });
+
it("roots a local asset path using the live server root path", async () => {
const { resolveLogoSrc } = await importWithRoot("/litellm");
expect(resolveLogoSrc("/ui/assets/logos/github.svg")).toBe("/litellm/ui/assets/logos/github.svg");
diff --git a/ui/litellm-dashboard/src/lib/assetPaths.ts b/ui/litellm-dashboard/src/lib/assetPaths.ts
index 83daafbb00e..d59335fd7d0 100644
--- a/ui/litellm-dashboard/src/lib/assetPaths.ts
+++ b/ui/litellm-dashboard/src/lib/assetPaths.ts
@@ -23,5 +23,6 @@ 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;
return withServerRoot(value, root);
};
diff --git a/ui/litellm-dashboard/vitest.config.ts b/ui/litellm-dashboard/vitest.config.ts
index 44a2487b97c..e1c58a0c2b5 100644
--- a/ui/litellm-dashboard/vitest.config.ts
+++ b/ui/litellm-dashboard/vitest.config.ts
@@ -1,7 +1,19 @@
-import { defineConfig } from "vitest/config";
+import { defineConfig, type Plugin, type ViteUserConfig } from "vitest/config";
import { resolve } from "path";
-export default defineConfig({
+const staticImageData: Plugin = {
+ name: "next-static-image-data",
+ enforce: "pre",
+ load(id: string) {
+ const path = id.split("?")[0];
+ if (!/\.(svg|png|jpe?g|gif|webp|avif|ico|bmp)$/i.test(path)) return undefined;
+ const name = path.slice(path.lastIndexOf("/") + 1);
+ return `export default { src: ${JSON.stringify(`/_next/static/media/${name}`)}, height: 24, width: 24 };`;
+ },
+};
+
+const config: ViteUserConfig = {
+ plugins: [staticImageData],
test: {
environment: "jsdom",
setupFiles: ["tests/setupTests.ts"],
@@ -47,4 +59,6 @@ export default defineConfig({
jsx: "automatic",
jsxImportSource: "react",
},
-});
+};
+
+export default defineConfig(config);