From b12931a84c1af2e14a33329167d9f9a6607cf992 Mon Sep 17 00:00:00 2001 From: Tin Chi Lo Date: Mon, 3 Aug 2026 16:47:34 -0700 Subject: [PATCH] fix(ui): let a caller retry a failed auto-router model fetch in-form A failed fetchAvailableModels call disabled every preset with no way to recover short of closing and reopening the modal, even for a transient error. Surface the failure next to the template selector with a Retry action that re-runs the same useQuery. --- .../add_model/add_auto_router_tab.test.tsx | 16 ++++++++++++++++ .../components/add_model/add_auto_router_tab.tsx | 9 +++++++++ 2 files changed, 25 insertions(+) diff --git a/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.test.tsx b/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.test.tsx index 30153b6f87a..5ea9e9de3fd 100644 --- a/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.test.tsx +++ b/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.test.tsx @@ -184,6 +184,22 @@ describe("AddAutoRouterTab", () => { expect(isOptionDisabled(optionByLabel("OpenAI Family")!)).toBe(true); }); + // A failed fetch must not strand the caller: Retry re-runs the same query, and a caller who + // only hit a transient error can recover without closing and reopening the whole modal. + it("recovers presets once Retry re-fetches successfully", async () => { + const user = userEvent.setup(); + mockFetchAvailableModels.mockRejectedValueOnce(new Error("boom")).mockResolvedValueOnce(ALL_FAMILY_MODELS); + + renderWithProviders(); + await waitFor(() => expect(screen.getByRole("button", { name: /retry/i })).toBeInTheDocument()); + + await user.click(screen.getByRole("button", { name: /retry/i })); + + openTemplateDropdown(); + await waitFor(() => expect(isOptionDisabled(optionByLabel("Anthropic Family")!)).toBe(false)); + expect(mockFetchAvailableModels).toHaveBeenCalledTimes(2); + }); + // The headline behavior: selecting a preset must pre-fill the tier config so the created // router carries the preset's models. Real tier validation runs here (getMissingTiersError is // not stubbed), so if selection stopped pre-filling, the empty tiers would either block the diff --git a/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.tsx b/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.tsx index f73a06860c3..9d1ed00d31c 100644 --- a/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.tsx +++ b/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.tsx @@ -109,6 +109,7 @@ const AddAutoRouterTab: React.FC = ({ data: modelInfo = [], isLoading: modelsLoading, isError: modelsError, + refetch: refetchModels, } = useQuery({ queryKey: ["availableModels", "autoRouter", accessToken], queryFn: () => fetchAvailableModels(accessToken), @@ -357,6 +358,14 @@ const AddAutoRouterTab: React.FC = ({ {showValidationErrors && !selectedPreset && (
Please select a template
)} + {modelsError && ( +
+ Could not load available models.{" "} + +
+ )}