diff --git a/terraform/provider/tools/endpointaudit/coverage_allowlist.txt b/terraform/provider/tools/endpointaudit/coverage_allowlist.txt index 00f8f3b6031..982136d7fe3 100644 --- a/terraform/provider/tools/endpointaudit/coverage_allowlist.txt +++ b/terraform/provider/tools/endpointaudit/coverage_allowlist.txt @@ -67,6 +67,7 @@ GET /user/spend/report # Admin UI helper endpoints; serve UI forms and caller-scoped views, not desired state GET /budget/settings GET /credentials/{credential_name}/jwks +POST /credentials/nvidia_nim/models # Credential-scoped upstream discovery, not Terraform-managed state GET /router/fields GET /guardrails/ui/add_guardrail_settings GET /guardrails/ui/category_yaml/{category_name} diff --git a/ui/litellm-dashboard/src/components/add_model/NimModelNameField.integration.test.tsx b/ui/litellm-dashboard/src/components/add_model/NimModelNameField.integration.test.tsx index 1a6fb4fb56f..2423c6b5d71 100644 --- a/ui/litellm-dashboard/src/components/add_model/NimModelNameField.integration.test.tsx +++ b/ui/litellm-dashboard/src/components/add_model/NimModelNameField.integration.test.tsx @@ -7,6 +7,7 @@ import type { MountedFormValues } from "../common_components/MountedFormField"; import { apiClient } from "../networking"; import { getPlaceholder, Providers } from "../provider_info_helpers"; import NimModelNameField from "./NimModelNameField"; +import i18n from "@/i18n/i18n"; vi.mock("../networking", () => ({ apiClient: { post: vi.fn() } })); const catalogModel = "nvidia_nim/catalog-model"; @@ -77,6 +78,30 @@ describe("NIM model discovery", () => { }); }); + it("translates discovery status and refresh controls in the selected language", async () => { + const previousLanguage = i18n.language; + i18n.addResourceBundle("es", "translation", { + addModel: { + nimModelDiscovery: { + loading: "Translated loading", + unavailable: "Translated unavailable", + refresh: "Translated refresh", + }, + }, + }); + try { + await act(() => i18n.changeLanguage("es")); + vi.mocked(apiClient.post).mockResolvedValue({ status: "unavailable", models: [] }); + render(
); + expect(screen.getByText("Translated loading")).toBeInTheDocument(); + expect(await screen.findByText("Translated unavailable")).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Translated refresh" })).toBeEnabled(); + } finally { + await act(() => i18n.changeLanguage(previousLanguage)); + i18n.removeResourceBundle("es", "translation"); + } + }); + it("keeps the catalog usable when upstream discovery is unavailable", async () => { vi.mocked(apiClient.post).mockResolvedValue({ status: "unavailable", models: [] }); render(); diff --git a/ui/litellm-dashboard/src/components/add_model/NimModelNameField.tsx b/ui/litellm-dashboard/src/components/add_model/NimModelNameField.tsx index 7d44c92284e..231f19b4278 100644 --- a/ui/litellm-dashboard/src/components/add_model/NimModelNameField.tsx +++ b/ui/litellm-dashboard/src/components/add_model/NimModelNameField.tsx @@ -1,5 +1,6 @@ import { useEffect, useMemo, useState, type ComponentProps } from "react"; import { useFormContext, useWatch } from "react-hook-form"; +import { useTranslation } from "react-i18next"; import { apiClient } from "../networking"; import { resolveLitellmProviderSlug } from "../provider_info_helpers"; import type { MountedFormValues } from "../common_components/MountedFormField"; @@ -17,6 +18,7 @@ type Props = ComponentProps & { }; export default function NimModelNameField({ accessToken, canDiscover, ...props }: Props) { + const { t } = useTranslation(); const { control } = useFormContext(); const credential = useWatch({ control, name: "litellm_credential_name" }); const apiKey = useWatch({ control, name: "api_key" }); @@ -86,11 +88,9 @@ export default function NimModelNameField({ accessToken, canDiscover, ...props } {enabled && (

- {!current && "Loading models from NVIDIA NIM"} - {current?.status === "success" && - "Models listed by your NIM endpoint. Inference access and capabilities may vary"} - {current?.status === "unavailable" && - "Could not refresh NIM models. Showing the built-in catalog. You can also enter a custom model"} + {!current && t("addModel.nimModelDiscovery.loading")} + {current?.status === "success" && t("addModel.nimModelDiscovery.success")} + {current?.status === "unavailable" && t("addModel.nimModelDiscovery.unavailable")}

)} diff --git a/ui/litellm-dashboard/src/i18n/locales/en.json b/ui/litellm-dashboard/src/i18n/locales/en.json index 3c9dbc99a6b..9a19a309136 100644 --- a/ui/litellm-dashboard/src/i18n/locales/en.json +++ b/ui/litellm-dashboard/src/i18n/locales/en.json @@ -18523,6 +18523,12 @@ "decisionModelSelect": { "label": "Decision model", "placeholder": "Select the decision model" + }, + "nimModelDiscovery": { + "loading": "Loading models from NVIDIA NIM", + "success": "Models listed by your NIM endpoint. Inference access and capabilities may vary", + "unavailable": "Could not refresh NIM models. Showing the built-in catalog. You can also enter a custom model", + "refresh": "Refresh NIM models" } }, "modelAdd": {