diff --git a/ui/litellm-dashboard/.eslintrc.json b/ui/litellm-dashboard/.eslintrc.json
index 6049b3b352a..78891527ad7 100644
--- a/ui/litellm-dashboard/.eslintrc.json
+++ b/ui/litellm-dashboard/.eslintrc.json
@@ -36,7 +36,8 @@
"src/components/model_group_alias_settings.tsx",
"src/components/cache_dashboard.tsx",
"src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterTestPanel.tsx",
- "src/components/mcp_hub_table_columns.tsx"
+ "src/components/mcp_hub_table_columns.tsx",
+ "src/components/model_hub_table_columns.tsx"
],
"rules": {
"litellm-ui/no-banned-ui-imports": "off",
diff --git a/ui/litellm-dashboard/src/components/model_hub_table_columns.tsx b/ui/litellm-dashboard/src/components/model_hub_table_columns.tsx
index 64de1dd98e8..e768ba72ea6 100644
--- a/ui/litellm-dashboard/src/components/model_hub_table_columns.tsx
+++ b/ui/litellm-dashboard/src/components/model_hub_table_columns.tsx
@@ -1,7 +1,14 @@
import { ColumnDef } from "@tanstack/react-table";
-import { Button, Badge, Text } from "@tremor/react";
-import { Tooltip, Tag } from "antd";
-import { CopyOutlined, InfoCircleOutlined } from "@ant-design/icons";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
+import { Copy, Info } from "lucide-react";
+import { cn } from "@/lib/utils";
interface ModelHubData {
model_group: string;
@@ -18,36 +25,70 @@ interface ModelHubData {
supports_function_calling: boolean;
supported_openai_params?: string[];
is_public_model_group: boolean;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
}
-const formatCapabilityName = (key: string) => {
- return key
+const formatCapabilityName = (key: string) =>
+ key
.replace(/^supports_/, "")
.split("_")
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(" ");
-};
-const getModelCapabilities = (model: ModelHubData) => {
- return Object.entries(model)
+const getModelCapabilities = (model: ModelHubData) =>
+ Object.entries(model)
.filter(([key, value]) => key.startsWith("supports_") && value === true)
.map(([key]) => key);
-};
-const formatCost = (cost: number) => {
- return `$${(cost * 1_000_000).toFixed(2)}`;
-};
+const formatCost = (cost: number) => `$${(cost * 1_000_000).toFixed(2)}`;
const formatTokens = (tokens: number) => {
- if (tokens >= 1_000_000) {
- return `${(tokens / 1_000_000).toFixed(1)}M`;
- } else if (tokens >= 1_000) {
- return `${(tokens / 1_000).toFixed(1)}K`;
- }
+ if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(1)}M`;
+ if (tokens >= 1_000) return `${(tokens / 1_000).toFixed(1)}K`;
return tokens.toString();
};
+// Categorical capability-color palette. Each index cycles through a set of
+// tags; added to the raw-colors eslintrc override since these are
+// categorical by design.
+const capabilityColors = [
+ "bg-emerald-100 text-emerald-800 dark:bg-emerald-950/40 dark:text-emerald-300",
+ "bg-blue-100 text-blue-800 dark:bg-blue-950/40 dark:text-blue-300",
+ "bg-purple-100 text-purple-800 dark:bg-purple-950/40 dark:text-purple-300",
+ "bg-orange-100 text-orange-800 dark:bg-orange-950/40 dark:text-orange-300",
+ "bg-red-100 text-red-800 dark:bg-red-950/40 dark:text-red-300",
+ "bg-amber-100 text-amber-800 dark:bg-amber-950/40 dark:text-amber-300",
+];
+
+function CopyIconButton({
+ value,
+ onCopy,
+ title,
+}: {
+ value: string;
+ onCopy: (text: string) => void;
+ title: string;
+}) {
+ return (
+
+
+
+
+
+ {title}
+
+
+ );
+}
+
export const modelHubColumns = (
showModal: (model: ModelHubData) => void,
copyToClipboard: (text: string) => void,
@@ -61,21 +102,20 @@ export const modelHubColumns = (
sortingFn: "alphanumeric",
cell: ({ row }) => {
const model = row.original;
-
return (
- {model.model_group}
-
- copyToClipboard(model.model_group)}
- className="cursor-pointer text-gray-500 hover:text-blue-500 text-xs"
- />
-
+ {model.model_group}
+
- {/* Show provider on mobile when provider column is hidden */}
- {model.providers.join(", ")}
+
+ {model.providers.join(", ")}
+
);
@@ -85,28 +125,32 @@ export const modelHubColumns = (
header: "Provider",
accessorKey: "providers",
enableSorting: true,
- sortingFn: (rowA, rowB) => {
- const providersA = rowA.original.providers.join(", ");
- const providersB = rowB.original.providers.join(", ");
- return providersA.localeCompare(providersB);
- },
+ sortingFn: (rowA, rowB) =>
+ rowA.original.providers.join(", ").localeCompare(
+ rowB.original.providers.join(", "),
+ ),
cell: ({ row }) => {
const model = row.original;
-
return (
{model.providers.slice(0, 2).map((provider) => (
-
+
{provider}
-
+
))}
- {model.providers.length > 2 && +{model.providers.length - 2}}
+ {model.providers.length > 2 && (
+
+ +{model.providers.length - 2}
+
+ )}
);
},
- meta: {
- className: "hidden md:table-cell",
- },
+ meta: { className: "hidden md:table-cell" },
},
{
header: "Mode",
@@ -115,62 +159,77 @@ export const modelHubColumns = (
sortingFn: "alphanumeric",
cell: ({ row }) => {
const model = row.original;
-
return model.mode ? (
-
+
{model.mode}
) : (
- -
+ -
);
},
- meta: {
- className: "hidden lg:table-cell",
- },
+ meta: { className: "hidden lg:table-cell" },
},
{
header: "Tokens",
accessorKey: "max_input_tokens",
enableSorting: true,
sortingFn: (rowA, rowB) => {
- const tokensA = (rowA.original.max_input_tokens || 0) + (rowA.original.max_output_tokens || 0);
- const tokensB = (rowB.original.max_input_tokens || 0) + (rowB.original.max_output_tokens || 0);
+ const tokensA =
+ (rowA.original.max_input_tokens || 0) +
+ (rowA.original.max_output_tokens || 0);
+ const tokensB =
+ (rowB.original.max_input_tokens || 0) +
+ (rowB.original.max_output_tokens || 0);
return tokensA - tokensB;
},
cell: ({ row }) => {
const model = row.original;
-
return (
-
- {model.max_input_tokens ? formatTokens(model.max_input_tokens) : "-"} /{" "}
- {model.max_output_tokens ? formatTokens(model.max_output_tokens) : "-"}
-
+
+ {model.max_input_tokens
+ ? formatTokens(model.max_input_tokens)
+ : "-"}{" "}
+ /{" "}
+ {model.max_output_tokens
+ ? formatTokens(model.max_output_tokens)
+ : "-"}
+
);
},
- meta: {
- className: "hidden lg:table-cell",
- },
+ meta: { className: "hidden lg:table-cell" },
},
{
header: "Cost/1M",
accessorKey: "input_cost_per_token",
enableSorting: true,
sortingFn: (rowA, rowB) => {
- const costA = (rowA.original.input_cost_per_token || 0) + (rowA.original.output_cost_per_token || 0);
- const costB = (rowB.original.input_cost_per_token || 0) + (rowB.original.output_cost_per_token || 0);
+ const costA =
+ (rowA.original.input_cost_per_token || 0) +
+ (rowA.original.output_cost_per_token || 0);
+ const costB =
+ (rowB.original.input_cost_per_token || 0) +
+ (rowB.original.output_cost_per_token || 0);
return costA - costB;
},
cell: ({ row }) => {
const model = row.original;
-
return (
- {model.input_cost_per_token ? formatCost(model.input_cost_per_token) : "-"}
-
- {model.output_cost_per_token ? formatCost(model.output_cost_per_token) : "-"}
-
+
+ {model.input_cost_per_token
+ ? formatCost(model.input_cost_per_token)
+ : "-"}
+
+
+ {model.output_cost_per_token
+ ? formatCost(model.output_cost_per_token)
+ : "-"}
+
);
},
@@ -182,15 +241,20 @@ export const modelHubColumns = (
cell: ({ row }) => {
const model = row.original;
const capabilities = getModelCapabilities(model);
- const colors = ["green", "blue", "purple", "orange", "red", "yellow"];
-
return (
{capabilities.length === 0 ? (
- -
+ -
) : (
capabilities.map((capability, index) => (
-
+
{formatCapabilityName(capability)}
))
@@ -204,50 +268,52 @@ export const modelHubColumns = (
accessorKey: "is_public_model_group",
enableSorting: true,
sortingFn: (rowA, rowB) => {
- const publicA = rowA.original.is_public_model_group === true ? 1 : 0;
- const publicB = rowB.original.is_public_model_group === true ? 1 : 0;
+ const publicA =
+ rowA.original.is_public_model_group === true ? 1 : 0;
+ const publicB =
+ rowB.original.is_public_model_group === true ? 1 : 0;
return publicA - publicB;
},
cell: ({ row }) => {
const model = row.original;
-
return model.is_public_model_group === true ? (
-
+
Yes
) : (
-
+
No
);
},
- meta: {
- className: "hidden md:table-cell",
- },
+ meta: { className: "hidden md:table-cell" },
},
{
header: "Details",
id: "details",
enableSorting: false,
- cell: ({ row }) => {
- const model = row.original;
-
- return (
-
- );
- },
+ cell: ({ row }) => (
+
+ ),
},
];
- // Filter out columns based on publicPage setting
if (publicPage) {
return allColumns.filter((column) => {
- // Remove the public column
- if ("accessorKey" in column && column.accessorKey === "is_public_model_group") return false;
-
+ if (
+ "accessorKey" in column &&
+ column.accessorKey === "is_public_model_group"
+ ) {
+ return false;
+ }
return true;
});
}