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 <p> tags,
and Title with <h3>. 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 <h2>, now <h3>).

Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
Cursor Agent 2026-04-24 13:49:29 +00:00
parent 913030c3f5
commit 5a40bca0ce
No known key found for this signature in database
3 changed files with 77 additions and 70 deletions

View file

@ -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 `<Form form={form}>` 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

View file

@ -16,10 +16,6 @@ beforeAll(() => {
});
vi.mock("@tremor/react", () => ({
Card: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
Grid: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
Text: ({ children }: { children: React.ReactNode }) => <span>{children}</span>,
Title: ({ children }: { children: React.ReactNode }) => <h2>{children}</h2>,
AreaChart: () => <div>AreaChart</div>,
BarChart: () => <div>BarChart</div>,
}));
@ -270,7 +266,7 @@ describe("ActivityMetrics", () => {
};
render(<ActivityMetrics modelMetrics={multipleModels} />);
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(<ActivityMetrics modelMetrics={modelsWithEmptyKey} />);
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);

View file

@ -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 (
<div className="space-y-2">
{/* Summary Cards */}
<Grid numItems={4} className="gap-4">
<Card>
<Text>Total Requests</Text>
<Title>{metrics.total_requests.toLocaleString()}</Title>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<Card className="p-6">
<p className="text-sm text-muted-foreground">Total Requests</p>
<h3 className="text-lg font-semibold">{metrics.total_requests.toLocaleString()}</h3>
</Card>
<Card>
<Text>Total Successful Requests</Text>
<Title>{metrics.total_successful_requests.toLocaleString()}</Title>
<Card className="p-6">
<p className="text-sm text-muted-foreground">Total Successful Requests</p>
<h3 className="text-lg font-semibold">{metrics.total_successful_requests.toLocaleString()}</h3>
</Card>
<Card>
<Text>Total Tokens</Text>
<Title>{metrics.total_tokens.toLocaleString()}</Title>
<Text>{Math.round(metrics.total_tokens / metrics.total_successful_requests)} avg per successful request</Text>
<Card className="p-6">
<p className="text-sm text-muted-foreground">Total Tokens</p>
<h3 className="text-lg font-semibold">{metrics.total_tokens.toLocaleString()}</h3>
<p className="text-sm text-muted-foreground">{Math.round(metrics.total_tokens / metrics.total_successful_requests)} avg per successful request</p>
</Card>
<Card>
<Text>Total Spend</Text>
<Title>${formatNumberWithCommas(metrics.total_spend, 2)}</Title>
<Text>
<Card className="p-6">
<p className="text-sm text-muted-foreground">Total Spend</p>
<h3 className="text-lg font-semibold">${formatNumberWithCommas(metrics.total_spend, 2)}</h3>
<p className="text-sm text-muted-foreground">
${formatNumberWithCommas(metrics.total_spend / metrics.total_successful_requests, 3)} per successful request
</Text>
</p>
</Card>
</Grid>
</div>
{metrics.top_api_keys && metrics.top_api_keys.length > 0 && (
<Card className="mt-4">
<Title>Top Virtual Keys by Spend</Title>
<Card className="mt-4 p-6">
<h3 className="text-lg font-semibold">Top Virtual Keys by Spend</h3>
<div className="mt-3">
<div className="grid grid-cols-1 gap-2">
{metrics.top_api_keys.map((keyData, index) => (
<div key={keyData.api_key} className="flex justify-between items-center p-3 bg-gray-50 rounded-lg">
<div key={keyData.api_key} className="flex justify-between items-center p-3 bg-muted rounded-lg">
<div>
<Text className="font-medium">{keyData.key_alias || `${keyData.api_key.substring(0, 10)}...`}</Text>
{keyData.team_id && <Text className="text-xs text-gray-500">Team: {keyData.team_id}</Text>}
<p className="font-medium">{keyData.key_alias || `${keyData.api_key.substring(0, 10)}...`}</p>
{keyData.team_id && <p className="text-xs text-muted-foreground">Team: {keyData.team_id}</p>}
</div>
<div className="text-right">
<Text className="font-medium">${formatNumberWithCommas(keyData.spend, 2)}</Text>
<Text className="text-xs text-gray-500">
<p className="font-medium">${formatNumberWithCommas(keyData.spend, 2)}</p>
<p className="text-xs text-muted-foreground">
{keyData.requests.toLocaleString()} requests | {keyData.tokens.toLocaleString()} tokens
</Text>
</p>
</div>
</div>
))}
@ -84,9 +84,9 @@ const ModelSection = ({
)}
{/* Spend per day - Full width card */}
<Card className="mt-4">
<Card className="mt-4 p-6">
<div className="flex justify-between items-center">
<Title>Spend per day</Title>
<h3 className="text-lg font-semibold">Spend per day</h3>
<CustomLegend categories={["metrics.spend"]} colors={["green"]} />
</div>
<BarChart
@ -101,10 +101,10 @@ const ModelSection = ({
</Card>
{/* Charts */}
<Grid numItems={2} className="gap-4 mt-4">
<Card>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
<Card className="p-6">
<div className="flex justify-between items-center">
<Title>Total Tokens</Title>
<h3 className="text-lg font-semibold">Total Tokens</h3>
<CustomLegend
categories={["metrics.prompt_tokens", "metrics.completion_tokens", "metrics.total_tokens"]}
colors={["blue", "cyan", "indigo"]}
@ -122,9 +122,9 @@ const ModelSection = ({
/>
</Card>
<Card>
<Card className="p-6">
<div className="flex justify-between items-center">
<Title>Requests per day</Title>
<h3 className="text-lg font-semibold">Requests per day</h3>
<CustomLegend categories={["metrics.api_requests"]} colors={["blue"]} />
</div>
<BarChart
@ -139,9 +139,9 @@ const ModelSection = ({
/>
</Card>
<Card>
<Card className="p-6">
<div className="flex justify-between items-center">
<Title>Success vs Failed Requests</Title>
<h3 className="text-lg font-semibold">Success vs Failed Requests</h3>
<CustomLegend
categories={["metrics.successful_requests", "metrics.failed_requests"]}
colors={["green", "red"]}
@ -160,17 +160,17 @@ const ModelSection = ({
</Card>
{!hidePromptCachingMetrics && (
<Card>
<Card className="p-6">
<div className="flex justify-between items-center">
<Title>Prompt Caching Metrics</Title>
<h3 className="text-lg font-semibold">Prompt Caching Metrics</h3>
<CustomLegend
categories={["metrics.cache_read_input_tokens", "metrics.cache_creation_input_tokens"]}
colors={["cyan", "purple"]}
/>
</div>
<div className="mb-2">
<Text>Cache Read: {metrics.total_cache_read_input_tokens?.toLocaleString() || 0} tokens</Text>
<Text>Cache Creation: {metrics.total_cache_creation_input_tokens?.toLocaleString() || 0} tokens</Text>
<p className="text-sm text-muted-foreground">Cache Read: {metrics.total_cache_read_input_tokens?.toLocaleString() || 0} tokens</p>
<p className="text-sm text-muted-foreground">Cache Creation: {metrics.total_cache_creation_input_tokens?.toLocaleString() || 0} tokens</p>
</div>
<AreaChart
className="mt-4"
@ -184,7 +184,7 @@ const ModelSection = ({
/>
</Card>
)}
</Grid>
</div>
</div>
);
};
@ -265,30 +265,30 @@ export const ActivityMetrics: React.FC<ActivityMetricsProps> = ({ modelMetrics,
<div className="space-y-8">
{/* Global Summary */}
<div className="border rounded-lg p-4">
<Title>Overall Usage</Title>
<Grid numItems={4} className="gap-4 mb-4">
<Card>
<Text>Total Requests</Text>
<Title>{totalMetrics.total_requests.toLocaleString()}</Title>
<h3 className="text-lg font-semibold">Overall Usage</h3>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-4">
<Card className="p-6">
<p className="text-sm text-muted-foreground">Total Requests</p>
<h3 className="text-lg font-semibold">{totalMetrics.total_requests.toLocaleString()}</h3>
</Card>
<Card>
<Text>Total Successful Requests</Text>
<Title>{totalMetrics.total_successful_requests.toLocaleString()}</Title>
<Card className="p-6">
<p className="text-sm text-muted-foreground">Total Successful Requests</p>
<h3 className="text-lg font-semibold">{totalMetrics.total_successful_requests.toLocaleString()}</h3>
</Card>
<Card>
<Text>Total Tokens</Text>
<Title>{totalMetrics.total_tokens.toLocaleString()}</Title>
<Card className="p-6">
<p className="text-sm text-muted-foreground">Total Tokens</p>
<h3 className="text-lg font-semibold">{totalMetrics.total_tokens.toLocaleString()}</h3>
</Card>
<Card>
<Text>Total Spend</Text>
<Title>${formatNumberWithCommas(totalMetrics.total_spend, 2)}</Title>
<Card className="p-6">
<p className="text-sm text-muted-foreground">Total Spend</p>
<h3 className="text-lg font-semibold">${formatNumberWithCommas(totalMetrics.total_spend, 2)}</h3>
</Card>
</Grid>
</div>
<Grid numItems={2} className="gap-4">
<Card>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Card className="p-6">
<div className="flex justify-between items-center">
<Title>Total Tokens Over Time</Title>
<h3 className="text-lg font-semibold">Total Tokens Over Time</h3>
<CustomLegend
categories={["metrics.prompt_tokens", "metrics.completion_tokens", "metrics.total_tokens"]}
colors={["blue", "cyan", "indigo"]}
@ -305,9 +305,9 @@ export const ActivityMetrics: React.FC<ActivityMetricsProps> = ({ modelMetrics,
showLegend={false}
/>
</Card>
<Card>
<Card className="p-6">
<div className="flex justify-between items-center">
<Title>Total Requests Over Time</Title>
<h3 className="text-lg font-semibold">Total Requests Over Time</h3>
<CustomLegend
categories={["metrics.successful_requests", "metrics.failed_requests"]}
colors={["emerald", "red"]}
@ -324,7 +324,7 @@ export const ActivityMetrics: React.FC<ActivityMetricsProps> = ({ modelMetrics,
showLegend={false}
/>
</Card>
</Grid>
</div>
</div>
{/* Individual Model Sections */}
@ -333,9 +333,9 @@ export const ActivityMetrics: React.FC<ActivityMetricsProps> = ({ modelMetrics,
<AccordionItem key={modelName} value={modelName}>
<AccordionTrigger>
<div className="flex justify-between items-center w-full pr-2">
<Title>
<h3 className="text-lg font-semibold">
{modelMetrics[modelName].label || "Unknown Item"}
</Title>
</h3>
<div className="flex space-x-4 text-sm text-muted-foreground">
<span>
$