diff --git a/ui/litellm-dashboard/src/components/common_components/RouterSettingsAccordion.test.tsx b/ui/litellm-dashboard/src/components/common_components/RouterSettingsAccordion.test.tsx
index a70b7602e5b..5d55a8b014f 100644
--- a/ui/litellm-dashboard/src/components/common_components/RouterSettingsAccordion.test.tsx
+++ b/ui/litellm-dashboard/src/components/common_components/RouterSettingsAccordion.test.tsx
@@ -1,11 +1,14 @@
-import { act, fireEvent, render, screen } from "@testing-library/react";
+import { act, cleanup, fireEvent, render, screen } from "@testing-library/react";
import type { ReactNode } from "react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { RouterSettingsFormValue } from "../router_settings/RouterSettingsForm";
+import { modelInfoCall } from "../networking";
+import { fetchAvailableModels } from "@/components/llm_calls/fetch_models";
import RouterSettingsAccordion, { RouterSettingsAccordionValue } from "./RouterSettingsAccordion";
vi.mock("../networking", () => ({
getRouterSettingsCall: vi.fn().mockResolvedValue({}),
+ modelInfoCall: vi.fn().mockResolvedValue({ data: [] }),
}));
vi.mock("@/components/llm_calls/fetch_models", () => ({
@@ -13,7 +16,9 @@ vi.mock("@/components/llm_calls/fetch_models", () => ({
}));
vi.mock("../Settings/RouterSettings/Fallbacks/FallbackSelectionForm", () => ({
- FallbackSelectionForm: () => null,
+ FallbackSelectionForm: ({ availableModels }: { availableModels: string[] }) => (
+
{availableModels.join(",")}
+ ),
}));
vi.mock("@tremor/react", () => ({
@@ -41,14 +46,25 @@ vi.mock("../router_settings/RouterSettingsForm", () => ({
describe("RouterSettingsAccordion", () => {
beforeEach(() => {
+ vi.clearAllMocks();
vi.useFakeTimers();
});
afterEach(() => {
- vi.runOnlyPendingTimers();
+ cleanup();
+ if (vi.isFakeTimers()) {
+ vi.runOnlyPendingTimers();
+ }
vi.useRealTimers();
});
+ const flushPromises = async () => {
+ await act(async () => {
+ await Promise.resolve();
+ await Promise.resolve();
+ });
+ };
+
const flushInitialPropagation = async (onChange: ReturnType) => {
await act(async () => {
vi.advanceTimersByTime(100);
@@ -81,6 +97,29 @@ describe("RouterSettingsAccordion", () => {
expect(onChange.mock.calls[0][0].router_settings.routing_strategy).toBe("usage-based-routing");
});
+ it("populates the fallback dropdown with team-scoped models when teamId is set", async () => {
+ vi.mocked(modelInfoCall).mockResolvedValueOnce({
+ data: [{ model_name: "team-a-model" }, { model_name: "team-b-model" }],
+ });
+
+ render();
+ await flushPromises();
+
+ expect(screen.getByTestId("available-models").textContent).toBe("team-a-model,team-b-model");
+ expect(modelInfoCall).toHaveBeenCalledWith("test-token", "", "", 1, 1000, undefined, undefined, "team-1");
+ expect(fetchAvailableModels).not.toHaveBeenCalled();
+ });
+
+ it("uses the global model list when no teamId is provided", async () => {
+ vi.mocked(fetchAvailableModels).mockResolvedValueOnce([{ model_group: "shared-gpt" }]);
+
+ render();
+ await flushPromises();
+
+ expect(screen.getByTestId("available-models").textContent).toBe("shared-gpt");
+ expect(modelInfoCall).not.toHaveBeenCalled();
+ });
+
it("does not call onChange when unmounted mid-wait", async () => {
const onChange = vi.fn<(value: RouterSettingsAccordionValue) => void>();
const { unmount } = render();
diff --git a/ui/litellm-dashboard/src/components/common_components/RouterSettingsAccordion.tsx b/ui/litellm-dashboard/src/components/common_components/RouterSettingsAccordion.tsx
index 08b917e302f..81e07592705 100644
--- a/ui/litellm-dashboard/src/components/common_components/RouterSettingsAccordion.tsx
+++ b/ui/litellm-dashboard/src/components/common_components/RouterSettingsAccordion.tsx
@@ -1,7 +1,7 @@
import React, { useEffect, useState, useImperativeHandle, forwardRef, useRef } from "react";
import { TabPanel, TabPanels, TabGroup, TabList, Tab } from "@tremor/react";
import { useDebouncedCallback } from "@tanstack/react-pacer/debouncer";
-import { getRouterSettingsCall } from "../networking";
+import { getRouterSettingsCall, modelInfoCall } from "../networking";
import RouterSettingsForm, { RouterSettingsFormValue } from "../router_settings/RouterSettingsForm";
import { Fallbacks } from "../Settings/RouterSettings/Fallbacks/AddFallbacks";
import { FallbackSelectionForm } from "../Settings/RouterSettings/Fallbacks/FallbackSelectionForm";
@@ -30,6 +30,7 @@ interface RouterSettingsAccordionProps {
value?: RouterSettingsAccordionValue;
onChange?: (value: RouterSettingsAccordionValue) => void;
modelData?: any;
+ teamId?: string;
}
export interface RouterSettingsAccordionRef {
@@ -39,7 +40,7 @@ export interface RouterSettingsAccordionRef {
const PROPAGATE_WAIT_MS = 100;
const RouterSettingsAccordion = forwardRef(
- ({ accessToken, value, onChange, modelData }, ref) => {
+ ({ accessToken, value, onChange, modelData, teamId }, ref) => {
const [formValue, setFormValue] = useState({
routerSettings: {},
selectedStrategy: null,
@@ -182,6 +183,15 @@ const RouterSettingsAccordion = forwardRef {
try {
+ if (teamId) {
+ const response = await modelInfoCall(accessToken, "", "", 1, 1000, undefined, undefined, teamId);
+ const teamModels: ModelGroup[] = (response?.data ?? [])
+ .map((item: { model_name?: string }) => item.model_name)
+ .filter((name: string | undefined): name is string => Boolean(name))
+ .map((name: string) => ({ model_group: name }));
+ setModelInfo(teamModels);
+ return;
+ }
const uniqueModels = await fetchAvailableModels(accessToken);
setModelInfo(uniqueModels);
} catch (error) {
@@ -189,7 +199,7 @@ const RouterSettingsAccordion = forwardRef {
diff --git a/ui/litellm-dashboard/src/components/team/TeamInfo.tsx b/ui/litellm-dashboard/src/components/team/TeamInfo.tsx
index eaf2faa08ee..69b10a57738 100644
--- a/ui/litellm-dashboard/src/components/team/TeamInfo.tsx
+++ b/ui/litellm-dashboard/src/components/team/TeamInfo.tsx
@@ -1246,6 +1246,7 @@ const TeamInfoView: React.FC = ({