mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
Add Agent table
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { AgentTable } from "./AgentTable";
|
||||
import { AgentTableRowProps } from "./AgentTableRow";
|
||||
import { userEvent, within, expect } from "@storybook/test";
|
||||
|
||||
const meta: Meta<typeof AgentTable> = {
|
||||
title: "AGPT UI/Agent Table",
|
||||
component: AgentTable,
|
||||
tags: ["autodocs"],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof AgentTable>;
|
||||
|
||||
const sampleAgents: AgentTableRowProps[] = [
|
||||
{
|
||||
agentName: "Super Coder",
|
||||
description: "An AI agent that writes clean, efficient code",
|
||||
imageSrc:
|
||||
"https://ddz4ak4pa3d19.cloudfront.net/cache/53/b2/53b2bc7d7900f0e1e60bf64ebf38032d.jpg",
|
||||
dateSubmitted: "2023-05-15",
|
||||
status: "Active",
|
||||
runs: 1500,
|
||||
rating: 4.8,
|
||||
onEdit: () => console.log("Edit Super Coder"),
|
||||
},
|
||||
{
|
||||
agentName: "Data Analyzer",
|
||||
description: "Processes and analyzes large datasets with ease",
|
||||
imageSrc:
|
||||
"https://ddz4ak4pa3d19.cloudfront.net/cache/40/f7/40f7bc97c952f8df0f9c88d29defe8d4.jpg",
|
||||
dateSubmitted: "2023-05-10",
|
||||
status: "Active",
|
||||
runs: 1200,
|
||||
rating: 4.5,
|
||||
onEdit: () => console.log("Edit Data Analyzer"),
|
||||
},
|
||||
{
|
||||
agentName: "UI Designer",
|
||||
description: "Creates beautiful and intuitive user interfaces",
|
||||
imageSrc:
|
||||
"https://ddz4ak4pa3d19.cloudfront.net/cache/14/9e/149ebb9014aa8c0097e72ed89845af0e.jpg",
|
||||
dateSubmitted: "2023-05-05",
|
||||
status: "Inactive",
|
||||
runs: 800,
|
||||
rating: 4.2,
|
||||
onEdit: () => console.log("Edit UI Designer"),
|
||||
},
|
||||
];
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
agents: sampleAgents,
|
||||
},
|
||||
};
|
||||
|
||||
export const EmptyTable: Story = {
|
||||
args: {
|
||||
agents: [],
|
||||
},
|
||||
};
|
||||
|
||||
// Tests
|
||||
export const InteractionTest: Story = {
|
||||
...Default,
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const editButtons = await canvas.findAllByText("Edit");
|
||||
await userEvent.click(editButtons[0]);
|
||||
// You would typically assert something here, but console.log is used in the mocked function
|
||||
},
|
||||
};
|
||||
|
||||
export const EmptyTableTest: Story = {
|
||||
...EmptyTable,
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const emptyMessage = canvas.getByText("No agents found");
|
||||
expect(emptyMessage).toBeTruthy();
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
// AgentTable.tsx
|
||||
import * as React from "react";
|
||||
import { AgentTableRow, AgentTableRowProps } from "./AgentTableRow";
|
||||
import { AgentTableCard } from "./AgentTableCard";
|
||||
|
||||
export interface AgentTableProps {
|
||||
agents: AgentTableRowProps[];
|
||||
}
|
||||
|
||||
export const AgentTable: React.FC<AgentTableProps> = ({ agents }) => {
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-[1095px]">
|
||||
{/* Table for medium and larger screens */}
|
||||
<div className="hidden md:block">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-[#d9d9d9]">
|
||||
<th className="font-['PP Neue Montreal TT'] py-4 text-left text-base leading-[21px] tracking-tight text-[#282828]">
|
||||
Agent
|
||||
</th>
|
||||
<th className="font-['PP Neue Montreal TT'] py-4 text-left text-base leading-[21px] tracking-tight text-[#282828]">
|
||||
Date submitted
|
||||
</th>
|
||||
<th className="font-['PP Neue Montreal TT'] py-4 text-left text-base leading-[21px] tracking-tight text-[#282828]">
|
||||
Status
|
||||
</th>
|
||||
<th className="font-['PP Neue Montreal TT'] py-4 text-left text-base leading-[21px] tracking-tight text-[#282828]">
|
||||
Runs
|
||||
</th>
|
||||
<th className="font-['PP Neue Montreal TT'] py-4 text-left text-base leading-[21px] tracking-tight text-[#282828]">
|
||||
Reviews
|
||||
</th>
|
||||
<th className="font-['PP Neue Montreal TT'] py-4 text-left text-base leading-[21px] tracking-tight text-[#282828]">
|
||||
Actions
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{agents.length > 0 ? (
|
||||
agents.map((agent, index) => (
|
||||
<AgentTableRow key={index} {...agent} />
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td
|
||||
colSpan={6}
|
||||
className="font-['PP Neue Montreal TT'] py-4 text-center text-base text-[#282828]"
|
||||
>
|
||||
No agents available. Create your first agent to get started!
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/* Cards for small screens */}
|
||||
<div className="block md:hidden">
|
||||
{agents.length > 0 ? (
|
||||
agents.map((agent, index) => (
|
||||
<AgentTableCard key={index} {...agent} />
|
||||
))
|
||||
) : (
|
||||
<div className="font-['PP Neue Montreal TT'] py-4 text-center text-base text-[#707070]">
|
||||
No agents available. Create your first agent to get started!
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { AgentTableCard } from "./AgentTableCard";
|
||||
import { userEvent, within, expect } from "@storybook/test";
|
||||
|
||||
const meta: Meta<typeof AgentTableCard> = {
|
||||
title: "AGPT UI/Agent Table Card",
|
||||
component: AgentTableCard,
|
||||
tags: ["autodocs"],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof AgentTableCard>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
agentName: "Super Coder",
|
||||
description: "An AI agent that writes clean, efficient code",
|
||||
imageSrc:
|
||||
"https://ddz4ak4pa3d19.cloudfront.net/cache/53/b2/53b2bc7d7900f0e1e60bf64ebf38032d.jpg",
|
||||
dateSubmitted: "2023-05-15",
|
||||
status: "Active",
|
||||
runs: 1500,
|
||||
rating: 4.8,
|
||||
onEdit: () => console.log("Edit Super Coder"),
|
||||
},
|
||||
};
|
||||
|
||||
export const NoRating: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
rating: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
export const NoRuns: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
runs: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
export const InactiveAgent: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
status: "Inactive",
|
||||
},
|
||||
};
|
||||
|
||||
export const LongDescription: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
description:
|
||||
"This is a very long description that should wrap to multiple lines. It contains detailed information about the agent and its capabilities.",
|
||||
},
|
||||
};
|
||||
|
||||
export const InteractionTest: Story = {
|
||||
...Default,
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const editButton = canvas.getByText("Edit");
|
||||
await userEvent.click(editButton);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
// AgentCard.tsx
|
||||
import * as React from "react";
|
||||
import Image from "next/image";
|
||||
import { Button } from "./Button";
|
||||
import { IconStarFilled, IconEdit } from "../ui/icons";
|
||||
import { Card } from "../ui/card";
|
||||
import { AgentTableRowProps } from "./AgentTableRow";
|
||||
|
||||
export const AgentTableCard: React.FC<AgentTableRowProps> = ({
|
||||
agentName,
|
||||
description,
|
||||
imageSrc,
|
||||
dateSubmitted,
|
||||
status,
|
||||
runs,
|
||||
rating,
|
||||
onEdit,
|
||||
}) => {
|
||||
return (
|
||||
<Card className="mb-4 p-4">
|
||||
<div className="flex">
|
||||
<div className="relative mr-4 h-20 w-20 overflow-hidden rounded-xl">
|
||||
<Image
|
||||
src={imageSrc}
|
||||
alt={agentName}
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="mb-2 font-neue text-lg font-medium tracking-tight text-[#272727]">
|
||||
{agentName}
|
||||
</h3>
|
||||
<p className="mb-2 font-neue text-sm leading-tight tracking-tight text-[#282828]">
|
||||
{description}
|
||||
</p>
|
||||
<div className="mb-2 font-neue text-sm leading-tight tracking-tight text-[#282828]">
|
||||
<strong>Date submitted:</strong> {dateSubmitted}
|
||||
</div>
|
||||
<div className="mb-2 font-neue text-sm leading-tight tracking-tight text-[#282828]">
|
||||
<strong>Status:</strong> {status}
|
||||
</div>
|
||||
{runs !== undefined && (
|
||||
<div className="mb-2 font-neue text-sm leading-tight tracking-tight text-[#282828]">
|
||||
<strong>Runs:</strong> {runs.toLocaleString()}
|
||||
</div>
|
||||
)}
|
||||
{rating !== undefined && (
|
||||
<div className="mb-2 flex items-center font-neue text-sm leading-tight tracking-tight text-[#282828]">
|
||||
<strong>Rating:</strong>
|
||||
<span className="ml-2">{rating.toFixed(1)}</span>
|
||||
<IconStarFilled className="ml-1" />
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="mt-2 flex items-center gap-1"
|
||||
onClick={onEdit}
|
||||
>
|
||||
<IconEdit />
|
||||
<span>Edit</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
import * as React from "react";
|
||||
import Image from "next/image";
|
||||
import { Button } from "./Button";
|
||||
import { IconStarFilled, IconEdit } from "../ui/icons";
|
||||
|
||||
export interface AgentTableRowProps {
|
||||
agentName: string;
|
||||
description: string;
|
||||
imageSrc: string;
|
||||
dateSubmitted: string;
|
||||
status: string;
|
||||
runs?: number;
|
||||
rating?: number;
|
||||
onEdit: () => void;
|
||||
}
|
||||
|
||||
export const AgentTableRow: React.FC<AgentTableRowProps> = ({
|
||||
agentName,
|
||||
description,
|
||||
imageSrc,
|
||||
dateSubmitted,
|
||||
status,
|
||||
runs,
|
||||
rating,
|
||||
onEdit,
|
||||
}) => {
|
||||
return (
|
||||
<tr className="border-b border-[#d9d9d9] py-4">
|
||||
<td className="flex items-center">
|
||||
<div className="relative my-4 mr-4 h-20 w-20 overflow-hidden rounded-xl sm:h-20 sm:w-[125px]">
|
||||
<Image
|
||||
src={imageSrc}
|
||||
alt={agentName}
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="max-w-[293px]">
|
||||
<h3 className="mb-2 font-neue text-lg font-medium tracking-tight text-[#272727]">
|
||||
{agentName}
|
||||
</h3>
|
||||
<p className="font-neue text-sm leading-tight tracking-tight text-[#282828]">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
<td className="font-neue text-base leading-[21px] tracking-tight text-[#282828]">
|
||||
{dateSubmitted}
|
||||
</td>
|
||||
<td className="font-neue text-base leading-[21px] tracking-tight text-[#282828]">
|
||||
{status}
|
||||
</td>
|
||||
<td className="font-neue text-base leading-[21px] tracking-tight text-[#282828]">
|
||||
{runs !== undefined ? runs.toLocaleString() : ""}
|
||||
</td>
|
||||
<td>
|
||||
{rating !== undefined && (
|
||||
<div className="flex items-center">
|
||||
<span className="mr-2 font-neue text-base font-medium tracking-tight text-[#272727]">
|
||||
{rating.toFixed(1)}
|
||||
</span>
|
||||
<IconStarFilled />
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex items-center gap-1"
|
||||
onClick={onEdit}
|
||||
>
|
||||
<IconEdit />
|
||||
<span className="font-neue text-sm">Edit</span>
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
@@ -18,9 +18,7 @@ type Story = StoryObj<typeof meta>;
|
||||
|
||||
const defaultLinkGroups = [
|
||||
{
|
||||
links: [
|
||||
{ text: "Integrations", href: "/integrations" },
|
||||
],
|
||||
links: [{ text: "Integrations", href: "/integrations" }],
|
||||
},
|
||||
{
|
||||
links: [
|
||||
@@ -67,8 +65,14 @@ export const LongLinkTexts: Story = {
|
||||
linkGroups: [
|
||||
{
|
||||
links: [
|
||||
{ text: "This is a very long link text that might wrap", href: "/long-link-1" },
|
||||
{ text: "Another extremely long link text for testing purposes", href: "/long-link-2" },
|
||||
{
|
||||
text: "This is a very long link text that might wrap",
|
||||
href: "/long-link-1",
|
||||
},
|
||||
{
|
||||
text: "Another extremely long link text for testing purposes",
|
||||
href: "/long-link-2",
|
||||
},
|
||||
],
|
||||
},
|
||||
...defaultLinkGroups,
|
||||
|
||||
@@ -5,78 +5,81 @@ import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
|
||||
import { Menu } from "lucide-react";
|
||||
|
||||
interface SidebarLinkGroup {
|
||||
links: {
|
||||
text: string;
|
||||
href: string;
|
||||
}[];
|
||||
links: {
|
||||
text: string;
|
||||
href: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
interface SidebarProps {
|
||||
linkGroups: SidebarLinkGroup[];
|
||||
linkGroups: SidebarLinkGroup[];
|
||||
}
|
||||
|
||||
export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
||||
return (
|
||||
<>
|
||||
<Sheet>
|
||||
<SheetTrigger asChild>
|
||||
<button aria-label="Open sidebar menu" className="md:hidden fixed top-1/2 left-0 border border-neutral-500 bg-neutral-200 rounded-r-xl p-1">
|
||||
<Menu className="h-6 w-6" />
|
||||
<span className="sr-only">Open sidebar menu</span>
|
||||
</button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="left" className="w-[280px] sm:w-[280px] p-0">
|
||||
<div className="h-full bg-neutral-100">
|
||||
<div className="p-6 flex flex-col justify-start items-start gap-[30px]">
|
||||
<h2 className="text-neutral-900 text-xl font-medium font-neue leading-7 tracking-tight">
|
||||
Creator Dashboard
|
||||
</h2>
|
||||
<Separator className="self-stretch" />
|
||||
{linkGroups.map((group, groupIndex) => (
|
||||
<React.Fragment key={groupIndex}>
|
||||
{group.links.map((link, linkIndex) => (
|
||||
<Link
|
||||
key={linkIndex}
|
||||
href={link.href}
|
||||
className="self-stretch text-neutral-500 text-xl font-normal font-neue leading-7 tracking-tight hover:text-neutral-700"
|
||||
>
|
||||
{link.text}
|
||||
</Link>
|
||||
))}
|
||||
{groupIndex < linkGroups.length - 1 && (
|
||||
<Separator className="self-stretch" />
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
<div className="hidden md:block w-[280px] h-[934px] relative">
|
||||
<div className="w-full h-full absolute left-0 top-0 bg-neutral-100" />
|
||||
<div className="w-[210px] absolute left-10 top-[63px] flex flex-col justify-start items-start gap-[30px]">
|
||||
<h2 className="self-stretch text-neutral-900 text-xl font-normal font-neue leading-7 tracking-tight hover:text-neutral-700">
|
||||
Creator Dashboard
|
||||
</h2>
|
||||
return (
|
||||
<>
|
||||
<Sheet>
|
||||
<SheetTrigger asChild>
|
||||
<button
|
||||
aria-label="Open sidebar menu"
|
||||
className="fixed left-0 top-1/2 rounded-r-xl border border-neutral-500 bg-neutral-200 p-1 md:hidden"
|
||||
>
|
||||
<Menu className="h-6 w-6" />
|
||||
<span className="sr-only">Open sidebar menu</span>
|
||||
</button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="left" className="w-[280px] p-0 sm:w-[280px]">
|
||||
<div className="h-full bg-neutral-100">
|
||||
<div className="flex flex-col items-start justify-start gap-[30px] p-6">
|
||||
<h2 className="font-neue text-xl font-medium leading-7 tracking-tight text-neutral-900">
|
||||
Creator Dashboard
|
||||
</h2>
|
||||
<Separator className="self-stretch" />
|
||||
{linkGroups.map((group, groupIndex) => (
|
||||
<React.Fragment key={groupIndex}>
|
||||
{group.links.map((link, linkIndex) => (
|
||||
<Link
|
||||
key={linkIndex}
|
||||
href={link.href}
|
||||
className="self-stretch font-neue text-xl font-normal leading-7 tracking-tight text-neutral-500 hover:text-neutral-700"
|
||||
>
|
||||
{link.text}
|
||||
</Link>
|
||||
))}
|
||||
{groupIndex < linkGroups.length - 1 && (
|
||||
<Separator className="self-stretch" />
|
||||
{linkGroups.map((group, groupIndex) => (
|
||||
<React.Fragment key={groupIndex}>
|
||||
{group.links.map((link, linkIndex) => (
|
||||
<Link
|
||||
key={linkIndex}
|
||||
href={link.href}
|
||||
className="self-stretch text-neutral-600 text-xl font-normal font-neue leading-7 tracking-tight hover:text-neutral-700"
|
||||
>
|
||||
{link.text}
|
||||
</Link>
|
||||
))}
|
||||
{groupIndex < linkGroups.length - 1 && (
|
||||
<Separator className="self-stretch" />
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
<div className="relative hidden h-[934px] w-[280px] md:block">
|
||||
<div className="absolute left-0 top-0 h-full w-full bg-neutral-100" />
|
||||
<div className="absolute left-10 top-[63px] flex w-[210px] flex-col items-start justify-start gap-[30px]">
|
||||
<h2 className="self-stretch font-neue text-xl font-normal leading-7 tracking-tight text-neutral-900 hover:text-neutral-700">
|
||||
Creator Dashboard
|
||||
</h2>
|
||||
<Separator className="self-stretch" />
|
||||
{linkGroups.map((group, groupIndex) => (
|
||||
<React.Fragment key={groupIndex}>
|
||||
{group.links.map((link, linkIndex) => (
|
||||
<Link
|
||||
key={linkIndex}
|
||||
href={link.href}
|
||||
className="self-stretch font-neue text-xl font-normal leading-7 tracking-tight text-neutral-600 hover:text-neutral-700"
|
||||
>
|
||||
{link.text}
|
||||
</Link>
|
||||
))}
|
||||
{groupIndex < linkGroups.length - 1 && (
|
||||
<Separator className="self-stretch" />
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user