diff --git a/ui/litellm-dashboard/src/components/router_settings/DefaultLitellmParamsSection.test.tsx b/ui/litellm-dashboard/src/components/router_settings/DefaultLitellmParamsSection.test.tsx deleted file mode 100644 index 8c640f3b91a..00000000000 --- a/ui/litellm-dashboard/src/components/router_settings/DefaultLitellmParamsSection.test.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import { describe, it, expect, vi } from "vitest"; -import { fireEvent, render, screen } from "@testing-library/react"; -import userEvent from "@testing-library/user-event"; -import DefaultLitellmParamsSection from "./DefaultLitellmParamsSection"; - -describe("DefaultLitellmParamsSection", () => { - it("should render the non-cache-control keys as JSON in the textarea", () => { - render(); - const textarea = screen.getByRole("textbox") as HTMLTextAreaElement; - expect(textarea.value).toContain('"timeout": 30'); - expect(textarea.value).toContain('"max_retries": 0'); - }); - - it("should not show the cache control editor when no injection points are set", () => { - render(); - expect(screen.queryByTestId("cache-control-location-select-0")).not.toBeInTheDocument(); - }); - - it("should show the cache control editor pre-populated when injection points are already set", () => { - render( - , - ); - expect(screen.getByTestId("cache-control-location-select-0")).toBeInTheDocument(); - }); - - it("should normalize nullable cache control fields from persisted settings", () => { - render( - , - ); - - expect(screen.getByTestId("cache-control-location-select-0")).toBeInTheDocument(); - expect(screen.getByTestId("cache-control-index-input-0")).toHaveValue(""); - }); - - it("should preserve unsupported cache control points in the JSON editor", () => { - render( - , - ); - - const textarea = screen.getByRole("textbox") as HTMLTextAreaElement; - expect(textarea.value).toContain('"location": "tool_config"'); - expect(screen.queryByTestId("cache-control-location-select-0")).not.toBeInTheDocument(); - }); - - it("should call onChange with cache_control_injection_points added when the toggle is switched on", async () => { - const onChange = vi.fn(); - const user = userEvent.setup(); - render(); - - await user.click(screen.getByRole("switch")); - - expect(onChange).toHaveBeenCalledWith({ timeout: 30, cache_control_injection_points: [{ location: "message" }] }); - }); - - it("should call onChange with cache_control_injection_points removed when the toggle is switched off", async () => { - const onChange = vi.fn(); - const user = userEvent.setup(); - render( - , - ); - - await user.click(screen.getByRole("switch")); - - expect(onChange).toHaveBeenCalledWith({ timeout: 30 }); - }); - - it("should merge edited JSON textarea content with cache_control_injection_points on blur", async () => { - const onChange = vi.fn(); - const user = userEvent.setup(); - render( - , - ); - - const textarea = screen.getByRole("textbox") as HTMLTextAreaElement; - await user.clear(textarea); - await user.type(textarea, '{{"timeout": 60}'); - await user.tab(); - - expect(onChange).toHaveBeenCalledWith({ timeout: 60, cache_control_injection_points: [{ location: "message" }] }); - }); - - it("should not call onChange and should flag the field as invalid when the textarea has invalid JSON on blur", async () => { - const onChange = vi.fn(); - const user = userEvent.setup(); - render(); - - const textarea = screen.getByRole("textbox") as HTMLTextAreaElement; - await user.clear(textarea); - await user.type(textarea, "not json"); - await user.tab(); - - expect(onChange).not.toHaveBeenCalled(); - expect(textarea).toHaveClass("ant-input-status-error"); - }); - - it("should clear the invalid state once the textarea is edited again", async () => { - const onChange = vi.fn(); - const user = userEvent.setup(); - render(); - - const textarea = screen.getByRole("textbox") as HTMLTextAreaElement; - await user.clear(textarea); - await user.type(textarea, "not json"); - await user.tab(); - expect(textarea).toHaveClass("ant-input-status-error"); - - await user.click(textarea); - await user.type(textarea, "{{}}"); - - expect(textarea).not.toHaveClass("ant-input-status-error"); - }); - - it("should reject valid JSON that is not an object", async () => { - const onChange = vi.fn(); - render(); - - const textarea = screen.getByRole("textbox") as HTMLTextAreaElement; - fireEvent.change(textarea, { target: { value: "[]" } }); - fireEvent.blur(textarea); - - expect(onChange).not.toHaveBeenCalled(); - expect(textarea).toHaveClass("ant-input-status-error"); - }); -}); diff --git a/ui/litellm-dashboard/src/components/router_settings/DefaultLitellmParamsSection.tsx b/ui/litellm-dashboard/src/components/router_settings/DefaultLitellmParamsSection.tsx deleted file mode 100644 index b934b100a88..00000000000 --- a/ui/litellm-dashboard/src/components/router_settings/DefaultLitellmParamsSection.tsx +++ /dev/null @@ -1,166 +0,0 @@ -import React from "react"; -import { Input, Switch } from "antd"; -import CacheControlInjectionPointsEditor, { - CacheControlInjectionPoint, -} from "../shared/cache_control_injection_points_editor"; -import NotificationsManager from "../molecules/notifications_manager"; - -interface DefaultLitellmParamsSectionProps { - value: Record; - onChange: (value: Record) => void; -} - -type ParsedDefaultParams = { status: "valid"; value: Record } | { status: "invalid"; message: string }; - -const CACHE_CONTROL_ROLES = ["user", "system", "assistant"] as const; -type CacheControlRole = (typeof CACHE_CONTROL_ROLES)[number]; - -const parseDefaultParams = (text: string): ParsedDefaultParams => { - try { - const parsed: unknown = JSON.parse(text || "{}"); - if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) { - return { status: "invalid", message: "Expected a JSON object" }; - } - return { status: "valid", value: parsed as Record }; - } catch (error) { - return { status: "invalid", message: error instanceof Error ? error.message : "Invalid JSON" }; - } -}; - -const isCacheControlRole = (value: unknown): value is CacheControlRole => - CACHE_CONTROL_ROLES.some((validRole) => validRole === value); - -const parseCacheControlInjectionPoint = (value: unknown): CacheControlInjectionPoint | undefined => { - if (typeof value !== "object" || value === null) { - return undefined; - } - if (!("location" in value) || value.location !== "message") { - return undefined; - } - const role = "role" in value ? value.role : undefined; - const index = "index" in value ? value.index : undefined; - const hasInvalidRole = role !== undefined && role !== null && !isCacheControlRole(role); - if (hasInvalidRole) { - return undefined; - } - const hasIndex = index !== undefined && index !== null; - const hasInvalidIndex = hasIndex && (typeof index !== "number" || !Number.isInteger(index)); - if (hasInvalidIndex) { - return undefined; - } - return { - location: "message", - ...(isCacheControlRole(role) ? { role } : {}), - ...(typeof index === "number" ? { index } : {}), - }; -}; - -const parseCacheControlInjectionPoints = (value: unknown): CacheControlInjectionPoint[] | undefined => { - if (!Array.isArray(value)) { - return undefined; - } - const points = value.map(parseCacheControlInjectionPoint); - return points.every((point) => point !== undefined) - ? points.filter((point): point is CacheControlInjectionPoint => point !== undefined) - : undefined; -}; - -const DefaultLitellmParamsSection: React.FC = ({ value, onChange }) => { - const cacheControlInjectionPoints = React.useMemo( - () => parseCacheControlInjectionPoints(value.cache_control_injection_points), - [value.cache_control_injection_points], - ); - const otherParams = React.useMemo( - () => - Object.fromEntries( - Object.entries(value).filter( - ([key]) => key !== "cache_control_injection_points" || cacheControlInjectionPoints === undefined, - ), - ), - [value, cacheControlInjectionPoints], - ); - - const [otherParamsText, setOtherParamsText] = React.useState(() => JSON.stringify(otherParams, null, 2)); - const [hasInvalidJson, setHasInvalidJson] = React.useState(false); - const showCacheControl = (cacheControlInjectionPoints?.length ?? 0) > 0; - - const handleOtherParamsBlur = () => { - const parsed = parseDefaultParams(otherParamsText); - if (parsed.status === "invalid") { - setHasInvalidJson(true); - NotificationsManager.warning(`Default LiteLLM Params is not valid JSON: ${parsed.message}`); - return; - } - setHasInvalidJson(false); - onChange( - cacheControlInjectionPoints - ? { ...parsed.value, cache_control_injection_points: cacheControlInjectionPoints } - : parsed.value, - ); - }; - - const handleCacheControlPointsChange = (points: CacheControlInjectionPoint[]) => { - const parsed = parseDefaultParams(otherParamsText); - const base = parsed.status === "valid" ? parsed.value : otherParams; - onChange(points.length > 0 ? { ...base, cache_control_injection_points: points } : base); - }; - - const handleCacheControlToggle = (checked: boolean) => { - handleCacheControlPointsChange(checked ? [{ location: "message" }] : []); - }; - - return ( -
-
- Default LiteLLM Params -

- Default parameters for Router.chat.completion.create. Set cache control injection points to enable prompt - caching for every model on this proxy.{" "} - - Learn more - -

- { - setOtherParamsText(e.target.value); - setHasInvalidJson(false); - }} - onBlur={handleOtherParamsBlur} - status={hasInvalidJson ? "error" : undefined} - autoSize={{ minRows: 2 }} - className="font-mono text-sm w-full" - /> -
- -
-
- - Cache Control Injection Points - - -
- {showCacheControl && ( -
- -
- )} -
-
- ); -}; - -export default DefaultLitellmParamsSection; diff --git a/ui/litellm-dashboard/src/components/router_settings/RouterSettingsForm.test.tsx b/ui/litellm-dashboard/src/components/router_settings/RouterSettingsForm.test.tsx index b8c2dc37587..90f2edb6b48 100644 --- a/ui/litellm-dashboard/src/components/router_settings/RouterSettingsForm.test.tsx +++ b/ui/litellm-dashboard/src/components/router_settings/RouterSettingsForm.test.tsx @@ -156,29 +156,14 @@ describe("RouterSettingsForm", () => { ); }); - it("should show frontend-defined Default LiteLLM Params without backend field metadata", () => { - render(); - expect(screen.getByText("Cache Control Injection Points")).toBeInTheDocument(); - }); - - it("should call onChange with the updated default_litellm_params when the section changes", async () => { - const onChange = vi.fn(); - const user = userEvent.setup(); + it("should not render Default LiteLLM Params when persisted values exist", () => { const props = { ...baseProps, - onChange, value: { ...defaultValue, routerSettings: { default_litellm_params: { timeout: 30 } } }, }; render(); - await user.click(screen.getByRole("switch", { name: "Cache Control Injection Points" })); - - expect(onChange).toHaveBeenCalledWith( - expect.objectContaining({ - routerSettings: expect.objectContaining({ - default_litellm_params: { timeout: 30, cache_control_injection_points: [{ location: "message" }] }, - }), - }), - ); + expect(screen.queryByText("Default LiteLLM Params")).not.toBeInTheDocument(); + expect(screen.queryByText("Cache Control Injection Points")).not.toBeInTheDocument(); }); }); diff --git a/ui/litellm-dashboard/src/components/router_settings/RouterSettingsForm.tsx b/ui/litellm-dashboard/src/components/router_settings/RouterSettingsForm.tsx index d53e58b5049..b1d95d228fb 100644 --- a/ui/litellm-dashboard/src/components/router_settings/RouterSettingsForm.tsx +++ b/ui/litellm-dashboard/src/components/router_settings/RouterSettingsForm.tsx @@ -1,5 +1,4 @@ import React from "react"; -import DefaultLitellmParamsSection from "./DefaultLitellmParamsSection"; import LatencyBasedConfiguration from "./LatencyBasedConfiguration"; import OptionalPreCallChecksSelector from "./OptionalPreCallChecksSelector"; import ReliabilityRetriesSection from "./ReliabilityRetriesSection"; @@ -48,13 +47,6 @@ const RouterSettingsForm: React.FC = ({ }); }; - const handleDefaultLitellmParamsChange = (params: Record) => { - onChange({ - ...value, - routerSettings: { ...value.routerSettings, default_litellm_params: params }, - }); - }; - return (
{/* Routing Settings Section */} @@ -89,20 +81,11 @@ const RouterSettingsForm: React.FC = ({ />
- {/* Divider */} -
- {/* Strategy-Specific Args - Show immediately after strategy if latency-based */} {value.selectedStrategy === "latency-based-routing" && ( )} - {/* Default LiteLLM Params */} -
{/* Other Settings */} diff --git a/ui/litellm-dashboard/src/components/router_settings/index.test.tsx b/ui/litellm-dashboard/src/components/router_settings/index.test.tsx index d35261b77e6..46d0bdaeb8c 100644 --- a/ui/litellm-dashboard/src/components/router_settings/index.test.tsx +++ b/ui/litellm-dashboard/src/components/router_settings/index.test.tsx @@ -139,6 +139,8 @@ describe("RouterSettings", () => { await waitFor(() => { expect(screen.getByTestId("strategy-select")).toBeInTheDocument(); }); + expect(screen.queryByText("Default LiteLLM Params")).not.toBeInTheDocument(); + expect(screen.queryByText("Cache Control Injection Points")).not.toBeInTheDocument(); await user.click(screen.getByRole("button", { name: /save changes/i }));