feat(ui): AIHub/AgentHubTableColumns to shadcn

- @tremor Button/Badge/Text + antd Tooltip/Tag + @ant-design Copy/
  InfoCircle \u2192 shadcn Button/Badge/Tooltip + lucide Copy/Info.
- Categorical badge palette: blue (version), emerald (public/
  capabilities), purple (skills), gray (fallback). Added to overrides.

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 13:39:03 +00:00
parent 10868011ac
commit e959382ec2
No known key found for this signature in database
2 changed files with 215 additions and 190 deletions

View file

@ -69,6 +69,7 @@
"src/components/CostTrackingSettings/how_it_works.tsx",
"src/components/Settings/AdminSettings/UISettings/PageVisibilitySettings.tsx",
"src/components/AIHub/marketplace_table_columns.tsx",
"src/components/AIHub/AgentHubTableColumns.tsx",
"src/components/UsagePage/components/EntityUsage/SpendByProvider.tsx",
"src/components/view_logs/ToolsSection/FormattedToolView.tsx",
"src/components/Settings/LoggingAndAlerts/LoggingCallbacks/LoggingCallbacksTable.tsx",

View file

@ -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";
export interface AgentHubData {
agent_id?: string;
@ -12,6 +19,7 @@ export interface AgentHubData {
version: string;
capabilities?: {
streaming?: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
};
defaultInputModes?: string[];
@ -25,207 +33,223 @@ export interface AgentHubData {
}>;
supportsAuthenticatedExtendedCard?: boolean;
is_public?: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
}
const badgeClass = {
blue: "bg-blue-100 text-blue-700 dark:bg-blue-950 dark:text-blue-300",
emerald:
"bg-emerald-100 text-emerald-700 dark:bg-emerald-950 dark:text-emerald-300",
purple:
"bg-purple-100 text-purple-700 dark:bg-purple-950 dark:text-purple-300",
gray: "bg-muted text-muted-foreground",
};
export const getAgentHubTableColumns = (
showModal: (agent: AgentHubData) => void,
copyToClipboard: (text: string) => void,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
publicPage: boolean = false,
): ColumnDef<AgentHubData>[] => {
const allColumns: ColumnDef<AgentHubData>[] = [
{
header: "Agent Name",
accessorKey: "name",
enableSorting: true,
sortingFn: "alphanumeric",
cell: ({ row }) => {
const agent = row.original;
return (
<div className="space-y-1">
<div className="flex items-center space-x-2">
<Text className="font-medium text-sm">{agent.name}</Text>
<Tooltip title="Copy agent name">
<CopyOutlined
onClick={() => copyToClipboard(agent.name)}
className="cursor-pointer text-gray-500 hover:text-blue-500 text-xs"
/>
): ColumnDef<AgentHubData>[] => [
{
header: "Agent Name",
accessorKey: "name",
enableSorting: true,
sortingFn: "alphanumeric",
cell: ({ row }) => {
const agent = row.original;
return (
<div className="space-y-1">
<div className="flex items-center space-x-2">
<span className="font-medium text-sm">{agent.name}</span>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
onClick={() => copyToClipboard(agent.name)}
className="cursor-pointer text-muted-foreground hover:text-primary"
aria-label="Copy agent name"
>
<Copy className="h-3 w-3" />
</button>
</TooltipTrigger>
<TooltipContent>Copy agent name</TooltipContent>
</Tooltip>
</div>
{/* Show description on mobile */}
<div className="md:hidden">
<Text className="text-xs text-gray-600">{agent.description}</Text>
</div>
</TooltipProvider>
</div>
);
},
},
{
header: "Description",
accessorKey: "description",
enableSorting: true,
sortingFn: "alphanumeric",
cell: ({ row }) => {
const agent = row.original;
return <Text className="text-xs line-clamp-2">{agent.description || "-"}</Text>;
},
meta: {
className: "hidden md:table-cell",
},
},
{
header: "Version",
accessorKey: "version",
enableSorting: true,
sortingFn: "alphanumeric",
cell: ({ row }) => {
const agent = row.original;
return (
<Badge color="blue" size="sm">
v{agent.version}
</Badge>
);
},
meta: {
className: "hidden lg:table-cell",
},
},
{
header: "Protocol",
accessorKey: "protocolVersion",
enableSorting: true,
sortingFn: "alphanumeric",
cell: ({ row }) => {
const agent = row.original;
return <Text className="text-xs">{agent.protocolVersion || "-"}</Text>;
},
meta: {
className: "hidden lg:table-cell",
},
},
{
header: "Skills",
accessorKey: "skills",
enableSorting: false,
cell: ({ row }) => {
const agent = row.original;
const skills = agent.skills || [];
return (
<div className="space-y-1">
<Text className="text-xs font-medium">
{skills.length} skill{skills.length !== 1 ? "s" : ""}
</Text>
{skills.length > 0 && (
<div className="flex flex-wrap gap-1">
{skills.slice(0, 2).map((skill) => (
<Tag key={skill.id} color="purple" className="text-xs">
{skill.name}
</Tag>
))}
{skills.length > 2 && <Text className="text-xs text-gray-500">+{skills.length - 2}</Text>}
</div>
)}
<div className="md:hidden">
<p className="text-xs text-muted-foreground">
{agent.description}
</p>
</div>
);
},
</div>
);
},
{
header: "Capabilities",
accessorKey: "capabilities",
enableSorting: false,
cell: ({ row }) => {
const agent = row.original;
const capabilities = agent.capabilities || {};
const capabilityList = Object.entries(capabilities)
.filter(([_, value]) => value === true)
.map(([key]) => key);
return (
<div className="flex flex-wrap gap-1">
{capabilityList.length === 0 ? (
<Text className="text-gray-500 text-xs">-</Text>
) : (
capabilityList.map((capability) => (
<Badge key={capability} color="green" size="xs">
{capability}
},
{
header: "Description",
accessorKey: "description",
enableSorting: true,
sortingFn: "alphanumeric",
cell: ({ row }) => (
<p className="text-xs line-clamp-2">{row.original.description || "-"}</p>
),
meta: {
className: "hidden md:table-cell",
},
},
{
header: "Version",
accessorKey: "version",
enableSorting: true,
sortingFn: "alphanumeric",
cell: ({ row }) => (
<Badge className={cn("text-xs", badgeClass.blue)}>
v{row.original.version}
</Badge>
),
meta: {
className: "hidden lg:table-cell",
},
},
{
header: "Protocol",
accessorKey: "protocolVersion",
enableSorting: true,
sortingFn: "alphanumeric",
cell: ({ row }) => (
<span className="text-xs">{row.original.protocolVersion || "-"}</span>
),
meta: {
className: "hidden lg:table-cell",
},
},
{
header: "Skills",
accessorKey: "skills",
enableSorting: false,
cell: ({ row }) => {
const agent = row.original;
const skills = agent.skills || [];
return (
<div className="space-y-1">
<span className="text-xs font-medium">
{skills.length} skill{skills.length !== 1 ? "s" : ""}
</span>
{skills.length > 0 && (
<div className="flex flex-wrap gap-1">
{skills.slice(0, 2).map((skill) => (
<Badge
key={skill.id}
className={cn("text-xs", badgeClass.purple)}
>
{skill.name}
</Badge>
))
)}
))}
{skills.length > 2 && (
<span className="text-xs text-muted-foreground">
+{skills.length - 2}
</span>
)}
</div>
)}
</div>
);
},
},
{
header: "Capabilities",
accessorKey: "capabilities",
enableSorting: false,
cell: ({ row }) => {
const agent = row.original;
const capabilities = agent.capabilities || {};
const capabilityList = Object.entries(capabilities)
.filter(([, value]) => value === true)
.map(([key]) => key);
return (
<div className="flex flex-wrap gap-1">
{capabilityList.length === 0 ? (
<span className="text-muted-foreground text-xs">-</span>
) : (
capabilityList.map((capability) => (
<Badge
key={capability}
className={cn("text-xs", badgeClass.emerald)}
>
{capability}
</Badge>
))
)}
</div>
);
},
},
{
header: "I/O Modes",
accessorKey: "defaultInputModes",
enableSorting: false,
cell: ({ row }) => {
const agent = row.original;
const inputModes = agent.defaultInputModes || [];
const outputModes = agent.defaultOutputModes || [];
return (
<div className="space-y-1">
<div className="text-xs">
<span className="font-medium">In:</span>{" "}
{inputModes.join(", ") || "-"}
</div>
);
},
},
{
header: "I/O Modes",
accessorKey: "defaultInputModes",
enableSorting: false,
cell: ({ row }) => {
const agent = row.original;
const inputModes = agent.defaultInputModes || [];
const outputModes = agent.defaultOutputModes || [];
return (
<div className="space-y-1">
<Text className="text-xs">
<span className="font-medium">In:</span> {inputModes.join(", ") || "-"}
</Text>
<Text className="text-xs">
<span className="font-medium">Out:</span> {outputModes.join(", ") || "-"}
</Text>
<div className="text-xs">
<span className="font-medium">Out:</span>{" "}
{outputModes.join(", ") || "-"}
</div>
);
},
meta: {
className: "hidden xl:table-cell",
},
</div>
);
},
{
header: "Public",
accessorKey: "is_public",
enableSorting: true,
sortingFn: (rowA, rowB) => {
const publicA = rowA.original.is_public === true ? 1 : 0;
const publicB = rowB.original.is_public === true ? 1 : 0;
return publicA - publicB;
},
cell: ({ row }) => {
const agent = row.original;
return agent.is_public === true ? (
<Badge color="green" size="xs">
Yes
</Badge>
) : (
<Badge color="gray" size="xs">
No
</Badge>
);
},
meta: {
className: "hidden md:table-cell",
},
meta: {
className: "hidden xl:table-cell",
},
{
header: "Details",
id: "details",
enableSorting: false,
cell: ({ row }) => {
const agent = row.original;
return (
<Button size="xs" variant="secondary" onClick={() => showModal(agent)} icon={InfoCircleOutlined}>
<span className="hidden lg:inline">Details</span>
<span className="lg:hidden">Info</span>
</Button>
);
},
},
{
header: "Public",
accessorKey: "is_public",
enableSorting: true,
sortingFn: (rowA, rowB) => {
const publicA = rowA.original.is_public === true ? 1 : 0;
const publicB = rowB.original.is_public === true ? 1 : 0;
return publicA - publicB;
},
];
return allColumns;
};
cell: ({ row }) =>
row.original.is_public === true ? (
<Badge className={cn("text-xs", badgeClass.emerald)}>Yes</Badge>
) : (
<Badge className={cn("text-xs", badgeClass.gray)}>No</Badge>
),
meta: {
className: "hidden md:table-cell",
},
},
{
header: "Details",
id: "details",
enableSorting: false,
cell: ({ row }) => {
const agent = row.original;
return (
<Button
size="sm"
variant="secondary"
onClick={() => showModal(agent)}
>
<Info className="h-3.5 w-3.5" />
<span className="hidden lg:inline">Details</span>
<span className="lg:hidden">Info</span>
</Button>
);
},
},
];