feat(ui): ModelsCell + ResponseMetrics to shadcn

- ModelsCell: @tremor Badge/Icon/TableCell/Text + @heroicons Chevron
  \u2192 shadcn Badge + preserved tremor TableCell (whitelisted) + lucide
  ChevronDown/ChevronRight. Categorical red/emerald/blue palettes for
  all-proxy / access_group / direct models; file added to overrides.
- ResponseMetrics: antd Tooltip + @ant-design icons (Clock/Number/
  Import/Export/Bulb/Tool/Dollar) \u2192 shadcn Tooltip + lucide
  equivalents wrapped in a TooltipIconStat helper.

Gates: TS \u2713 | Build \u2713.

Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
Cursor Agent 2026-04-23 12:18:00 +00:00
parent ca2c45e5e7
commit 6fa4d35d7d
No known key found for this signature in database
3 changed files with 144 additions and 87 deletions

View file

@ -59,6 +59,7 @@
"src/components/guardrails/content_filter/PatternTable.tsx",
"src/components/guardrails/content_filter/ContentFilterDisplay.tsx",
"src/components/prompts/prompt_editor_view/PromptEditorHeader.tsx",
"src/app/(dashboard)/teams/components/TeamsTable/ModelsCell.tsx",
"src/components/policies/impact_preview_alert.tsx",
"src/components/policies/impact_popover.tsx",
"src/components/DeletedKeysPage/DeletedKeysPage.tsx",

View file

