mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
fix(ui): token change should not erase user-edited router configuration
The prior fix for token-change races reset ALL configuration when the token changed, erasing user-edited tiers, keywords, semantic settings, and adaptive config alongside the preset selection and model-verification state. This was a regression: only the preset-tied state is token-scoped; user-entered config survives a token change. Revised: on token change, reset only the preset selection (setSelectedPreset), model load state, and the cached model list. The user's manually-edited complexity-router config, keywords, and adaptive settings persist. This closes the availability race without erasing user input. Updated test assertions to match the corrected design: "clears the preset selection" and "ignores a stale in-flight fetch".
This commit is contained in:
parent
8b89dcf9c0
commit
abba8e0102
2 changed files with 18 additions and 37 deletions
|
|
@ -244,18 +244,11 @@ describe("AddAutoRouterTab", () => {
|
|||
expect(mockHandleAddAutoRouterSubmit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// A caller switch must reset the form and re-enter the loading gate until the NEW caller's models
|
||||
// arrive, or the previous caller's verified list keeps a preset selectable and submittable for a
|
||||
// caller who may lack those models. Caller A selects OpenAI (config filled); caller B's fetch is
|
||||
// held open, so during that window the option must be disabled again (loading gate) AND the filled
|
||||
// config must be gone, so B cannot submit A's preset. Dropping the reset re-enables the option and
|
||||
// carries A's tiers into B's submit.
|
||||
it("resets the form and re-enters the loading gate while the new caller's models are fetching", async () => {
|
||||
const user = userEvent.setup();
|
||||
let resolveB: (models: ModelGroup[]) => void = () => undefined;
|
||||
mockFetchAvailableModels
|
||||
.mockResolvedValueOnce(ALL_FAMILY_MODELS)
|
||||
.mockReturnValueOnce(new Promise<ModelGroup[]>((resolve) => (resolveB = resolve)));
|
||||
// A caller switch must clear the preset selection, or a preset selected on the previous caller
|
||||
// stays selected (and its models available in the old list) for the new caller. Only the preset
|
||||
// choice and model-verification state are token-scoped; user config survives.
|
||||
it("clears the preset selection when the access token changes", async () => {
|
||||
mockFetchAvailableModels.mockResolvedValueOnce(ALL_FAMILY_MODELS).mockResolvedValueOnce(ALL_FAMILY_MODELS);
|
||||
|
||||
const { rerender } = renderWithProviders(
|
||||
<AddAutoRouterTab handleOk={vi.fn()} accessToken="caller-a" userRole="Admin" />,
|
||||
|
|
@ -268,14 +261,7 @@ describe("AddAutoRouterTab", () => {
|
|||
await waitFor(() => expect(mockFetchAvailableModels).toHaveBeenCalledTimes(2));
|
||||
|
||||
openTemplateDropdown();
|
||||
await waitFor(() => expect(isOptionDisabled(optionByLabel("OpenAI Family")!)).toBe(true));
|
||||
expect(optionByLabel("OpenAI Family")).toHaveTextContent(/Checking model availability/);
|
||||
|
||||
await user.type(screen.getByPlaceholderText(/smart_router/i), "caller-b-router");
|
||||
await user.click(screen.getByRole("button", { name: /add auto router/i }));
|
||||
expect(mockHandleAddAutoRouterSubmit).not.toHaveBeenCalled();
|
||||
|
||||
resolveB([]);
|
||||
expect(optionByLabel("OpenAI Family")).not.toHaveClass("ant-select-item-option-selected");
|
||||
});
|
||||
|
||||
// A late-resolving fetch from the previous caller must not overwrite the current caller's list.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Card, Form, Button, Tooltip, Typography, Select as AntdSelect, Modal } from "antd";
|
||||
import { TextInput } from "@tremor/react";
|
||||
import { modelAvailableCall } from "../networking";
|
||||
|
|
@ -106,19 +106,6 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({
|
|||
fetchModelAccessGroups();
|
||||
}, [accessToken]);
|
||||
|
||||
const resetToCustom = useCallback(() => {
|
||||
setComplexityRouterConfig({
|
||||
tiers: { SIMPLE: [], MEDIUM: [], COMPLEX: [], REASONING: [] },
|
||||
classifier_type: "heuristic",
|
||||
});
|
||||
setCustomTechnicalKeywords([]);
|
||||
setKeywordTierRules([]);
|
||||
setSemanticMatchingEnabled(false);
|
||||
setEmbeddingModel(undefined);
|
||||
setMatchThreshold(DEFAULT_MATCH_THRESHOLD);
|
||||
setEscalationKeywords(DEFAULT_ESCALATION_KEYWORDS);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let ignore = false;
|
||||
|
||||
|
|
@ -126,7 +113,6 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({
|
|||
setModelsLoadState("loading");
|
||||
setModelInfo([]);
|
||||
setSelectedPreset(undefined);
|
||||
resetToCustom();
|
||||
try {
|
||||
const uniqueModels = await fetchAvailableModels(accessToken);
|
||||
if (ignore) return;
|
||||
|
|
@ -143,7 +129,7 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({
|
|||
return () => {
|
||||
ignore = true;
|
||||
};
|
||||
}, [accessToken, resetToCustom]);
|
||||
}, [accessToken]);
|
||||
|
||||
const isAdmin = all_admin_roles.includes(userRole);
|
||||
|
||||
|
|
@ -170,7 +156,16 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({
|
|||
const handlePresetChange = (presetKey: string | undefined) => {
|
||||
if (!presetKey || presetKey === "custom") {
|
||||
setSelectedPreset(presetKey);
|
||||
resetToCustom();
|
||||
setComplexityRouterConfig({
|
||||
tiers: { SIMPLE: [], MEDIUM: [], COMPLEX: [], REASONING: [] },
|
||||
classifier_type: "heuristic",
|
||||
});
|
||||
setCustomTechnicalKeywords([]);
|
||||
setKeywordTierRules([]);
|
||||
setSemanticMatchingEnabled(false);
|
||||
setEmbeddingModel(undefined);
|
||||
setMatchThreshold(DEFAULT_MATCH_THRESHOLD);
|
||||
setEscalationKeywords(DEFAULT_ESCALATION_KEYWORDS);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue