diff --git a/ui/litellm-dashboard/src/components/ModelInfoEditForm.tsx b/ui/litellm-dashboard/src/components/ModelInfoEditForm.tsx index d56e65237eb..fb4d90b5ea2 100644 --- a/ui/litellm-dashboard/src/components/ModelInfoEditForm.tsx +++ b/ui/litellm-dashboard/src/components/ModelInfoEditForm.tsx @@ -271,6 +271,7 @@ const displayCost = (localModelData: any, field: TouchedPricingField): string => interface ModelInfoEditFormProps { localModelData: any; modelData: { model_info: { team_id?: string | null } & Record }; + teamAlias: string | null; accessToken: string | null; isEditing: boolean; isSaving: boolean; @@ -341,6 +342,7 @@ const ChipList: React.FC<{ values: unknown; emptyLabel: string }> = ({ values, e const ModelInfoEditForm: React.FC = ({ localModelData, modelData, + teamAlias, accessToken, isEditing, isSaving, @@ -799,8 +801,12 @@ const ModelInfoEditForm: React.FC = ({
- Team ID - {modelData.model_info.team_id || "Not Set"} + Team + + {teamAlias + ? `${teamAlias} (${modelData.model_info.team_id})` + : modelData.model_info.team_id || "Not Set"} +
diff --git a/ui/litellm-dashboard/src/components/model_info_view.test.tsx b/ui/litellm-dashboard/src/components/model_info_view.test.tsx index 3db9418dfb9..f714b8e5c4a 100644 --- a/ui/litellm-dashboard/src/components/model_info_view.test.tsx +++ b/ui/litellm-dashboard/src/components/model_info_view.test.tsx @@ -42,6 +42,11 @@ vi.mock("@/app/(dashboard)/hooks/models/useModelCostMap", () => ({ useModelCostMap: (...args: any[]) => mockUseModelCostMap(...args), })); +const mockUseTeams = vi.fn(); +vi.mock("@/app/(dashboard)/hooks/teams/useTeams", () => ({ + useTeams: () => mockUseTeams(), +})); + const mockUsePtuCostAttributionEnabled = vi.fn(); vi.mock("@/app/(dashboard)/hooks/uiSettings/usePtuCostAttributionEnabled", () => ({ usePtuCostAttributionEnabled: () => mockUsePtuCostAttributionEnabled(), @@ -102,6 +107,7 @@ describe("ModelInfoView", () => { }); vi.clearAllMocks(); mockUsePtuCostAttributionEnabled.mockReturnValue(false); + mockUseTeams.mockReturnValue({ data: undefined, isLoading: false, error: null }); mockUseModelsInfo.mockReturnValue({ data: { @@ -1305,6 +1311,78 @@ describe("ModelInfoView", () => { }); }); + describe("team alias", () => { + const teamModel = { + ...defaultModelData, + model_info: { ...defaultModelData.model_info, team_id: "team-1" }, + }; + + beforeEach(() => { + mockUseModelsInfo.mockReturnValue({ data: { data: [teamModel] }, isLoading: false, error: null }); + mockModelInfoV1Call.mockResolvedValue({ data: [teamModel] }); + }); + + const readRawJson = async (user: ReturnType) => { + await user.click(await screen.findByRole("tab", { name: /raw json/i })); + const pre = await screen.findByText(/"model_name": "GPT-4"/, { selector: "pre" }); + return JSON.parse(pre.textContent ?? ""); + }; + + it("shows the team alias next to the team id and adds team_alias to the raw JSON", async () => { + mockUseTeams.mockReturnValue({ + data: [ + { team_id: "team-0", team_alias: "other" }, + { team_id: "team-1", team_alias: "alpha" }, + ], + isLoading: false, + error: null, + }); + const user = userEvent.setup(); + render(, { wrapper }); + + expect(await screen.findByText("alpha (team-1)")).toBeInTheDocument(); + + const raw = await readRawJson(user); + expect(raw.model_info).toMatchObject({ team_id: "team-1", team_alias: "alpha" }); + const keys = Object.keys(raw.model_info); + expect(keys.indexOf("team_alias")).toBe(keys.indexOf("team_id") + 1); + }); + + it("falls back to the bare team id when the team is not in the caller's team list", async () => { + mockUseTeams.mockReturnValue({ + data: [{ team_id: "team-0", team_alias: "other" }], + isLoading: false, + error: null, + }); + const user = userEvent.setup(); + render(, { wrapper }); + + expect(await screen.findByText("team-1")).toBeInTheDocument(); + + const raw = await readRawJson(user); + expect(raw.model_info.team_id).toBe("team-1"); + expect(raw.model_info).not.toHaveProperty("team_alias"); + }); + + it("shows Not Set and no team_alias for a model without a team", async () => { + mockUseModelsInfo.mockReturnValue({ data: { data: [defaultModelData] }, isLoading: false, error: null }); + mockModelInfoV1Call.mockResolvedValue({ data: [defaultModelData] }); + mockUseTeams.mockReturnValue({ + data: [{ team_id: "team-1", team_alias: "alpha" }], + isLoading: false, + error: null, + }); + const user = userEvent.setup(); + render(, { wrapper }); + + expect(await screen.findByText("Team")).toBeInTheDocument(); + expect(screen.queryByText(/alpha/)).not.toBeInTheDocument(); + + const raw = await readRawJson(user); + expect(raw.model_info).not.toHaveProperty("team_alias"); + }); + }); + it("renders the provider card logo from the bundled provider map", async () => { render(, { wrapper }); diff --git a/ui/litellm-dashboard/src/components/model_info_view.tsx b/ui/litellm-dashboard/src/components/model_info_view.tsx index 35afcdb2985..f25416327c0 100644 --- a/ui/litellm-dashboard/src/components/model_info_view.tsx +++ b/ui/litellm-dashboard/src/components/model_info_view.tsx @@ -169,6 +169,12 @@ export default function ModelInfoView({ // Keep modelData variable name for backwards compatibility const modelData = transformedModelData; + const teamAlias = teams?.find((team) => team.team_id === modelData?.model_info?.team_id)?.team_alias || null; + const rawModelInfoEntries = Object.entries(modelData?.model_info ?? {}).flatMap((entry) => + entry[0] === "team_id" && teamAlias ? [entry, ["team_alias", teamAlias]] : [entry], + ); + const rawModelData = modelData && { ...modelData, model_info: Object.fromEntries(rawModelInfoEntries) }; + const canEditModel = canModifyModel({ userRole, userID, isViewOnly }, teams ?? null, { teamId: modelData?.model_info?.team_id, isDbModel: modelData?.model_info?.db_model === true, @@ -765,6 +771,7 @@ export default function ModelInfoView({ -
{JSON.stringify(modelData, null, 2)}
+
+                {JSON.stringify(rawModelData, null, 2)}
+