Merge pull request #17223 from BerriAI/litellm_router_fallback_dropdown_fix

[Fix] Change Add Fallback Modal to use Antd Select
This commit is contained in:
yuneng-jiang
2025-11-27 11:40:59 -08:00
committed by GitHub
2 changed files with 69 additions and 17 deletions
@@ -0,0 +1,47 @@
import { render, screen } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import AddFallbacks from "./add_fallbacks";
vi.mock("./networking", () => ({
setCallbacksCall: vi.fn(),
}));
vi.mock("./playground/llm_calls/fetch_models", () => ({
fetchAvailableModels: vi.fn(() =>
Promise.resolve([
{ model_group: "gpt-4" },
{ model_group: "gpt-3.5-turbo" },
{ model_group: "claude-3-opus" },
{ model_group: "claude-3-sonnet" },
]),
),
}));
vi.mock("./molecules/notifications_manager", () => ({
default: {
success: vi.fn(),
fromBackend: vi.fn(),
},
}));
describe("AddFallbacks", () => {
const mockAccessToken = "test-token";
const mockRouterSettings = { fallbacks: [] };
const mockSetRouterSettings = vi.fn();
beforeEach(() => {
vi.clearAllMocks();
});
it("should render the component", () => {
render(
<AddFallbacks
accessToken={mockAccessToken}
routerSettings={mockRouterSettings}
setRouterSettings={mockSetRouterSettings}
/>,
);
expect(screen.getByRole("button", { name: /Add Fallbacks/i })).toBeInTheDocument();
});
});
@@ -2,13 +2,12 @@
* Modal to add fallbacks to the proxy router config
*/
import React, { useState, useEffect } from "react";
import { Button } from "@tremor/react";
import { SearchSelect, SearchSelectItem } from "@tremor/react";
import { setCallbacksCall } from "./networking";
import { Modal, Form } from "antd";
import { fetchAvailableModels, ModelGroup } from "./playground/llm_calls/fetch_models";
import { Form, Modal, Select } from "antd";
import React, { useEffect, useState } from "react";
import NotificationManager from "./molecules/notifications_manager";
import { setCallbacksCall } from "./networking";
import { fetchAvailableModels, ModelGroup } from "./playground/llm_calls/fetch_models";
interface AddFallbacksProps {
models?: string[];
@@ -134,10 +133,10 @@ const AddFallbacks: React.FC<AddFallbacksProps> = ({ models, accessToken, router
rules={[{ required: true, message: "Please select the primary model that needs fallbacks" }]}
className="!mb-0"
>
<SearchSelect
<Select
placeholder="Select the model that needs fallback protection"
value={selectedModel}
onValueChange={(value: string) => {
value={selectedModel || undefined}
onChange={(value: string) => {
setSelectedModel(value);
// Remove the selected model from fallbacks if it was selected
const updatedFallbacks = selectedFallbacks.filter((model) => model !== value);
@@ -145,15 +144,18 @@ const AddFallbacks: React.FC<AddFallbacksProps> = ({ models, accessToken, router
form.setFieldValue("models", updatedFallbacks);
form.setFieldValue("model_name", value);
}}
showSearch
allowClear
style={{ width: "100%" }}
>
{Array.from(new Set(modelInfo.map((option) => option.model_group))).map(
(model: string, index: number) => (
<SearchSelectItem key={index} value={model}>
<Select.Option key={index} value={model}>
{model}
</SearchSelectItem>
</Select.Option>
),
)}
</SearchSelect>
</Select>
<p className="text-sm text-gray-500 mt-1">This is the primary model that users will request</p>
</Form.Item>
@@ -200,26 +202,29 @@ const AddFallbacks: React.FC<AddFallbacksProps> = ({ models, accessToken, router
)}
{/* Model selector */}
<SearchSelect
<Select
placeholder="Add a fallback model"
value=""
onValueChange={(value: string) => {
value={undefined}
onChange={(value: string) => {
if (value && !selectedFallbacks.includes(value)) {
const newFallbacks = [...selectedFallbacks, value];
setSelectedFallbacks(newFallbacks);
form.setFieldValue("models", newFallbacks);
}
}}
showSearch
allowClear
style={{ width: "100%" }}
>
{Array.from(new Set(modelInfo.map((option) => option.model_group)))
.filter((data: string) => data !== selectedModel && !selectedFallbacks.includes(data))
.sort()
.map((model: string) => (
<SearchSelectItem key={model} value={model}>
<Select.Option key={model} value={model}>
{model}
</SearchSelectItem>
</Select.Option>
))}
</SearchSelect>
</Select>
</div>
<p className="text-sm text-gray-500 mt-1">
<strong>Order matters:</strong> Models will be tried in the order shown above (1st, 2nd, 3rd, etc.)