From 5a40bca0ceb8612b08943a6caa0a16831e130f49 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 24 Apr 2026 13:49:29 +0000 Subject: [PATCH] feat(ui): migrate activity_metrics tremor Card/Grid/Text/Title to shadcn Split the tremor import so only AreaChart/BarChart remain; replace the Grid layout with Tailwind grid classes, Card with shadcn Card (adding explicit p-6 to preserve tremor's default padding), Text with

tags, and Title with

. Swap raw text-gray-500/bg-gray-50 to semantic text-muted-foreground/bg-muted. Update the test mock + heading level query (Title used to render

, now

). Co-authored-by: yuneng-jiang --- ui/litellm-dashboard/docs/BLOCKERS.md | 11 ++ .../src/components/activity_metrics.test.tsx | 8 +- .../src/components/activity_metrics.tsx | 128 +++++++++--------- 3 files changed, 77 insertions(+), 70 deletions(-) diff --git a/ui/litellm-dashboard/docs/BLOCKERS.md b/ui/litellm-dashboard/docs/BLOCKERS.md index c098fa20c91..a57cfd11cb6 100644 --- a/ui/litellm-dashboard/docs/BLOCKERS.md +++ b/ui/litellm-dashboard/docs/BLOCKERS.md @@ -131,6 +131,17 @@ stay on antd for phase 1 and will be addressed in a targeted follow-up. antd Form via `form.setFieldValue`. `MCPPermissionManagement.test.tsx` wraps it in an antd `
` harness. Cannot migrate independently of the parent forms. Defer with parents. +- `src/components/usage.tsx` (949 LoC): legacy admin usage page with a very + wide tremor surface — BarList, DonutChart, AreaChart, DateRangePickerValue, + Tremor MultiSelect / MultiSelectItem (no direct shadcn primitive — requires + a custom searchable-multi-select combobox), Select/SelectItem used with + the Tremor API, plus TabGroup / TabList / Tab / TabPanels / TabPanel driving + four distinct admin reports, and Tremor Table inside Card wrappers for tag / + provider / customer views. Accurate migration requires building the same + searchable MultiSelect primitive blocking ChatUI, migrating the tab content + into value-keyed shadcn Tabs, and splitting the many chart imports from the + non-chart primitives in a way that preserves the categorical color palettes. + Exceeds the two-attempt budget for this run — defer. - `src/components/playground/chat_ui/ChatUI.tsx` (2239 LoC): chrome-only migration blocked by deeply-coupled antd `Select.OptGroup` + `optionLabelProp` + `maxTagCount="responsive"` + custom `filterOption` on the MCP servers diff --git a/ui/litellm-dashboard/src/components/activity_metrics.test.tsx b/ui/litellm-dashboard/src/components/activity_metrics.test.tsx index 308b4b395f7..c8401ddba8c 100644 --- a/ui/litellm-dashboard/src/components/activity_metrics.test.tsx +++ b/ui/litellm-dashboard/src/components/activity_metrics.test.tsx @@ -16,10 +16,6 @@ beforeAll(() => { }); vi.mock("@tremor/react", () => ({ - Card: ({ children }: { children: React.ReactNode }) =>
{children}
, - Grid: ({ children }: { children: React.ReactNode }) =>
{children}
, - Text: ({ children }: { children: React.ReactNode }) => {children}, - Title: ({ children }: { children: React.ReactNode }) =>

{children}

, AreaChart: () =>
AreaChart
, BarChart: () =>
BarChart
, })); @@ -270,7 +266,7 @@ describe("ActivityMetrics", () => { }; render(); - const headers = screen.getAllByRole("heading", { level: 2 }); + const headers = screen.getAllByRole("heading", { level: 3 }); const gpt4Index = headers.findIndex((h) => h.textContent?.includes("GPT-4")); const gpt35Index = headers.findIndex((h) => h.textContent?.includes("GPT-3.5")); expect(gpt4Index).toBeLessThan(gpt35Index); @@ -401,7 +397,7 @@ describe("ActivityMetrics", () => { }; render(); - const headings = screen.getAllByRole("heading", { level: 2 }); + const headings = screen.getAllByRole("heading", { level: 3 }); const gpt4Index = headings.findIndex((h) => h.textContent?.includes("GPT-4")); const unknownIndex = headings.findIndex((h) => h.textContent?.includes("Unknown")); expect(gpt4Index).toBeLessThan(unknownIndex); diff --git a/ui/litellm-dashboard/src/components/activity_metrics.tsx b/ui/litellm-dashboard/src/components/activity_metrics.tsx index d61fa5a05b0..8247b5b7475 100644 --- a/ui/litellm-dashboard/src/components/activity_metrics.tsx +++ b/ui/litellm-dashboard/src/components/activity_metrics.tsx @@ -1,7 +1,7 @@ import { formatNumberWithCommas } from "@/utils/dataUtils"; import { resolveTeamAliasFromTeamID } from "@/utils/teamUtils"; -// eslint-disable-next-line litellm-ui/no-banned-ui-imports -import { AreaChart, BarChart, Card, Grid, Text, Title } from "@tremor/react"; +import { AreaChart, BarChart } from "@tremor/react"; +import { Card } from "@/components/ui/card"; import { Accordion, AccordionContent, @@ -32,45 +32,45 @@ const ModelSection = ({ return (
{/* Summary Cards */} - - - Total Requests - {metrics.total_requests.toLocaleString()} +
+ +

Total Requests

+

{metrics.total_requests.toLocaleString()}

- - Total Successful Requests - {metrics.total_successful_requests.toLocaleString()} + +

Total Successful Requests

+

{metrics.total_successful_requests.toLocaleString()}

- - Total Tokens - {metrics.total_tokens.toLocaleString()} - {Math.round(metrics.total_tokens / metrics.total_successful_requests)} avg per successful request + +

Total Tokens

+

{metrics.total_tokens.toLocaleString()}

+

{Math.round(metrics.total_tokens / metrics.total_successful_requests)} avg per successful request

- - Total Spend - ${formatNumberWithCommas(metrics.total_spend, 2)} - + +

Total Spend

+

${formatNumberWithCommas(metrics.total_spend, 2)}

+

${formatNumberWithCommas(metrics.total_spend / metrics.total_successful_requests, 3)} per successful request - +

- +
{metrics.top_api_keys && metrics.top_api_keys.length > 0 && ( - - Top Virtual Keys by Spend + +

Top Virtual Keys by Spend

{metrics.top_api_keys.map((keyData, index) => ( -
+
- {keyData.key_alias || `${keyData.api_key.substring(0, 10)}...`} - {keyData.team_id && Team: {keyData.team_id}} +

{keyData.key_alias || `${keyData.api_key.substring(0, 10)}...`}

+ {keyData.team_id &&

Team: {keyData.team_id}

}
- ${formatNumberWithCommas(keyData.spend, 2)} - +

${formatNumberWithCommas(keyData.spend, 2)}

+

{keyData.requests.toLocaleString()} requests | {keyData.tokens.toLocaleString()} tokens - +

))} @@ -84,9 +84,9 @@ const ModelSection = ({ )} {/* Spend per day - Full width card */} - +
- Spend per day +

Spend per day

{/* Charts */} - - +
+
- Total Tokens +

Total Tokens

- +
- Requests per day +

Requests per day

- +
- Success vs Failed Requests +

Success vs Failed Requests

{!hidePromptCachingMetrics && ( - +
- Prompt Caching Metrics +

Prompt Caching Metrics

- Cache Read: {metrics.total_cache_read_input_tokens?.toLocaleString() || 0} tokens - Cache Creation: {metrics.total_cache_creation_input_tokens?.toLocaleString() || 0} tokens +

Cache Read: {metrics.total_cache_read_input_tokens?.toLocaleString() || 0} tokens

+

Cache Creation: {metrics.total_cache_creation_input_tokens?.toLocaleString() || 0} tokens

)} - +
); }; @@ -265,30 +265,30 @@ export const ActivityMetrics: React.FC = ({ modelMetrics,
{/* Global Summary */}
- Overall Usage - - - Total Requests - {totalMetrics.total_requests.toLocaleString()} +

Overall Usage

+
+ +

Total Requests

+

{totalMetrics.total_requests.toLocaleString()}

- - Total Successful Requests - {totalMetrics.total_successful_requests.toLocaleString()} + +

Total Successful Requests

+

{totalMetrics.total_successful_requests.toLocaleString()}

- - Total Tokens - {totalMetrics.total_tokens.toLocaleString()} + +

Total Tokens

+

{totalMetrics.total_tokens.toLocaleString()}

- - Total Spend - ${formatNumberWithCommas(totalMetrics.total_spend, 2)} + +

Total Spend

+

${formatNumberWithCommas(totalMetrics.total_spend, 2)}

- +
- - +
+
- Total Tokens Over Time +

Total Tokens Over Time

= ({ modelMetrics, showLegend={false} /> - +
- Total Requests Over Time +

Total Requests Over Time

= ({ modelMetrics, showLegend={false} /> - +
{/* Individual Model Sections */} @@ -333,9 +333,9 @@ export const ActivityMetrics: React.FC = ({ modelMetrics,
- + <h3 className="text-lg font-semibold"> {modelMetrics[modelName].label || "Unknown Item"} - +

$