From a6474f7354e645309bac0a07223d42d9ebcc8b27 Mon Sep 17 00:00:00 2001 From: Tin Chi Lo Date: Sat, 18 Jul 2026 00:17:19 -0700 Subject: [PATCH] feat(ui): optimization type filter scoping cost savings KPIs, charts, and recent requests --- .../cost-savings/CostSavingsView.test.tsx | 122 ++++++++++ .../cost-savings/CostSavingsView.tsx | 211 +++++++++++++----- 2 files changed, 272 insertions(+), 61 deletions(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-savings/CostSavingsView.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-savings/CostSavingsView.test.tsx index 5c14e913e4d..7efb567a075 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/cost-savings/CostSavingsView.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-savings/CostSavingsView.test.tsx @@ -1,5 +1,6 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import * as networking from "@/components/networking"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; @@ -16,6 +17,12 @@ function renderView() { ); } +async function clickFilter(name: string) { + const radio = screen.getByRole("radio", { name }); + const label = radio.closest("label"); + await userEvent.click(label ?? radio); +} + beforeAll(() => { if (typeof window !== "undefined" && !window.ResizeObserver) { window.ResizeObserver = class ResizeObserver { @@ -86,6 +93,42 @@ const RECENT_RESPONSE: networking.RecentOptimizedRequestsResponse = { scanned_requests: 42, }; +const MIXED_RECENT_RESPONSE: networking.RecentOptimizedRequestsResponse = { + requests: [ + { + request_id: "req_cache_only", + start_time: "2026-07-15T12:00:00+00:00", + model: "claude-x", + total_tokens: 1000, + optimizations: ["caching"], + original_cost: 0.04, + optimized_cost: 0.03, + savings: 0.01, + }, + { + request_id: "req_compress_only", + start_time: "2026-07-15T12:01:00+00:00", + model: "claude-y", + total_tokens: 2000, + optimizations: ["compression"], + original_cost: 0.08, + optimized_cost: 0.05, + savings: 0.03, + }, + { + request_id: "req_both", + start_time: "2026-07-15T12:02:00+00:00", + model: "claude-z", + total_tokens: 3000, + optimizations: ["caching", "compression"], + original_cost: 0.12, + optimized_cost: 0.07, + savings: 0.05, + }, + ], + scanned_requests: 99, +}; + describe("formatUsd", () => { it("formats zero, cents, and sub-cent values", () => { expect(formatUsd(0)).toBe("$0"); @@ -158,4 +201,83 @@ describe("CostSavingsView", () => { expect(screen.getByText(/No optimized requests in this window/)).toBeInTheDocument(); }); }); + + it("filters the recent requests table by optimization type", async () => { + vi.mocked(networking.costSavingsRecentRequestsCall).mockResolvedValue(MIXED_RECENT_RESPONSE); + renderView(); + await waitFor(() => { + expect(screen.getByText("req_both")).toBeInTheDocument(); + }); + expect(screen.getByText("req_cache_only")).toBeInTheDocument(); + expect(screen.getByText("req_compress_only")).toBeInTheDocument(); + + await clickFilter("Compression"); + + expect(screen.queryByText("req_cache_only")).not.toBeInTheDocument(); + expect(screen.getByText("req_compress_only")).toBeInTheDocument(); + expect(screen.getByText("req_both")).toBeInTheDocument(); + + await clickFilter("Caching"); + + expect(screen.getByText("req_cache_only")).toBeInTheDocument(); + expect(screen.queryByText("req_compress_only")).not.toBeInTheDocument(); + expect(screen.getByText("req_both")).toBeInTheDocument(); + }); + + it("shows a type-specific empty state when the filter excludes every request", async () => { + vi.mocked(networking.costSavingsRecentRequestsCall).mockResolvedValue({ + requests: [ + { + request_id: "req_cache_only", + start_time: "2026-07-15T12:00:00+00:00", + model: "claude-x", + total_tokens: 1000, + optimizations: ["caching"], + original_cost: 0.04, + optimized_cost: 0.03, + savings: 0.01, + }, + ], + scanned_requests: 7, + }); + renderView(); + await waitFor(() => { + expect(screen.getByText("req_cache_only")).toBeInTheDocument(); + }); + + await clickFilter("Compression"); + + expect(screen.queryByText("req_cache_only")).not.toBeInTheDocument(); + expect(screen.getByText("No compression requests in this window.")).toBeInTheDocument(); + }); + + it("scopes the KPI cards and charts to the selected optimization type", async () => { + renderView(); + await waitFor(() => { + expect(screen.getAllByText("$2.00").length).toBeGreaterThan(0); + }); + expect(screen.getByText("Savings Distribution")).toBeInTheDocument(); + + await clickFilter("Compression"); + + expect(screen.queryByText("Total Savings")).not.toBeInTheDocument(); + expect(screen.queryByText("Caching Savings")).not.toBeInTheDocument(); + expect(screen.getByText("Compression Savings")).toBeInTheDocument(); + expect(screen.getAllByText("$0.50").length).toBeGreaterThan(0); + expect(screen.getByText("Tokens Compressed Away")).toBeInTheDocument(); + expect(screen.getByText("250")).toBeInTheDocument(); + expect(screen.queryByText("Savings Distribution")).not.toBeInTheDocument(); + + await clickFilter("Caching"); + + expect(screen.getByText("Caching Savings")).toBeInTheDocument(); + expect(screen.queryByText("Compression Savings")).not.toBeInTheDocument(); + expect(screen.getByText("Cached Tokens Read")).toBeInTheDocument(); + expect(screen.getByText("1,000")).toBeInTheDocument(); + + await clickFilter("All"); + + expect(screen.getByText("Total Savings")).toBeInTheDocument(); + expect(screen.getByText("Savings Distribution")).toBeInTheDocument(); + }); }); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-savings/CostSavingsView.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-savings/CostSavingsView.tsx index ac7377d206e..3877652049d 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/cost-savings/CostSavingsView.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-savings/CostSavingsView.tsx @@ -1,9 +1,9 @@ "use client"; import { useQuery } from "@tanstack/react-query"; -import { Alert, Card, Table, Tag, Typography } from "antd"; +import { Alert, Card, Segmented, Table, Tag, Typography } from "antd"; import type { ColumnsType } from "antd/es/table"; -import { ComponentProps, useState } from "react"; +import { ComponentProps, useMemo, useState } from "react"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import { MetricCard } from "@/components/GuardrailsMonitor/MetricCard"; import AdvancedDatePicker from "@/components/shared/advanced_date_picker"; @@ -15,18 +15,32 @@ import { costSavingsActivityCall, costSavingsRecentRequestsCall, OptimizedRequestSummary, + RecentOptimizedRequestsResponse, } from "@/components/networking"; type DateRangeValue = ComponentProps["value"]; -const SERIES_CATEGORIES = ["Caching", "Compression"] as const; -const SERIES_COLORS: readonly ChartColor[] = ["emerald", "blue"]; +const SERIES = [ + { key: "caching", name: "Caching", color: "emerald" }, + { key: "compression", name: "Compression", color: "blue" }, +] as const; + +const SERIES_CATEGORIES = SERIES.map((series) => series.name); +const SERIES_COLORS: readonly ChartColor[] = SERIES.map((series) => series.color); const OPTIMIZATION_TAG_COLOR: Record = { caching: "green", compression: "blue", }; +type OptimizationFilter = "all" | CostOptimizationType; + +const OPTIMIZATION_FILTER_OPTIONS: readonly { label: string; value: OptimizationFilter }[] = [ + { label: "All", value: "all" }, + { label: "Caching", value: "caching" }, + { label: "Compression", value: "compression" }, +]; + export function formatUsd(value: number): string { if (value === 0) return "$0"; const sign = value < 0 ? "-" : ""; @@ -97,9 +111,41 @@ const RECENT_REQUEST_COLUMNS: ColumnsType = [ interface SavingsKpiGridProps { totals: CostSavingsMetrics | undefined; + filter: OptimizationFilter; } -function SavingsKpiGrid({ totals }: SavingsKpiGridProps) { +const FILTERED_KPI_CONTENT = { + caching: { + savingsLabel: "Caching Savings", + savingsColor: "text-emerald-600", + tokensLabel: "Cached Tokens Read", + savingsOf: (totals: CostSavingsMetrics | undefined) => totals?.cache_savings ?? 0, + tokensOf: (totals: CostSavingsMetrics | undefined) => totals?.cache_read_input_tokens ?? 0, + }, + compression: { + savingsLabel: "Compression Savings", + savingsColor: "text-blue-600", + tokensLabel: "Tokens Compressed Away", + savingsOf: (totals: CostSavingsMetrics | undefined) => totals?.compression_savings ?? 0, + tokensOf: (totals: CostSavingsMetrics | undefined) => totals?.compression_saved_tokens ?? 0, + }, +} as const; + +function SavingsKpiGrid({ totals, filter }: SavingsKpiGridProps) { + if (filter !== "all") { + const content = FILTERED_KPI_CONTENT[filter]; + return ( +
+ + + +
+ ); + } return (
@@ -120,9 +166,58 @@ function SavingsKpiGrid({ totals }: SavingsKpiGridProps) { ); } +interface RecentOptimizedRequestsCardProps { + data: RecentOptimizedRequestsResponse | undefined; + isLoading: boolean; + optimizationFilter: OptimizationFilter; +} + +function RecentOptimizedRequestsCard({ data, isLoading, optimizationFilter }: RecentOptimizedRequestsCardProps) { + const requests = data?.requests; + const filteredRequests = useMemo(() => { + if (!requests) return []; + if (optimizationFilter === "all") return requests; + return requests.filter((request) => request.optimizations.includes(optimizationFilter)); + }, [requests, optimizationFilter]); + + return ( + +
+ + Recent Optimized Requests + + + Latest requests that benefited from caching or compression + {data ? ` (scanned last ${data.scanned_requests} requests in range)` : ""} + +
+ {isLoading ? ( +
+ +
+ ) : ( + + )} + + ); +} + export default function CostSavingsView() { const { accessToken } = useAuthorized(); const [dateValue, setDateValue] = useState(defaultDateRange); + const [optimizationFilter, setOptimizationFilter] = useState("all"); const startTime = dateValue.from; const endTime = dateValue.to; @@ -142,6 +237,9 @@ export default function CostSavingsView() { const totals = activityQuery.data?.totals; const unpricedModels = activityQuery.data?.unpriced_models ?? []; + const visibleSeries = SERIES.filter((series) => optimizationFilter === "all" || series.key === optimizationFilter); + const visibleCategories = visibleSeries.map((series) => series.name); + const visibleColors: readonly ChartColor[] = visibleSeries.map((series) => series.color); const chartData = activityQuery.data?.results.map((day) => ({ date: day.date, @@ -154,7 +252,6 @@ export default function CostSavingsView() { { name: "Compression", value: totals.compression_savings }, ] : []; - const recentRequests = recentQuery.data?.requests ?? []; return (
@@ -165,7 +262,14 @@ export default function CostSavingsView() { Savings from prompt caching and prompt compression
- +
+ + options={[...OPTIMIZATION_FILTER_OPTIONS]} + value={optimizationFilter} + onChange={setOptimizationFilter} + /> + +
{unpricedModels.length > 0 && ( @@ -178,10 +282,16 @@ export default function CostSavingsView() { /> )} - +
- + Savings Over Time @@ -193,65 +303,44 @@ export default function CostSavingsView() { className="mt-4" data={chartData} index="date" - categories={SERIES_CATEGORIES} - colors={SERIES_COLORS} + categories={visibleCategories} + colors={visibleColors} valueFormatter={formatUsd} yAxisWidth={80} /> )} - - - Savings Distribution - - By optimization type - {activityQuery.isLoading ? ( - - ) : ( - <> - - - - )} - + {optimizationFilter === "all" && ( + + + Savings Distribution + + By optimization type + {activityQuery.isLoading ? ( + + ) : ( + <> + + + + )} + + )}
- -
- - Recent Optimized Requests - - - Latest requests that benefited from caching or compression - {recentQuery.data ? ` (scanned last ${recentQuery.data.scanned_requests} requests in range)` : ""} - -
- {recentQuery.isLoading ? ( -
- -
- ) : ( -
- )} - + ); }