mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
fix(ui): stop treating a failed background model refetch as unverifiable
react-query keeps the last successful list around when a later refetch fails: data stays populated, but isError flips true. presetAvailability read isError alone, so once a caller had a good cached list, any subsequent refetch hiccup (window refocus, a manual retry that itself fails, etc.) made every preset unverifiable again, wrongly blocked an already-selected preset at the new submit-time check, and showed a "models are no longer available" toast for models that were, per the cache, still there. Only treat the state as unverifiable when there has never been a successful fetch (data is still undefined); otherwise keep trusting the cached list, matching how react-query itself treats stale-but-valid data.
This commit is contained in:
parent
aa4843cb02
commit
b140c02a91
2 changed files with 34 additions and 4 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import { renderWithProviders, screen, waitFor, testQueryClient } from "../../../tests/test-utils";
|
||||
import { fireEvent } from "@testing-library/react";
|
||||
import { act, fireEvent } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { vi } from "vitest";
|
||||
import AddAutoRouterTab from "./add_auto_router_tab";
|
||||
|
|
@ -200,6 +200,31 @@ describe("AddAutoRouterTab", () => {
|
|||
expect(mockFetchAvailableModels).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
// react-query keeps the last successful list when a later refetch fails, so a background
|
||||
// refetch error must not treat an already-verified, still-cached preset as unverifiable: the
|
||||
// caller never sees a stale error message, and a preset they already picked stays submittable.
|
||||
it("keeps a selected preset submit-reachable when a background refetch fails but cached models remain valid", async () => {
|
||||
const user = userEvent.setup();
|
||||
mockFetchAvailableModels.mockResolvedValueOnce(ALL_FAMILY_MODELS).mockRejectedValue(new Error("boom"));
|
||||
|
||||
renderWithProviders(<Harness />);
|
||||
openTemplateDropdown();
|
||||
await waitFor(() => expect(isOptionDisabled(optionByLabel("Anthropic Family")!)).toBe(false));
|
||||
fireEvent.click(optionByLabel("Anthropic Family")!);
|
||||
|
||||
await act(async () => {
|
||||
await testQueryClient.refetchQueries({ queryKey: ["availableModels", "autoRouter", "token"] });
|
||||
});
|
||||
|
||||
await user.type(screen.getByPlaceholderText(/smart_router/i), "resilient-router");
|
||||
await user.click(screen.getByRole("button", { name: /add auto router/i }));
|
||||
|
||||
await waitFor(() => expect(mockHandleAddAutoRouterSubmit).toHaveBeenCalled());
|
||||
expect(NotificationManager.fromBackend).not.toHaveBeenCalledWith(
|
||||
"This template's models are no longer available. Please reselect a template or switch to Custom.",
|
||||
);
|
||||
});
|
||||
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({
|
|||
}, [accessToken]);
|
||||
|
||||
const {
|
||||
data: modelInfo = [],
|
||||
data,
|
||||
isLoading: modelsLoading,
|
||||
isError: modelsError,
|
||||
refetch: refetchModels,
|
||||
|
|
@ -115,6 +115,11 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({
|
|||
queryFn: () => fetchAvailableModels(accessToken),
|
||||
enabled: Boolean(accessToken),
|
||||
});
|
||||
const modelInfo = React.useMemo(() => data ?? [], [data]);
|
||||
// react-query keeps the last successful list around when a later refetch fails, so isError
|
||||
// alone can't tell "never loaded" apart from "loaded, then a background refetch errored" - only
|
||||
// the former leaves us with nothing trustworthy to verify a preset's models against.
|
||||
const modelsUnverifiable = modelsError && data === undefined;
|
||||
|
||||
const isAdmin = all_admin_roles.includes(userRole);
|
||||
|
||||
|
|
@ -133,7 +138,7 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({
|
|||
// makes the load-race (pick during loading, then discover a missing model) unrepresentable.
|
||||
const presetAvailability = (preset: AutoRouterPreset): PresetAvailability => {
|
||||
if (modelsLoading) return { kind: "loading" };
|
||||
if (modelsError) return { kind: "unverifiable" };
|
||||
if (modelsUnverifiable) return { kind: "unverifiable" };
|
||||
const missing = getMissingModelsInPreset(preset, availableModelSet);
|
||||
return missing.length > 0 ? { kind: "missing_models", models: missing } : { kind: "available" };
|
||||
};
|
||||
|
|
@ -372,7 +377,7 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({
|
|||
{showValidationErrors && !selectedPreset && (
|
||||
<div className="text-xs mt-1 text-red-500">Please select a template</div>
|
||||
)}
|
||||
{modelsError && (
|
||||
{modelsUnverifiable && (
|
||||
<div className="text-xs mt-1 text-red-500">
|
||||
Could not load available models.{" "}
|
||||
<button type="button" className="underline" onClick={() => refetchModels()}>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue