From d7e31e73c00e6cf7b59cf399512638873088944e Mon Sep 17 00:00:00 2001 From: ryan-crabbe-berri Date: Thu, 23 Jul 2026 13:36:36 -0700 Subject: [PATCH] refactor(ui): give each Cost Optimization tab its own route Split the Cost Optimization page's four tabs (Usage, Prompt Compression, Autorouter, Prompt Caching) into their own prerendered paths under /cost-optimization, mirroring the Models + Endpoints and Caching per-tab routing. A shared layout renders the header, the experimental banner and the tab bar, deriving the active tab from the pathname; each tab is its own page.tsx that pulls only the props it needs, so deep links and hard-loads to /cost-optimization/compression, /autorouter and /caching resolve to real static HTML with no nginx change. The former CostOptimizationView is removed. The tab bar is rebuilt on the shadcn Tabs primitive and the antd Alert becomes a plain banner, so the page adds no new antd usage. --- .../_components/CostOptimizationView.test.tsx | 34 -------- .../_components/CostOptimizationView.tsx | 81 ------------------ .../cost-optimization/autorouter/page.tsx | 9 ++ .../cost-optimization/caching/page.tsx | 9 ++ .../cost-optimization/compression/page.tsx | 9 ++ .../cost-optimization/layout.test.tsx | 82 +++++++++++++++++++ .../(dashboard)/cost-optimization/layout.tsx | 82 +++++++++++++++++++ .../(dashboard)/cost-optimization/page.tsx | 4 +- .../cost-optimization/tabRoutes.test.ts | 38 +++++++++ .../cost-optimization/tabRoutes.ts | 21 +++++ 10 files changed, 252 insertions(+), 117 deletions(-) delete mode 100644 ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.test.tsx delete mode 100644 ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/autorouter/page.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/caching/page.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/compression/page.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/layout.test.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/layout.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/tabRoutes.test.ts create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/tabRoutes.ts diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.test.tsx deleted file mode 100644 index 46aa23fcfc0..00000000000 --- a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.test.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { fireEvent, render } from "@testing-library/react"; -import { describe, expect, it, vi } from "vitest"; - -vi.mock("./UsageTab", () => ({ __esModule: true, default: () =>
})); -vi.mock("./PromptCompressionTab", () => ({ __esModule: true, default: () =>
})); -vi.mock("./AutorouterTab", () => ({ __esModule: true, default: () =>
})); -vi.mock("./PromptCachingTab", () => ({ __esModule: true, default: () =>
})); - -import CostOptimizationView from "./CostOptimizationView"; - -const renderView = () => render(); - -describe("CostOptimizationView", () => { - it("renders all four cost-optimization tabs", () => { - const { getByText } = renderView(); - - expect(getByText("Usage")).toBeInTheDocument(); - expect(getByText("Prompt Compression")).toBeInTheDocument(); - expect(getByText("Autorouter")).toBeInTheDocument(); - expect(getByText("Prompt Caching")).toBeInTheDocument(); - }); - - it("defaults to the Usage tab and switches the active tab on click", () => { - const { getByRole } = renderView(); - - expect(getByRole("tab", { name: "Usage" })).toHaveAttribute("aria-selected", "true"); - expect(getByRole("tab", { name: "Prompt Compression" })).toHaveAttribute("aria-selected", "false"); - - fireEvent.click(getByRole("tab", { name: "Prompt Compression" })); - - expect(getByRole("tab", { name: "Usage" })).toHaveAttribute("aria-selected", "false"); - expect(getByRole("tab", { name: "Prompt Compression" })).toHaveAttribute("aria-selected", "true"); - }); -}); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.tsx deleted file mode 100644 index 3bab6afee57..00000000000 --- a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.tsx +++ /dev/null @@ -1,81 +0,0 @@ -"use client"; - -import React from "react"; -import { PiggyBank } from "lucide-react"; -import { Alert, Tabs } from "antd"; - -import UsageTab from "./UsageTab"; -import PromptCompressionTab from "./PromptCompressionTab"; -import AutorouterTab from "./AutorouterTab"; -import PromptCachingTab from "./PromptCachingTab"; -import { useDailyActivityRange } from "./useDailyActivityRange"; - -interface CostOptimizationViewProps { - accessToken: string | null; - userId: string | null; - userRole: string; -} - -const CostOptimizationView: React.FC = ({ accessToken, userId, userRole }) => { - const activity = useDailyActivityRange(accessToken, userId, userRole); - - const items = [ - { - key: "usage", - label: "Usage", - children: , - }, - { - key: "compression", - label: "Prompt Compression", - children: , - }, - { - key: "autorouter", - label: "Autorouter", - children: , - }, - { - key: "caching", - label: "Prompt Caching", - children: , - }, - ]; - - return ( -
-
-
- -

Cost Optimization

-
-

- Track and configure the mechanisms that save you money: prompt compression, prompt caching, and auto routing -

-
- - - Have feedback? Join the discussion{" "} - - here - - - } - /> - - -
- ); -}; - -export default CostOptimizationView; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/autorouter/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/autorouter/page.tsx new file mode 100644 index 00000000000..6cf9700fdcb --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/autorouter/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +import AutorouterTab from "@/app/(dashboard)/cost-optimization/_components/AutorouterTab"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; + +export default function AutorouterPage() { + const { accessToken, userId, userRole } = useAuthorized(); + return ; +} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/caching/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/caching/page.tsx new file mode 100644 index 00000000000..de3f628829b --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/caching/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +import PromptCachingTab from "@/app/(dashboard)/cost-optimization/_components/PromptCachingTab"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; + +export default function PromptCachingPage() { + const { accessToken } = useAuthorized(); + return ; +} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/compression/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/compression/page.tsx new file mode 100644 index 00000000000..6bfbff16d84 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/compression/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +import PromptCompressionTab from "@/app/(dashboard)/cost-optimization/_components/PromptCompressionTab"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; + +export default function PromptCompressionPage() { + const { accessToken } = useAuthorized(); + return ; +} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/layout.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/layout.test.tsx new file mode 100644 index 00000000000..39e36935c88 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/layout.test.tsx @@ -0,0 +1,82 @@ +/* @vitest-environment jsdom */ +import { act, render } from "@testing-library/react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import CostOptimizationLayout from "./layout"; + +const { mockPush, navState } = vi.hoisted(() => ({ + mockPush: vi.fn(), + navState: { pathname: "/cost-optimization" }, +})); +vi.mock("next/navigation", () => ({ + usePathname: () => navState.pathname, + useRouter: () => ({ push: mockPush }), +})); + +vi.mock("@/components/networking", () => ({ serverRootPath: "" })); + +const renderLayout = () => + render( + +
CHILD
+
, + ); + +describe("CostOptimizationLayout", () => { + beforeEach(() => { + navState.pathname = "/cost-optimization"; + mockPush.mockClear(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (global as any).ResizeObserver = class { + observe() {} + unobserve() {} + disconnect() {} + }; + }); + + it("renders the tab bar and the active tab's page content", () => { + const { getByRole, getByTestId } = renderLayout(); + expect(getByRole("tab", { name: "Usage" })).toBeInTheDocument(); + expect(getByRole("tab", { name: "Prompt Compression" })).toBeInTheDocument(); + expect(getByRole("tab", { name: "Autorouter" })).toBeInTheDocument(); + expect(getByRole("tab", { name: "Prompt Caching" })).toBeInTheDocument(); + expect(getByTestId("tab-content")).toHaveTextContent("CHILD"); + }); + + it("marks the base route's Usage tab active", () => { + const { getByRole } = renderLayout(); + expect(getByRole("tab", { name: "Usage" })).toHaveAttribute("aria-selected", "true"); + expect(getByRole("tab", { name: "Prompt Compression" })).toHaveAttribute("aria-selected", "false"); + }); + + it("navigates to a tab's path when its tab is clicked", async () => { + const { getByRole } = renderLayout(); + await act(async () => { + getByRole("tab", { name: "Prompt Compression" }).click(); + }); + expect(mockPush).toHaveBeenCalledWith(expect.stringMatching(/\/cost-optimization\/compression\/$/)); + }); + + it("routes the base tab back to the cost-optimization root (no slug)", async () => { + navState.pathname = "/cost-optimization/autorouter"; + const { getByRole } = renderLayout(); + await act(async () => { + getByRole("tab", { name: "Usage" }).click(); + }); + expect(mockPush).toHaveBeenCalledWith(expect.stringMatching(/\/cost-optimization\/$/)); + }); + + it("redirects to the base cost-optimization path when the tab slug is unknown", async () => { + const replaceMock = vi.fn(); + const originalLocation = window.location; + Object.defineProperty(window, "location", { + configurable: true, + value: { replace: replaceMock, assign: vi.fn(), href: "http://localhost/", pathname: "/", search: "" }, + }); + navState.pathname = "/cost-optimization/bogus"; + await act(async () => { + renderLayout(); + }); + expect(replaceMock).toHaveBeenCalledWith(expect.stringMatching(/\/cost-optimization\/$/)); + Object.defineProperty(window, "location", { configurable: true, value: originalLocation }); + }); +}); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/layout.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/layout.tsx new file mode 100644 index 00000000000..02eadc96e96 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/layout.tsx @@ -0,0 +1,82 @@ +"use client"; + +import type { ReactNode } from "react"; +import { useEffect } from "react"; +import { usePathname, useRouter } from "next/navigation"; +import { PiggyBank, Info } from "lucide-react"; +import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { + costOptimizationTabHref, + slugFromPathname, + type CostOptimizationTabSlug, +} from "@/app/(dashboard)/cost-optimization/tabRoutes"; + +const BASE_TAB_KEY = "usage"; + +const ORDERED_KEYS: Array<"" | CostOptimizationTabSlug> = ["", "compression", "autorouter", "caching"]; + +const TAB_LABELS: Record<"" | CostOptimizationTabSlug, string> = { + "": "Usage", + compression: "Prompt Compression", + autorouter: "Autorouter", + caching: "Prompt Caching", +}; + +export default function CostOptimizationLayout({ children }: { children: ReactNode }) { + const pathname = usePathname(); + const router = useRouter(); + + const activeSlug = slugFromPathname(pathname); + const isKnownSlug = ORDERED_KEYS.some((slug) => slug === activeSlug); + const activeKey = isKnownSlug ? activeSlug || BASE_TAB_KEY : BASE_TAB_KEY; + + useEffect(() => { + if (activeSlug !== "" && !isKnownSlug) { + window.location.replace(costOptimizationTabHref("")); + } + }, [activeSlug, isKnownSlug]); + + return ( +
+
+
+ +

Cost Optimization

+
+

+ Track and configure the mechanisms that save you money: prompt compression, prompt caching, and auto routing +

+
+ +
+ + + This is an experimental dashboard. Have feedback? Join the discussion{" "} + + here + + +
+ + router.push(costOptimizationTabHref(key === BASE_TAB_KEY ? "" : key))} + > + + {ORDERED_KEYS.map((slug) => ( + + {TAB_LABELS[slug]} + + ))} + + + +
{children}
+
+ ); +} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/page.tsx index e82cc633ae2..e90af9ac37d 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/page.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/page.tsx @@ -1,9 +1,9 @@ "use client"; -import CostOptimizationView from "./_components/CostOptimizationView"; +import UsageTab from "./_components/UsageTab"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; export default function CostOptimizationPage() { const { accessToken, userId, userRole } = useAuthorized(); - return ; + return ; } diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/tabRoutes.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/tabRoutes.test.ts new file mode 100644 index 00000000000..49b9825a677 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/tabRoutes.test.ts @@ -0,0 +1,38 @@ +/* @vitest-environment jsdom */ +import { describe, expect, it, vi } from "vitest"; + +vi.mock("@/components/networking", () => ({ serverRootPath: "" })); + +import { COST_OPTIMIZATION_TAB_SLUGS, costOptimizationTabHref, slugFromPathname } from "./tabRoutes"; + +describe("slugFromPathname", () => { + it("returns empty string for the base path with or without a trailing slash", () => { + expect(slugFromPathname("/cost-optimization")).toBe(""); + expect(slugFromPathname("/cost-optimization/")).toBe(""); + }); + + it("extracts the tab slug from dev and proxy-mounted (/ui) paths", () => { + expect(slugFromPathname("/cost-optimization/autorouter")).toBe("autorouter"); + expect(slugFromPathname("/ui/cost-optimization/compression/")).toBe("compression"); + }); + + it("returns the raw segment for an unknown tab so the layout can redirect to base", () => { + expect(slugFromPathname("/ui/cost-optimization/bogus")).toBe("bogus"); + }); + + it("returns empty string when the cost-optimization base segment is not in the path", () => { + expect(slugFromPathname("/teams")).toBe(""); + }); +}); + +describe("costOptimizationTabHref", () => { + it("builds the trailing-slash base href for the empty slug", () => { + expect(costOptimizationTabHref("")).toBe("/ui/cost-optimization/"); + }); + + it("builds a trailing-slash href for every tab slug (required by static export)", () => { + for (const slug of COST_OPTIMIZATION_TAB_SLUGS) { + expect(costOptimizationTabHref(slug)).toBe(`/ui/cost-optimization/${slug}/`); + } + }); +}); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/tabRoutes.ts b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/tabRoutes.ts new file mode 100644 index 00000000000..df4429139a7 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/tabRoutes.ts @@ -0,0 +1,21 @@ +import { migratedHref } from "@/utils/migratedPages"; + +export const COST_OPTIMIZATION_BASE_SEGMENT = "cost-optimization"; + +export const COST_OPTIMIZATION_TAB_SLUGS = ["compression", "autorouter", "caching"] as const; + +export type CostOptimizationTabSlug = (typeof COST_OPTIMIZATION_TAB_SLUGS)[number]; + +export function costOptimizationTabHref(slug: string): string { + const base = migratedHref(COST_OPTIMIZATION_BASE_SEGMENT); + return slug ? `${base}/${slug}/` : `${base}/`; +} + +export function slugFromPathname(pathname: string): string { + const parts = pathname.split("/").filter(Boolean); + const idx = parts.indexOf(COST_OPTIMIZATION_BASE_SEGMENT); + if (idx === -1) { + return ""; + } + return parts[idx + 1] ?? ""; +}