mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): claude_code_plugins/plugin_table to shadcn
- @ant-design CopyOutlined + @heroicons ChevronDown/Up/SwitchVert/ Trash + @tremor Badge/Button + antd Tooltip \u2192 shadcn Badge/Button/ Tooltip + lucide ArrowUpDown/ChevronDown/ChevronUp/Copy/Trash2. - Tremor Table family preserved (phase 1 whitelist). - BADGE_COLOR_CLASSES map translates helper-returned tremor color tokens into categorical Tailwind palette classes. File added to .eslintrc.json overrides. Gates: TS \u2713 | Build \u2713. Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
parent
70ad22d3cf
commit
f01f8dd628
2 changed files with 160 additions and 96 deletions
|
|
@ -56,6 +56,7 @@
|
|||
"src/components/Projects/ProjectsPage.tsx",
|
||||
"src/components/pass_through_settings.tsx",
|
||||
"src/components/policies/attachment_table.tsx",
|
||||
"src/components/claude_code_plugins/plugin_table.tsx",
|
||||
"src/components/permissions/VectorStorePermissions.tsx",
|
||||
"src/components/permissions/AgentPermissions.tsx",
|
||||
"src/components/permissions/MCPServerPermissions.tsx",
|
||||
|
|
|
|||
|
|
@ -1,10 +1,3 @@
|
|||
import { CopyOutlined } from "@ant-design/icons";
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
ChevronUpIcon,
|
||||
SwitchVerticalIcon,
|
||||
TrashIcon,
|
||||
} from "@heroicons/react/outline";
|
||||
import {
|
||||
ColumnDef,
|
||||
flexRender,
|
||||
|
|
@ -13,9 +6,8 @@ import {
|
|||
SortingState,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
// eslint-disable-next-line litellm-ui/no-banned-ui-imports
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
|
|
@ -23,12 +15,25 @@ import {
|
|||
TableHeaderCell,
|
||||
TableRow,
|
||||
} from "@tremor/react";
|
||||
import { Tooltip } from "antd";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
ArrowUpDown,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Copy,
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
import React, { useState } from "react";
|
||||
import NotificationsManager from "../molecules/notifications_manager";
|
||||
import {
|
||||
getCategoryBadgeColor
|
||||
} from "./helpers";
|
||||
import { getCategoryBadgeColor } from "./helpers";
|
||||
import { Plugin } from "./types";
|
||||
|
||||
interface PluginTableProps {
|
||||
|
|
@ -40,10 +45,32 @@ interface PluginTableProps {
|
|||
onPluginClick: (pluginId: string) => void;
|
||||
}
|
||||
|
||||
// Map tremor-style badge color tokens to Tailwind palette classes.
|
||||
const BADGE_COLOR_CLASSES: Record<string, string> = {
|
||||
gray: "bg-muted text-muted-foreground",
|
||||
green:
|
||||
"bg-emerald-100 text-emerald-700 dark:bg-emerald-950 dark:text-emerald-300",
|
||||
red: "bg-red-100 text-red-700 dark:bg-red-950 dark:text-red-300",
|
||||
blue: "bg-blue-100 text-blue-700 dark:bg-blue-950 dark:text-blue-300",
|
||||
indigo: "bg-indigo-100 text-indigo-700 dark:bg-indigo-950 dark:text-indigo-300",
|
||||
purple: "bg-purple-100 text-purple-700 dark:bg-purple-950 dark:text-purple-300",
|
||||
orange: "bg-orange-100 text-orange-700 dark:bg-orange-950 dark:text-orange-300",
|
||||
amber: "bg-amber-100 text-amber-700 dark:bg-amber-950 dark:text-amber-300",
|
||||
yellow: "bg-amber-100 text-amber-700 dark:bg-amber-950 dark:text-amber-300",
|
||||
cyan: "bg-cyan-100 text-cyan-700 dark:bg-cyan-950 dark:text-cyan-300",
|
||||
pink: "bg-pink-100 text-pink-700 dark:bg-pink-950 dark:text-pink-300",
|
||||
rose: "bg-rose-100 text-rose-700 dark:bg-rose-950 dark:text-rose-300",
|
||||
teal: "bg-teal-100 text-teal-700 dark:bg-teal-950 dark:text-teal-300",
|
||||
};
|
||||
|
||||
const badgeClasses = (color: string): string =>
|
||||
BADGE_COLOR_CLASSES[color] || BADGE_COLOR_CLASSES.gray;
|
||||
|
||||
const PluginTable: React.FC<PluginTableProps> = ({
|
||||
pluginsList,
|
||||
isLoading,
|
||||
onDeleteClick,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
accessToken,
|
||||
isAdmin,
|
||||
onPluginClick,
|
||||
|
|
@ -72,25 +99,38 @@ const PluginTable: React.FC<PluginTableProps> = ({
|
|||
const name = plugin.name || "";
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<Tooltip title={name}>
|
||||
<Button
|
||||
size="xs"
|
||||
variant="light"
|
||||
className="font-mono text-blue-500 bg-blue-50 hover:bg-blue-100 text-xs font-normal px-2 py-0.5 text-left overflow-hidden truncate min-w-[150px] justify-start"
|
||||
onClick={() => onPluginClick(plugin.id)}
|
||||
>
|
||||
{name}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title="Copy Plugin ID">
|
||||
<CopyOutlined
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
copyToClipboard(plugin.id);
|
||||
}}
|
||||
className="cursor-pointer text-gray-500 hover:text-blue-500 text-xs"
|
||||
/>
|
||||
</Tooltip>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onPluginClick(plugin.id)}
|
||||
className="font-mono text-blue-500 bg-blue-50 hover:bg-blue-100 dark:bg-blue-950/30 dark:hover:bg-blue-950/60 text-xs font-normal px-2 py-0.5 text-left overflow-hidden truncate min-w-[150px] rounded"
|
||||
>
|
||||
{name}
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{name}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
copyToClipboard(plugin.id);
|
||||
}}
|
||||
className="cursor-pointer text-muted-foreground hover:text-primary"
|
||||
aria-label="Copy Plugin ID"
|
||||
>
|
||||
<Copy className="h-3 w-3" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Copy Plugin ID</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
|
@ -98,10 +138,11 @@ const PluginTable: React.FC<PluginTableProps> = ({
|
|||
{
|
||||
header: "Version",
|
||||
accessorKey: "version",
|
||||
cell: ({ row }) => {
|
||||
const version = row.original.version || "N/A";
|
||||
return <span className="text-xs text-gray-600">{version}</span>;
|
||||
},
|
||||
cell: ({ row }) => (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{row.original.version || "N/A"}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: "Description",
|
||||
|
|
@ -109,11 +150,16 @@ const PluginTable: React.FC<PluginTableProps> = ({
|
|||
cell: ({ row }) => {
|
||||
const description = row.original.description || "No description";
|
||||
return (
|
||||
<Tooltip title={description}>
|
||||
<span className="text-xs text-gray-600 block max-w-[300px] truncate">
|
||||
{description}
|
||||
</span>
|
||||
</Tooltip>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="text-xs text-muted-foreground block max-w-[300px] truncate">
|
||||
{description}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{description}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
|
@ -124,14 +170,14 @@ const PluginTable: React.FC<PluginTableProps> = ({
|
|||
const category = row.original.category;
|
||||
if (!category) {
|
||||
return (
|
||||
<Badge color="gray" className="text-xs font-normal" size="xs">
|
||||
<Badge className={cn("text-xs font-normal", badgeClasses("gray"))}>
|
||||
Uncategorized
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
const badgeColor = getCategoryBadgeColor(category);
|
||||
return (
|
||||
<Badge color={badgeColor} className="text-xs font-normal" size="xs">
|
||||
<Badge className={cn("text-xs font-normal", badgeClasses(badgeColor))}>
|
||||
{category}
|
||||
</Badge>
|
||||
);
|
||||
|
|
@ -144,9 +190,10 @@ const PluginTable: React.FC<PluginTableProps> = ({
|
|||
const plugin = row.original;
|
||||
return (
|
||||
<Badge
|
||||
color={plugin.enabled ? "green" : "gray"}
|
||||
className="text-xs font-normal"
|
||||
size="xs"
|
||||
className={cn(
|
||||
"text-xs font-normal",
|
||||
badgeClasses(plugin.enabled ? "green" : "gray"),
|
||||
)}
|
||||
>
|
||||
{plugin.enabled ? "Yes" : "No"}
|
||||
</Badge>
|
||||
|
|
@ -159,50 +206,58 @@ const PluginTable: React.FC<PluginTableProps> = ({
|
|||
cell: ({ row }) => {
|
||||
const plugin = row.original;
|
||||
return (
|
||||
<Tooltip title={plugin.created_at}>
|
||||
<span className="text-xs">{formatDate(plugin.created_at)}</span>
|
||||
</Tooltip>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="text-xs">{formatDate(plugin.created_at)}</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{plugin.created_at}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
},
|
||||
},
|
||||
...(isAdmin
|
||||
? [
|
||||
{
|
||||
header: "Actions",
|
||||
id: "actions",
|
||||
enableSorting: false,
|
||||
cell: ({ row }: any) => {
|
||||
const plugin = row.original;
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1">
|
||||
<Tooltip title="Delete skill">
|
||||
<Button
|
||||
size="xs"
|
||||
variant="light"
|
||||
color="red"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDeleteClick(plugin.name, plugin.name);
|
||||
}}
|
||||
icon={TrashIcon}
|
||||
className="text-red-500 hover:text-red-700 hover:bg-red-50"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
{
|
||||
header: "Actions",
|
||||
id: "actions",
|
||||
enableSorting: false,
|
||||
cell: ({ row }: { row: { original: Plugin } }) => {
|
||||
const plugin = row.original;
|
||||
return (
|
||||
<div className="flex items-center gap-1">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7 text-destructive hover:text-destructive hover:bg-destructive/10"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDeleteClick(plugin.name, plugin.name);
|
||||
}}
|
||||
aria-label="Delete skill"
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Delete skill</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
]
|
||||
: []),
|
||||
];
|
||||
|
||||
const table = useReactTable({
|
||||
data: pluginsList,
|
||||
columns,
|
||||
state: {
|
||||
sorting,
|
||||
},
|
||||
state: { sorting },
|
||||
onSortingChange: setSorting,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
|
|
@ -219,10 +274,11 @@ const PluginTable: React.FC<PluginTableProps> = ({
|
|||
{headerGroup.headers.map((header) => (
|
||||
<TableHeaderCell
|
||||
key={header.id}
|
||||
className={`py-1 h-8 ${header.id === "actions"
|
||||
? "sticky right-0 bg-white shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)]"
|
||||
className={`py-1 h-8 ${
|
||||
header.id === "actions"
|
||||
? "sticky right-0 bg-background shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)]"
|
||||
: ""
|
||||
}`}
|
||||
}`}
|
||||
onClick={
|
||||
header.column.getCanSort()
|
||||
? header.column.getToggleSortingHandler()
|
||||
|
|
@ -234,23 +290,23 @@ const PluginTable: React.FC<PluginTableProps> = ({
|
|||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
header.column.columnDef.header,
|
||||
header.getContext(),
|
||||
)}
|
||||
</div>
|
||||
{header.column.getCanSort() && (
|
||||
<div className="w-4">
|
||||
{header.column.getIsSorted() ? (
|
||||
{
|
||||
asc: (
|
||||
<ChevronUpIcon className="h-4 w-4 text-blue-500" />
|
||||
<ChevronUp className="h-4 w-4 text-primary" />
|
||||
),
|
||||
desc: (
|
||||
<ChevronDownIcon className="h-4 w-4 text-blue-500" />
|
||||
<ChevronDown className="h-4 w-4 text-primary" />
|
||||
),
|
||||
}[header.column.getIsSorted() as string]
|
||||
) : (
|
||||
<SwitchVerticalIcon className="h-4 w-4 text-gray-400" />
|
||||
<ArrowUpDown className="h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -263,8 +319,11 @@ const PluginTable: React.FC<PluginTableProps> = ({
|
|||
<TableBody>
|
||||
{isLoading ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-8 text-center">
|
||||
<div className="text-center text-gray-500">
|
||||
<TableCell
|
||||
colSpan={columns.length}
|
||||
className="h-8 text-center"
|
||||
>
|
||||
<div className="text-center text-muted-foreground">
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
</TableCell>
|
||||
|
|
@ -273,20 +332,21 @@ const PluginTable: React.FC<PluginTableProps> = ({
|
|||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
className="h-8 cursor-pointer hover:bg-gray-50"
|
||||
className="h-8 cursor-pointer hover:bg-muted"
|
||||
onClick={() => onPluginClick(row.original.id)}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell
|
||||
key={cell.id}
|
||||
className={`py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap ${cell.column.id === "actions"
|
||||
? "sticky right-0 bg-white shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)]"
|
||||
className={`py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap ${
|
||||
cell.column.id === "actions"
|
||||
? "sticky right-0 bg-background shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)]"
|
||||
: ""
|
||||
}`}
|
||||
}`}
|
||||
>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext()
|
||||
cell.getContext(),
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
|
|
@ -294,8 +354,11 @@ const PluginTable: React.FC<PluginTableProps> = ({
|
|||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-8 text-center">
|
||||
<div className="text-center text-gray-500">
|
||||
<TableCell
|
||||
colSpan={columns.length}
|
||||
className="h-8 text-center"
|
||||
>
|
||||
<div className="text-center text-muted-foreground">
|
||||
<p>No skills found. Add one to get started.</p>
|
||||
</div>
|
||||
</TableCell>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue