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 a7516b2a3a12..eeabfc681c85 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 @@ -102,6 +102,21 @@ const tierConfigSummary = (tiers: ComplexityTiers): string => { return parts.length > 0 ? parts.join(" ยท ") : "No tiers configured yet"; }; +// Why the submit is unavailable, or null when it is available. The button reads this to disable +// itself and to say what is missing, so the two can never give different answers. Checks the +// config actually being built, not which preset (if any) it came from: a preset only ever +// prefills once (handlePresetChange), and everything after that is edited exactly like Custom. +const getSubmitBlockedReason = ( + config: ComplexityRouterConfigValue, + keywordTierRules: KeywordTierRule[], + referencedModelsParams: Parameters[0], + availableModelSet: Set, +): string | null => + getMissingTiersError(config.tiers) ?? + getTierLabelsError(config.tier_labels) ?? + getKeywordTierRulesError(keywordTierRules) ?? + getReferencedModelsError(referencedModelsParams, availableModelSet); + const AddAutoRouterTab: React.FC = ({ handleOk, accessToken, @@ -222,15 +237,12 @@ const AddAutoRouterTab: React.FC = ({ embeddingModel, }; - // Why the submit is unavailable, or null when it is available. The button reads this to disable - // itself and to say what is missing, so the two can never give different answers. Checks the - // config actually being built, not which preset (if any) it came from: a preset only ever - // prefills once (handlePresetChange), and everything after that is edited exactly like Custom. - const submitBlockedReason = - getMissingTiersError(complexityRouterConfig.tiers) ?? - getTierLabelsError(complexityRouterConfig.tier_labels) ?? - getKeywordTierRulesError(keywordTierRules) ?? - getReferencedModelsError(referencedModelsParams, availableModelSet); + const submitBlockedReason = getSubmitBlockedReason( + complexityRouterConfig, + keywordTierRules, + referencedModelsParams, + availableModelSet, + ); const complexityRouterConfigParams: BuildComplexityRouterConfigParams = { tiers: complexityRouterConfig.tiers, diff --git a/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.test.ts b/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.test.ts index bcbf50bbdea2..187ec7070f2b 100644 --- a/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.test.ts +++ b/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.test.ts @@ -437,9 +437,8 @@ describe("getTierLabelsError", () => { }); it("accepts a full distinct rename", () => { - expect( - getTierLabelsError({ SIMPLE: "Cheap", MEDIUM: "Standard", COMPLEX: "Premium", REASONING: "Deep" }), - ).toBeNull(); + const fullRename = { SIMPLE: "Cheap", MEDIUM: "Standard", COMPLEX: "Premium", REASONING: "Deep" }; + expect(getTierLabelsError(fullRename)).toBeNull(); }); it("rejects two tiers sharing a name, which would be ambiguous in the logs", () => { @@ -473,9 +472,8 @@ describe("hydrateTierLabels", () => { }); it("drops non-string and blank values a hand-edited config could hold", () => { - expect(hydrateTierLabels({ SIMPLE: 7, MEDIUM: " ", COMPLEX: null, REASONING: "Deep" })).toEqual({ - REASONING: "Deep", - }); + const handEdited = { SIMPLE: 7, MEDIUM: " ", COMPLEX: null, REASONING: "Deep" }; + expect(hydrateTierLabels(handEdited)).toEqual({ REASONING: "Deep" }); }); it("ignores keys that are not tiers", () => {