mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
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:
parent
ca2c45e5e7
commit
6fa4d35d7d
3 changed files with 144 additions and 87 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue