mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-02-14 00:35:02 -05:00
library folder basic UI
This commit is contained in:
@@ -5,6 +5,7 @@ import { InfiniteScroll } from "@/components/contextual/InfiniteScroll/InfiniteS
|
||||
import { LibraryActionSubHeader } from "../LibraryActionSubHeader/LibraryActionSubHeader";
|
||||
import { LibraryAgentCard } from "../LibraryAgentCard/LibraryAgentCard";
|
||||
import { useLibraryAgentList } from "./useLibraryAgentList";
|
||||
import { LibraryFolder } from "../LibraryFolder/LibraryFolder";
|
||||
|
||||
interface Props {
|
||||
searchTerm: string;
|
||||
@@ -45,6 +46,11 @@ export function LibraryAgentList({
|
||||
loader={<LoadingSpinner size="medium" />}
|
||||
>
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
<LibraryFolder name="Github Agents" agentCount={34} color="blue" icon="🤨"/>
|
||||
<LibraryFolder name="Linear Agents" agentCount={3} color="green" icon="☘️"/>
|
||||
<LibraryFolder name="Discord Agents" agentCount={32} color="red" icon="🚀"/>
|
||||
<LibraryFolder name="Telegram Agents" agentCount={12} color="purple" icon="💬"/>
|
||||
<LibraryFolder name="Email Agents" agentCount={10} color="yellow" icon="👍"/>
|
||||
{agents.map((agent) => (
|
||||
<LibraryAgentCard key={agent.id} agent={agent} />
|
||||
))}
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
import { useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { Text } from "@/components/atoms/Text/Text";
|
||||
|
||||
type FolderSize = "xs" | "sm" | "md" | "lg" | "xl";
|
||||
export type FolderColor =
|
||||
| "neutral"
|
||||
| "slate"
|
||||
| "zinc"
|
||||
| "stone"
|
||||
| "red"
|
||||
| "orange"
|
||||
| "amber"
|
||||
| "yellow"
|
||||
| "lime"
|
||||
| "green"
|
||||
| "emerald"
|
||||
| "teal"
|
||||
| "cyan"
|
||||
| "sky"
|
||||
| "blue"
|
||||
| "indigo"
|
||||
| "violet"
|
||||
| "purple"
|
||||
| "fuchsia"
|
||||
| "pink"
|
||||
| "rose";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
size?: FolderSize | number;
|
||||
color?: FolderColor;
|
||||
icon?: string;
|
||||
isOpen?: boolean;
|
||||
}
|
||||
|
||||
const sizeMap: Record<FolderSize, number> = {
|
||||
xs: 0.5,
|
||||
sm: 0.75,
|
||||
md: 1,
|
||||
lg: 1.25,
|
||||
xl: 1.5,
|
||||
};
|
||||
|
||||
const colorMap: Record<
|
||||
FolderColor,
|
||||
{ bg: string; border: string; borderLight: string; fill: string; stroke: string }
|
||||
> = {
|
||||
neutral: { bg: "bg-neutral-300", border: "border-neutral-300", borderLight: "border-neutral-200", fill: "fill-neutral-300", stroke: "stroke-neutral-400" },
|
||||
slate: { bg: "bg-slate-300", border: "border-slate-300", borderLight: "border-slate-200", fill: "fill-slate-300", stroke: "stroke-slate-400" },
|
||||
zinc: { bg: "bg-zinc-300", border: "border-zinc-300", borderLight: "border-zinc-200", fill: "fill-zinc-300", stroke: "stroke-zinc-400" },
|
||||
stone: { bg: "bg-stone-300", border: "border-stone-300", borderLight: "border-stone-200", fill: "fill-stone-300", stroke: "stroke-stone-400" },
|
||||
red: { bg: "bg-red-300", border: "border-red-300", borderLight: "border-red-200", fill: "fill-red-300", stroke: "stroke-red-400" },
|
||||
orange: { bg: "bg-orange-200", border: "border-orange-200", borderLight: "border-orange-200", fill: "fill-orange-200", stroke: "stroke-orange-400" },
|
||||
amber: { bg: "bg-amber-200", border: "border-amber-200", borderLight: "border-amber-200", fill: "fill-amber-200", stroke: "stroke-amber-400" },
|
||||
yellow: { bg: "bg-yellow-200", border: "border-yellow-200", borderLight: "border-yellow-200", fill: "fill-yellow-200", stroke: "stroke-yellow-400" },
|
||||
lime: { bg: "bg-lime-300", border: "border-lime-300", borderLight: "border-lime-200", fill: "fill-lime-300", stroke: "stroke-lime-400" },
|
||||
green: { bg: "bg-green-200", border: "border-green-200", borderLight: "border-green-200", fill: "fill-green-200", stroke: "stroke-green-400" },
|
||||
emerald: { bg: "bg-emerald-300", border: "border-emerald-300", borderLight: "border-emerald-200", fill: "fill-emerald-300", stroke: "stroke-emerald-400" },
|
||||
teal: { bg: "bg-teal-300", border: "border-teal-300", borderLight: "border-teal-200", fill: "fill-teal-300", stroke: "stroke-teal-400" },
|
||||
cyan: { bg: "bg-cyan-300", border: "border-cyan-300", borderLight: "border-cyan-200", fill: "fill-cyan-300", stroke: "stroke-cyan-400" },
|
||||
sky: { bg: "bg-sky-300", border: "border-sky-300", borderLight: "border-sky-200", fill: "fill-sky-300", stroke: "stroke-sky-400" },
|
||||
blue: { bg: "bg-blue-300", border: "border-blue-300", borderLight: "border-blue-200", fill: "fill-blue-300", stroke: "stroke-blue-400" },
|
||||
indigo: { bg: "bg-indigo-300", border: "border-indigo-300", borderLight: "border-indigo-200", fill: "fill-indigo-300", stroke: "stroke-indigo-400" },
|
||||
violet: { bg: "bg-violet-300", border: "border-violet-300", borderLight: "border-violet-200", fill: "fill-violet-300", stroke: "stroke-violet-400" },
|
||||
purple: { bg: "bg-purple-200", border: "border-purple-200", borderLight: "border-purple-200", fill: "fill-purple-200", stroke: "stroke-purple-400" },
|
||||
fuchsia: { bg: "bg-fuchsia-300", border: "border-fuchsia-300", borderLight: "border-fuchsia-200", fill: "fill-fuchsia-300", stroke: "stroke-fuchsia-400" },
|
||||
pink: { bg: "bg-pink-300", border: "border-pink-300", borderLight: "border-pink-200", fill: "fill-pink-300", stroke: "stroke-pink-400" },
|
||||
rose: { bg: "bg-rose-300", border: "border-rose-300", borderLight: "border-rose-200", fill: "fill-rose-300", stroke: "stroke-rose-400" },
|
||||
};
|
||||
|
||||
export function FolderIcon({
|
||||
className = "",
|
||||
size = "xs",
|
||||
color = "blue",
|
||||
icon,
|
||||
isOpen = false,
|
||||
}: Props) {
|
||||
const scale = typeof size === "number" ? size : sizeMap[size];
|
||||
const colors = colorMap[color];
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`group relative cursor-pointer ${className}`}
|
||||
style={{
|
||||
width: 320 * scale,
|
||||
height: 208 * scale,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="h-52 w-80 origin-top-left"
|
||||
style={{ transform: `scale(${scale})`, perspective: "500px" }}
|
||||
>
|
||||
<div
|
||||
className={`folder-back relative mx-auto flex h-full w-[87.5%] justify-center overflow-visible rounded-3xl ${colors.bg} ${colors.border}`}
|
||||
>
|
||||
{[
|
||||
{
|
||||
initial: { rotate: -3, x: -38, y: 2 },
|
||||
open: { rotate: -8, x: -70, y: -75 },
|
||||
transition: {
|
||||
type: "spring" as const,
|
||||
bounce: 0.15,
|
||||
stiffness: 160,
|
||||
damping: 22,
|
||||
},
|
||||
className: "z-10",
|
||||
},
|
||||
{
|
||||
initial: { rotate: 0, x: 0, y: 0 },
|
||||
open: { rotate: 1, x: 2, y: -95 },
|
||||
transition: {
|
||||
type: "spring" as const,
|
||||
duration: 0.55,
|
||||
bounce: 0.12,
|
||||
stiffness: 190,
|
||||
damping: 24,
|
||||
},
|
||||
className: "z-20",
|
||||
},
|
||||
{
|
||||
initial: { rotate: 3.5, x: 42, y: 1 },
|
||||
open: { rotate: 9, x: 75, y: -80 },
|
||||
transition: {
|
||||
type: "spring" as const,
|
||||
duration: 0.58,
|
||||
bounce: 0.17,
|
||||
stiffness: 170,
|
||||
damping: 21,
|
||||
},
|
||||
className: "z-10",
|
||||
},
|
||||
].map((page, i) => (
|
||||
<motion.div
|
||||
key={i}
|
||||
initial={page.initial}
|
||||
animate={isOpen ? page.open : page.initial}
|
||||
transition={page.transition}
|
||||
className={`absolute top-2 h-fit w-32 rounded-xl shadow-lg ${page.className}`}
|
||||
>
|
||||
<Page color={color} />
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
animate={{
|
||||
rotateX: isOpen ? -15 : 0,
|
||||
}}
|
||||
transition={{ type: "spring", duration: 0.5, bounce: 0.25 }}
|
||||
className="absolute inset-x-0 -bottom-px z-30 mx-auto flex h-44 w-[87.5%] origin-bottom items-end justify-center overflow-visible"
|
||||
style={{ transformStyle: "preserve-3d" }}
|
||||
>
|
||||
<svg
|
||||
className="h-auto w-full "
|
||||
viewBox="0 0 173 109"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
preserveAspectRatio="none"
|
||||
>
|
||||
<path
|
||||
className={`${colors.fill} ${colors.stroke}`}
|
||||
d="M15.0423 0.500003C0.5 0.500009 0.5 14.2547 0.5 14.2547V92.5C0.5 101.337 7.66344 108.5 16.5 108.5H156.5C165.337 108.5 172.5 101.337 172.5 92.5V34.3302C172.5 25.4936 165.355 18.3302 156.519 18.3302H108.211C98.1341 18.3302 91.2921 5.57144 82.0156 1.63525C80.3338 0.921645 78.2634 0.500002 75.7187 0.500003H15.0423Z"
|
||||
/>
|
||||
|
||||
</svg>
|
||||
<div className="absolute inset-0 flex items-center justify-center text-7xl">
|
||||
{icon}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface PageProps {
|
||||
color: FolderColor;
|
||||
}
|
||||
|
||||
function Page({ color = "blue" }: PageProps) {
|
||||
const colors = colorMap[color];
|
||||
return (
|
||||
<div
|
||||
className={`h-full w-full rounded-xl border bg-white p-4 ${colors.borderLight}`}
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Text variant="h5" className="text-black">agent.json</Text>
|
||||
{Array.from({ length: 8 }).map((_, i) => (
|
||||
<div key={i} className="flex gap-2">
|
||||
<div className="h-1.5 flex-1 rounded-full bg-neutral-100" />
|
||||
<div className="h-1.5 flex-1 rounded-full bg-neutral-100" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
"use client";
|
||||
|
||||
import { Text } from "@/components/atoms/Text/Text";
|
||||
import { Button } from "@/components/atoms/Button/Button";
|
||||
import { FolderIcon, FolderColor } from "./FolderIcon";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
PencilSimpleIcon,
|
||||
TrashIcon,
|
||||
StarIcon,
|
||||
} from "@phosphor-icons/react";
|
||||
|
||||
interface Props {
|
||||
name: string;
|
||||
agentCount: number;
|
||||
color: FolderColor;
|
||||
icon: string;
|
||||
onEdit?: () => void;
|
||||
onDelete?: () => void;
|
||||
onFavorite?: () => void;
|
||||
isFavorite?: boolean;
|
||||
}
|
||||
|
||||
export function LibraryFolder({
|
||||
name,
|
||||
agentCount,
|
||||
color,
|
||||
icon,
|
||||
onEdit,
|
||||
onDelete,
|
||||
onFavorite,
|
||||
isFavorite = false,
|
||||
}: Props) {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="library-folder"
|
||||
className="group relative inline-flex h-[10.625rem] w-full max-w-[25rem] cursor-pointer flex-col items-start justify-start gap-2.5 rounded-medium border border-zinc-100 bg-white p-4 transition-all duration-200 hover:shadow-md"
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<div className="flex w-full items-start justify-between gap-4">
|
||||
{/* Left side - Folder name and agent count */}
|
||||
<div className="flex flex-1 flex-col gap-2">
|
||||
<Text
|
||||
variant="h5"
|
||||
data-testid="library-folder-name"
|
||||
className="line-clamp-2 hyphens-auto break-words"
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
<Text
|
||||
variant="small"
|
||||
className="text-zinc-500"
|
||||
data-testid="library-folder-agent-count"
|
||||
>
|
||||
{agentCount} {agentCount === 1 ? "agent" : "agents"}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
{/* Right side - Custom folder icon */}
|
||||
<div className="flex-shrink-0">
|
||||
<FolderIcon isOpen={isHovered} color={color} icon={icon} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action buttons - visible on hover */}
|
||||
<div
|
||||
className="flex items-center justify-end gap-2"
|
||||
data-testid="library-folder-actions"
|
||||
>
|
||||
<Button
|
||||
variant="icon"
|
||||
size="icon"
|
||||
aria-label="Favorite agent"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onFavorite?.();
|
||||
}}
|
||||
className="h-8 w-8 p-2"
|
||||
>
|
||||
<StarIcon
|
||||
className="h-4 w-4"
|
||||
weight={isFavorite ? "fill" : "regular"}
|
||||
color={isFavorite ? "#facc15" : "currentColor"}
|
||||
/>
|
||||
</Button>
|
||||
<Button
|
||||
variant="icon"
|
||||
size="icon"
|
||||
aria-label="Edit agent"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onEdit?.();
|
||||
}}
|
||||
className="h-8 w-8 p-2"
|
||||
>
|
||||
<PencilSimpleIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="icon"
|
||||
size="icon"
|
||||
aria-label="Delete agent"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete?.();
|
||||
}}
|
||||
className="h-8 w-8 p-2 hover:border-red-300 hover:bg-red-50 hover:text-red-600"
|
||||
>
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user