@ -1,5 +1,8 @@
import { Badge, Icon, TableCell, Text } from "@tremor/react";
import { ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/outline";
import { Badge } from "@/components/ui/badge";
// eslint-disable-next-line litellm-ui/no-banned-ui-imports
import { TableCell } from "@tremor/react";
import { ChevronDown, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { getModelDisplayName } from "@/components/key_team_helpers/fetch_available_models_team_key";
import React, { useMemo, useState } from "react";
import { Team } from "@/components/key_team_helpers/key_list";
@ -16,7 +19,10 @@ interface ModelEntry {
const ModelsCell = ({ team }: ModelsCellProps) => {
const [expandedAccordion, setExpandedAccordion] = useState<boolean>(false);
const isAllModels = !team.models || team.models.length === 0 || team.models.includes("all-proxy-models");
const isAllModels =
!team.models ||
team.models.length === 0 ||
team.models.includes("all-proxy-models");
const modelEntries: ModelEntry[] = useMemo(() => {
if (isAllModels) return [];
@ -33,21 +39,33 @@ const ModelsCell = ({ team }: ModelsCellProps) => {
const renderBadge = (entry: ModelEntry, index: number) => {
if (entry.name === "all-proxy-models") {
return (
<Badge key={index} size={"xs"} color="red">
<Text>All Proxy Models</Text>
<Badge
key={index}
className="bg-red-100 text-red-700 dark:bg-red-950 dark:text-red-300 text-xs"
>
All Proxy Models
</Badge>
);
}
const displayName = getModelDisplayName(entry.name);
const truncated = displayName.length > 30 ? `${displayName.slice(0, 30)}...` : displayName;
const truncated =
displayName.length > 30 ? `${displayName.slice(0, 30)}...` : displayName;
return (
<Badge
key={index}
size={"xs"}
color={entry.source === "access_group" ? "green" : "blue"}
title={entry.source === "access_group" ? "From access group" : "Direct assignment"}
className={cn(
"text-xs",
entry.source === "access_group"
? "bg-emerald-100 text-emerald-700 dark:bg-emerald-950 dark:text-emerald-300"
: "bg-blue-100 text-blue-700 dark:bg-blue-950 dark:text-blue-300",
)}
title={
entry.source === "access_group"
? "From access group"
: "Direct assignment"
}
>
<Text>{truncated}</Text>
{truncated}
</Badge>
);
};
@ -63,36 +81,46 @@ const ModelsCell = ({ team }: ModelsCellProps) => {
>
<div className="flex flex-col">
{modelEntries.length === 0 ? (
<Badge size={"xs"} className="mb-1" color="red">
<Text>All Proxy Models</Text>
<Badge className="bg-red-100 text-red-700 dark:bg-red-950 dark:text-red-300 text-xs mb-1">
All Proxy Models
</Badge>
) : (
<div className="flex flex-col">
<div className="flex items-start">
{modelEntries.length > 3 && (
<div>
<Icon
icon={expandedAccordion ? ChevronDownIcon : ChevronRightIcon}
className="cursor-pointer"
size="xs"
onClick={() => {
setExpandedAccordion((prev) => !prev);
}}
/>
</div>
<button
type="button"
onClick={() => setExpandedAccordion((prev) => !prev)}
className="cursor-pointer text-muted-foreground hover:text-foreground"
aria-label={expandedAccordion ? "Collapse" : "Expand"}
>
{expandedAccordion ? (
<ChevronDown className="h-3 w-3" />
) : (
<ChevronRight className="h-3 w-3" />
)}
</button>
)}
<div className="flex flex-wrap gap-1">
{modelEntries.slice(0, 3).map((entry, index) => renderBadge(entry, index))}
{modelEntries
.slice(0, 3)
.map((entry, index) => renderBadge(entry, index))}
{modelEntries.length > 3 && !expandedAccordion && (
<Badge size={"xs"} color="gray" className="cursor-pointer">
<Text>
+{modelEntries.length - 3} {modelEntries.length - 3 === 1 ? "more model" : "more models"}
</Text>
<Badge
variant="secondary"
className="text-xs cursor-pointer"
>
+{modelEntries.length - 3}{" "}
{modelEntries.length - 3 === 1
? "more model"
: "more models"}
</Badge>
)}
{expandedAccordion && (
<div className="flex flex-wrap gap-1">
{modelEntries.slice(3).map((entry, index) => renderBadge(entry, index + 3))}
{modelEntries
.slice(3)
.map((entry, index) => renderBadge(entry, index + 3))}
</div>
)}
</div>

View file

@ -1,14 +1,19 @@
import React from "react";
import { Tooltip } from "antd";
import {
ClockCircleOutlined,
NumberOutlined,
ImportOutlined,
ExportOutlined,
BulbOutlined,
ToolOutlined,
DollarOutlined,
} from "@ant-design/icons";
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import {
Clock,
DollarSign,
Hash,
LogIn,
LogOut,
Lightbulb,
Wrench,
} from "lucide-react";
export interface TokenUsage {
completionTokens?: number;
@ -25,81 +30,104 @@ interface ResponseMetricsProps {
toolName?: string;
}
const ResponseMetrics: React.FC<ResponseMetricsProps> = ({ timeToFirstToken, totalLatency, usage, toolName }) => {
const TooltipIconStat: React.FC<{
tooltip: string;
icon: React.ReactNode;
children: React.ReactNode;
}> = ({ tooltip, icon, children }) => (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div className="flex items-center">
{icon}
<span>{children}</span>
</div>
</TooltipTrigger>
<TooltipContent>{tooltip}</TooltipContent>
</Tooltip>
</TooltipProvider>
);
const ResponseMetrics: React.FC<ResponseMetricsProps> = ({
timeToFirstToken,
totalLatency,
usage,
toolName,
}) => {
if (!timeToFirstToken && !totalLatency && !usage) return null;
return (
<div className="response-metrics mt-2 pt-2 border-t border-gray-100 text-xs text-gray-500 flex flex-wrap gap-3">
<div className="response-metrics mt-2 pt-2 border-t border-border text-xs text-muted-foreground flex flex-wrap gap-3">
{timeToFirstToken !== undefined && (
<Tooltip title="Time to first token">
<div className="flex items-center">
<ClockCircleOutlined className="mr-1" />
<span>TTFT: {(timeToFirstToken / 1000).toFixed(2)}s</span>
</div>
</Tooltip>
<TooltipIconStat
tooltip="Time to first token"
icon={<Clock className="h-3 w-3 mr-1" />}
>
TTFT: {(timeToFirstToken / 1000).toFixed(2)}s
</TooltipIconStat>
)}
{totalLatency !== undefined && (
<Tooltip title="Total latency">
<div className="flex items-center">
<ClockCircleOutlined className="mr-1" />
<span>Total Latency: {(totalLatency / 1000).toFixed(2)}s</span>
</div>
</Tooltip>
<TooltipIconStat
tooltip="Total latency"
icon={<Clock className="h-3 w-3 mr-1" />}
>
Total Latency: {(totalLatency / 1000).toFixed(2)}s
</TooltipIconStat>
)}
{usage?.promptTokens !== undefined && (
<Tooltip title="Prompt tokens">
<div className="flex items-center">
<ImportOutlined className="mr-1" />
<span>In: {usage.promptTokens}</span>
</div>
</Tooltip>
<TooltipIconStat
tooltip="Prompt tokens"
icon={<LogIn className="h-3 w-3 mr-1" />}
>
In: {usage.promptTokens}
</TooltipIconStat>
)}
{usage?.completionTokens !== undefined && (
<Tooltip title="Completion tokens">
<div className="flex items-center">
<ExportOutlined className="mr-1" />
<span>Out: {usage.completionTokens}</span>
</div>
</Tooltip>
<TooltipIconStat
tooltip="Completion tokens"
icon={<LogOut className="h-3 w-3 mr-1" />}
>
Out: {usage.completionTokens}
</TooltipIconStat>
)}
{usage?.reasoningTokens !== undefined && (
<Tooltip title="Reasoning tokens">
<div className="flex items-center">
<BulbOutlined className="mr-1" />
<span>Reasoning: {usage.reasoningTokens}</span>
</div>
</Tooltip>
<TooltipIconStat
tooltip="Reasoning tokens"
icon={<Lightbulb className="h-3 w-3 mr-1" />}
>
Reasoning: {usage.reasoningTokens}
</TooltipIconStat>
)}
{usage?.totalTokens !== undefined && (
<Tooltip title="Total tokens">
<div className="flex items-center">
<NumberOutlined className="mr-1" />
<span>Total: {usage.totalTokens}</span>
</div>
</Tooltip>
<TooltipIconStat
tooltip="Total tokens"
icon={<Hash className="h-3 w-3 mr-1" />}
>
Total: {usage.totalTokens}
</TooltipIconStat>
)}
{usage?.cost !== undefined && (
<Tooltip title="Cost">
<div className="flex items-center">
<DollarOutlined className="mr-1" />
<span>${usage.cost.toFixed(6)}</span>
</div>
</Tooltip>
<TooltipIconStat
tooltip="Cost"
icon={<DollarSign className="h-3 w-3 mr-1" />}
>
${usage.cost.toFixed(6)}
</TooltipIconStat>
)}
{toolName && (
<Tooltip title="Tool used">
<div className="flex items-center">
<ToolOutlined className="mr-1" />
<span>Tool: {toolName}</span>
</div>
</Tooltip>
<TooltipIconStat
tooltip="Tool used"
icon={<Wrench className="h-3 w-3 mr-1" />}
>
Tool: {toolName}
</TooltipIconStat>
)}
</div>
);