This commit is contained in:
abhi1992002 2024-12-27 10:01:45 +05:30
parent b44e6c5164
commit e321d17151
23 changed files with 1380 additions and 144 deletions

View File

@ -1,5 +1,6 @@
import logging
from typing import List
import typing
import prisma.errors
import prisma.models
@ -15,17 +16,21 @@ logger = logging.getLogger(__name__)
async def get_library_agents(
user_id: str,
limit: int = 20,
offset: int = 0,
) -> List[backend.server.v2.library.model.LibraryAgent]:
"""
Returns all agents (AgentGraph) that belong to the user and all agents in their library (UserAgent table)
Returns paginated agents (AgentGraph) that belong to the user and agents in their library (UserAgent table)
"""
logger.debug(f"Getting library agents for user {user_id}")
logger.debug(f"Getting library agents for user {user_id} with limit {limit} offset {offset}")
try:
# Get agents created by user with nodes and links
user_created = await prisma.models.AgentGraph.prisma().find_many(
where=prisma.types.AgentGraphWhereInput(userId=user_id, isActive=True),
include=backend.data.includes.AGENT_GRAPH_INCLUDE,
skip=offset,
take=limit,
)
# Get agents in user's library with nodes and links
@ -47,6 +52,8 @@ async def get_library_agents(
}
}
},
skip=offset,
take=limit,
)
# Convert to Graph models first
@ -94,6 +101,122 @@ async def get_library_agents(
"Failed to fetch library agents"
) from e
async def search_library_agents(
user_id: str,
search_term: str,
sort_by: typing.Literal["most_recent", "highest_runtime", "most_runs", "alphabetical", "last_modified"],
limit: int = 20,
offset: int = 0,
) -> List[backend.server.v2.library.model.LibraryAgent]:
"""
Searches paginated agents (AgentGraph) that belong to the user and agents in their library (UserAgent table)
based on name or description containing the search term
"""
logger.debug(f"Searching library agents for user {user_id} with term '{search_term}', limit {limit} offset {offset}")
try:
# Get sort field
sort_order = "desc" if sort_by in ["most_recent", "highest_runtime", "most_runs", "last_modified"] else "asc"
sort_field = {
"most_recent": "createdAt",
"last_modified": "updatedAt",
"highest_runtime": "totalRuntime",
"most_runs": "runCount",
"alphabetical": "name"
}.get(sort_by, "updatedAt")
# Get user created agents matching search
user_created = await prisma.models.AgentGraph.prisma().find_many(
where=prisma.types.AgentGraphWhereInput(
userId=user_id,
isActive=True,
OR=[
{"name": {"contains": search_term, "mode": "insensitive"}},
{"description": {"contains": search_term, "mode": "insensitive"}}
]
),
include=backend.data.includes.AGENT_GRAPH_INCLUDE,
order={sort_field: sort_order},
skip=offset,
take=limit,
)
# Get library agents matching search
library_agents = await prisma.models.UserAgent.prisma().find_many(
where=prisma.types.UserAgentWhereInput(
userId=user_id,
isDeleted=False,
isArchived=False,
Agent={
"is": {
"OR": [
{"name": {"contains": search_term, "mode": "insensitive"}},
{"description": {"contains": search_term, "mode": "insensitive"}}
]
}
}
),
include={
"Agent": {
"include": {
"AgentNodes": {
"include": {
"Input": True,
"Output": True,
"Webhook": True,
"AgentBlock": True,
}
}
}
}
},
skip=offset,
take=limit,
)
# Convert to Graph models
graphs = []
for agent in user_created:
try:
graphs.append(backend.data.graph.GraphModel.from_db(agent))
except Exception as e:
logger.error(f"Error processing searched user agent {agent.id}: {e}")
continue
for agent in library_agents:
if agent.Agent:
try:
graphs.append(backend.data.graph.GraphModel.from_db(agent.Agent))
except Exception as e:
logger.error(f"Error processing searched library agent {agent.agentId}: {e}")
continue
# Convert to LibraryAgent models
result = []
for graph in graphs:
result.append(
backend.server.v2.library.model.LibraryAgent(
id=graph.id,
version=graph.version,
is_active=graph.is_active,
name=graph.name,
description=graph.description,
isCreatedByUser=any(a.id == graph.id for a in user_created),
input_schema=graph.input_schema,
output_schema=graph.output_schema,
)
)
logger.debug(f"Found {len(result)} library agents matching search")
return result
except prisma.errors.PrismaError as e:
logger.error(f"Database error searching library agents: {str(e)}")
raise backend.server.v2.store.exceptions.DatabaseError(
"Failed to search library agents"
) from e
async def add_agent_to_library(store_listing_version_id: str, user_id: str) -> None:
"""

View File

@ -4,7 +4,6 @@ import typing
import autogpt_libs.auth.depends
import autogpt_libs.auth.middleware
import fastapi
import prisma
import backend.data.graph
import backend.integrations.creds_manager
@ -28,14 +27,37 @@ integration_creds_manager = (
async def get_library_agents(
user_id: typing.Annotated[
str, fastapi.Depends(autogpt_libs.auth.depends.get_user_id)
]
) -> typing.Sequence[backend.server.v2.library.model.LibraryAgent]:
],
pagination_token: str | None = fastapi.Query(None)
) -> dict[str, typing.Any]:
"""
Get all agents in the user's library, including both created and saved agents.
Get agents in the user's library with pagination (20 agents per page).
Args:
user_id: ID of the authenticated user
pagination_token: Token to get next page of results
Returns:
Dictionary containing:
- agents: List of agents for current page
- next_token: Token to get next page (None if no more pages)
"""
try:
agents = await backend.server.v2.library.db.get_library_agents(user_id)
return agents
page_size = 20
agents = await backend.server.v2.library.db.get_library_agents(
user_id,
limit=page_size + 1,
offset=int(pagination_token) if pagination_token else 0
)
has_more = len(agents) > page_size
agents = agents[:page_size]
next_token = str(int(pagination_token or 0) + page_size) if has_more else None
return {
"agents": agents,
"next_token": next_token
}
except Exception:
logger.exception("Exception occurred whilst getting library agents")
raise fastapi.HTTPException(
@ -43,6 +65,58 @@ async def get_library_agents(
)
# For searching and filtering the library agents
@router.get(
"/agents/search",
tags=["library", "private"],
dependencies=[fastapi.Depends(autogpt_libs.auth.middleware.auth_middleware)],
)
async def search_library_agents(
user_id: typing.Annotated[
str, fastapi.Depends(autogpt_libs.auth.depends.get_user_id)
],
search_term: str = fastapi.Query(..., description="Search term to filter agents"),
sort_by: typing.Literal["most_recent", "highest_runtime", "most_runs", "alphabetical", "last_modified"] = fastapi.Query("most_recent", description="Sort results by criteria"),
pagination_token: str | None = fastapi.Query(None)
) -> dict[str, typing.Any]:
"""
Search for agents in the user's library with pagination (20 agents per page).
Args:
user_id: ID of the authenticated user
search_term: Term to search for in agent names/descriptions
sort_by: How to sort results (most_recent, highest_runtime, most_runs, alphabetical, last_modified)
pagination_token: Token to get next page of results
Returns:
Dictionary containing:
- agents: List of matching agents for current page
- next_token: Token to get next page (None if no more pages)
"""
try:
page_size = 20
agents = await backend.server.v2.library.db.search_library_agents(
user_id,
search_term,
sort_by=sort_by,
limit=page_size + 1,
offset=int(pagination_token) if pagination_token else 0
)
has_more = len(agents) > page_size
agents = agents[:page_size]
next_token = str(int(pagination_token or 0) + page_size) if has_more else None
return {
"agents": agents,
"next_token": next_token
}
except Exception:
logger.exception("Exception occurred whilst searching library agents")
raise fastapi.HTTPException(
status_code=500, detail="Failed to search library agents"
)
@router.post(
"/agents/{store_listing_version_id}",
tags=["library", "private"],
@ -70,49 +144,51 @@ async def add_agent_to_library(
"""
try:
# Get the graph from the store listing
store_listing_version = (
await prisma.models.StoreListingVersion.prisma().find_unique(
where={"id": store_listing_version_id}, include={"Agent": True}
)
)
# store_listing_version = (
# await prisma.models.StoreListingVersion.prisma().find_unique(
# where={"id": store_listing_version_id}, include={"Agent": True}
# )
# )
if not store_listing_version or not store_listing_version.Agent:
raise fastapi.HTTPException(
status_code=404,
detail=f"Store listing version {store_listing_version_id} not found",
)
# if not store_listing_version or not store_listing_version.Agent:
# raise fastapi.HTTPException(
# status_code=404,
# detail=f"Store listing version {store_listing_version_id} not found",
# )
agent = store_listing_version.Agent
# agent = store_listing_version.Agent
if agent.userId == user_id:
raise fastapi.HTTPException(
status_code=400, detail="Cannot add own agent to library"
)
# if agent.userId == user_id:
# raise fastapi.HTTPException(
# status_code=400, detail="Cannot add own agent to library"
# )
# Create a new graph from the template
graph = await backend.data.graph.get_graph(
agent.id, agent.version, template=True, user_id=user_id
)
# # Create a new graph from the template
# graph = await backend.data.graph.get_graph(
# agent.id, agent.version, template=True, user_id=user_id
# )
if not graph:
raise fastapi.HTTPException(
status_code=404, detail=f"Agent {agent.id} not found"
)
# if not graph:
# raise fastapi.HTTPException(
# status_code=404, detail=f"Agent {agent.id} not found"
# )
# Create a deep copy with new IDs
graph.version = 1
graph.is_template = False
graph.is_active = True
graph.reassign_ids(user_id=user_id, reassign_graph_id=True)
# # Create a deep copy with new IDs
# graph.version = 1
# graph.is_template = False
# graph.is_active = True
# graph.reassign_ids(user_id=user_id, reassign_graph_id=True)
# Save the new graph
graph = await backend.data.graph.create_graph(graph, user_id=user_id)
graph = (
await backend.integrations.webhooks.graph_lifecycle_hooks.on_graph_activate(
graph,
get_credentials=lambda id: integration_creds_manager.get(user_id, id),
)
)
# # Save the new graph
# graph = await backend.data.graph.create_graph(graph, user_id=user_id)
# graph = (
# await backend.integrations.webhooks.graph_lifecycle_hooks.on_graph_activate(
# graph,
# get_credentials=lambda id: integration_creds_manager.get(user_id, id),
# )
# )
await backend.server.v2.library.db.add_agent_to_library(store_listing_version_id=store_listing_version_id,user_id=user_id)
return fastapi.Response(status_code=201)

View File

@ -19,6 +19,7 @@ const nextConfig = {
typescript: {
ignoreBuildErrors: true,
},
transpilePackages: ["geist"],
};
export default withSentryConfig(nextConfig, {

View File

@ -62,6 +62,7 @@
"framer-motion": "^11.15.0",
"geist": "^1.3.1",
"launchdarkly-react-client-sdk": "^3.6.0",
"lodash.debounce": "^4.0.8",
"lucide-react": "^0.468.0",
"moment": "^2.30.1",
"next": "^14.2.13",
@ -69,6 +70,7 @@
"react": "^18",
"react-day-picker": "^9.4.4",
"react-dom": "^18",
"react-drag-drop-files": "^2.4.0",
"react-hook-form": "^7.54.0",
"react-icons": "^5.4.0",
"react-markdown": "^9.0.1",
@ -93,6 +95,7 @@
"@storybook/react": "^8.3.5",
"@storybook/test": "^8.3.5",
"@storybook/test-runner": "^0.20.1",
"@types/lodash": "^4.17.13",
"@types/negotiator": "^0.6.3",
"@types/node": "^22.9.0",
"@types/react": "^18",

View File

@ -1,13 +1,34 @@
"use client";
import LibraryActionHeader from "@/components/agptui/composite/LibraryActionHeader";
import LibraryAgentListContainer from "@/components/agptui/composite/LibraryAgentListContainer";
import { GraphMeta } from "@/lib/autogpt-server-api";
import { useState } from "react";
/**
* LibraryPage Component
* Main component that manages the library interface including agent listing and actions
*/
const LibraryPage = () => {
const [agents, setAgents] = useState<GraphMeta[]>([]);
const [agentLoading, setAgentLoading] = useState<boolean>(true);
return (
<main className="p-4">
{/* Top section - includes notification, search and uploading mechansim */}
<LibraryActionHeader />
<main className="mx-auto w-screen max-w-[1600px] space-y-[16px] bg-neutral-50 p-4 px-2 dark:bg-neutral-900 sm:px-8 md:px-12">
{/* Header section containing notifications, search functionality, agent count, filters and upload mechanism */}
<LibraryActionHeader
numberOfAgents={agents.length}
setAgents={setAgents}
setAgentLoading={setAgentLoading}
/>
{/* Last section for Agent Lists, Agent counter and filter */}
<div></div>
{/* Content section displaying agent list with counter and filtering options */}
<LibraryAgentListContainer
setAgents={setAgents}
agents={agents}
agentLoading={agentLoading}
setAgentLoading={setAgentLoading}
/>
</main>
);
};

View File

@ -125,3 +125,16 @@
scrollbar-width: thin; /* For Firefox (sets a thin scrollbar) */
scrollbar-color: transparent transparent; /* For Firefox (thumb and track colors) */
}
body {
overflow-x: hidden;
}
.drop-style {
border: dashed 2px #a3a3a3 !important;
border-radius: 20px;
}
.drop-style:hover {
border: dashed 2px #525252 !important;
}

View File

@ -4,6 +4,8 @@ import { Inter, Poppins } from "next/font/google";
import { Providers } from "@/app/providers";
import { cn } from "@/lib/utils";
import { Navbar } from "@/components/agptui/Navbar";
import { GeistSans } from "geist/font/sans";
import { GeistMono } from "geist/font/mono";
import "./globals.css";
import TallyPopupSimple from "@/components/TallyPopup";
@ -11,7 +13,7 @@ import { GoogleAnalytics } from "@next/third-parties/google";
import { Toaster } from "@/components/ui/toaster";
import { IconType } from "@/components/ui/icons";
const inter = Inter({ subsets: ["latin"] });
const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
const poppins = Poppins({
subsets: ["latin"],
@ -30,12 +32,14 @@ export default async function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="en">
<html
lang="en"
className={`${GeistSans.variable} ${GeistMono.variable} ${poppins.variable} ${inter.variable}`}
>
<body
className={cn(
"antialiased transition-colors",
"bg-neutral-50 antialiased transition-colors",
inter.className,
poppins.className,
)}
>
<Providers

View File

@ -38,7 +38,7 @@ const Monitor = () => {
const fetchAgents = useCallback(() => {
api.listLibraryAgents().then((agent) => {
setFlows(agent);
setFlows(agent.agents);
});
api.getExecutions().then((executions) => {
setExecutions(executions);

View File

@ -21,8 +21,8 @@ const buttonVariants = cva(
"hover:bg-neutral-100 text-[#272727] dark:text-neutral-100 dark:hover:bg-neutral-700",
link: "text-[#272727] underline-offset-4 hover:underline dark:text-neutral-100",
library_outline:
"rounded-[52px] hover:bg-[#262626] border border-zinc-700 hover:text-white",
library_primary: "rounded-[52px] bg-[#262626] text-white",
"rounded-[52px] hover:bg-[#262626] border border-zinc-700 hover:text-white font-sans",
library_primary: "rounded-[52px] bg-[#262626] text-white font-sans",
},
size: {
default:

View File

@ -0,0 +1,52 @@
import { cn } from "@/lib/utils";
import Link from "next/link";
import { GraphMeta } from "@/lib/autogpt-server-api";
export const LibraryAgentCard = ({ id, name, isCreatedByUser }: GraphMeta) => {
return (
<div
className={cn(
"flex h-[158px] flex-col rounded-[14px] border border-[#E5E5E5] bg-white p-5 transition-all duration-300 ease-in-out hover:scale-[1.02]",
!isCreatedByUser && "shadow-[0_-5px_0_0_rgb(196_181_253)]",
)}
>
<div className="flex flex-1">
<h3 className="font-inter flex-1 text-[18px] font-semibold leading-4">
{name}
</h3>
{/* <span
className={cn(
"h-[14px] w-[14px] rounded-full",
status == "Nothing running" && "bg-[#64748B]",
status == "healthy" && "bg-[#22C55E]",
status == "something wrong" && "bg-[#EF4444]",
status == "waiting for trigger" && "bg-[#FBBF24]",
)}
></span> */}
</div>
<div className="flex items-center justify-between">
<div className="mt-6 flex gap-3">
<Link
href={`/agents/${id}`}
className="font-inter text-[14px] font-[700] leading-[24px] text-neutral-800 hover:underline"
>
See runs
</Link>
<Link
href={`/build?flowID=${id}`}
className="font-inter text-[14px] font-[700] leading-[24px] text-neutral-800 hover:underline"
>
Open in builder
</Link>
</div>
{/* {output && (
<div className="h-[24px] w-fit rounded-[45px] bg-neutral-600 px-[9px] py-[2px] font-sans text-[12px] font-[700] text-neutral-50">
New output
</div>
)} */}
</div>
</div>
);
};

View File

@ -0,0 +1,68 @@
import { GraphMeta } from "@/lib/autogpt-server-api";
import { Dispatch, SetStateAction } from "react";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "../ui/select";
import { Filter } from "lucide-react";
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
type SortValue =
| "most_recent"
| "highest_runtime"
| "most_runs"
| "alphabetical"
| "last_modified";
const LibraryAgentFilter = ({
setAgents,
setAgentLoading,
}: {
setAgents: Dispatch<SetStateAction<GraphMeta[]>>;
setAgentLoading: Dispatch<SetStateAction<boolean>>;
}) => {
const api = useBackendAPI();
const handleSortChange = async (value: SortValue) => {
setAgentLoading(true);
await new Promise((resolve) => setTimeout(resolve, 1000));
let response = await api.librarySearchAgent("", undefined, value);
setAgents(response.agents);
setAgentLoading(false);
};
return (
<div className="flex items-center">
<span className="hidden sm:inline">sort by</span>
<Select onValueChange={handleSortChange}>
<SelectTrigger className="ml-1 w-fit space-x-1 border-none pl-2 shadow-md">
<Filter className="h-4 w-4 sm:hidden" />
<SelectValue
placeholder="most Recent"
className={
"font-sans text-[14px] font-[500] leading-[24px] text-neutral-600"
}
/>
</SelectTrigger>
<SelectContent>
<SelectGroup
className={
"font-sans text-[14px] font-[500] leading-[24px] text-neutral-600"
}
>
<SelectItem value="most_recent">Most Recent</SelectItem>
<SelectItem value="highest_runtime">Highest Runtime</SelectItem>
<SelectItem value="most_runs">Most Runs</SelectItem>
<SelectItem value="alphabetical">Alphabetical</SelectItem>
<SelectItem value="last_modified">Last Modified</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
);
};
export default LibraryAgentFilter;

View File

@ -0,0 +1,183 @@
import { Button } from "../ui/button";
import Image from "next/image";
import { Separator } from "../ui/separator";
import {
CirclePlayIcon,
ClipboardCopy,
ImageIcon,
Play,
PlayCircle,
Share2,
X,
} from "lucide-react";
import { Dispatch, SetStateAction } from "react";
export interface NotificationCardData {
type: "text" | "image" | "video" | "audio";
title: string;
id: string;
content?: string;
mediaUrl?: string;
}
interface NotificationCardProps extends NotificationCardData {
setNotifications: Dispatch<SetStateAction<NotificationCardData[] | null>>;
}
const NotificationCard = ({
type,
title,
id,
content,
mediaUrl,
setNotifications,
}: NotificationCardProps) => {
const barHeights = Array.from({ length: 60 }, () =>
Math.floor(Math.random() * (34 - 20 + 1) + 20),
);
const handleClose = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
setNotifications((prev) => {
if (!prev) return null;
return prev.filter((notification) => notification.id !== id);
});
};
return (
<div className="w-[430px] space-y-[22px] rounded-[14px] border border-neutral-100 bg-neutral-50 p-[16px] pt-[12px]">
<div className="flex items-center justify-between">
{/* count */}
<div className="flex items-center gap-[10px]">
<p className="font-sans text-[12px] font-medium text-neutral-500">
1/4
</p>
<p className="h-[26px] rounded-[45px] bg-green-100 px-[9px] py-[3px] font-sans text-[12px] font-medium text-green-800">
Success
</p>
</div>
{/* cross icon */}
<Button
variant="ghost"
className="p-0 hover:bg-transparent"
onClick={handleClose}
>
<X
className="h-6 w-6 text-[#020617] hover:scale-105"
strokeWidth={1.25}
/>
</Button>
</div>
<div className="space-y-[6px] p-0">
<p className="font-sans text-[14px] font-medium leading-[20px] text-neutral-500">
New Output Ready!
</p>
<h2 className="font-poppin text-[20px] font-medium leading-7 text-neutral-800">
{title}
</h2>
{type === "text" && <Separator />}
</div>
<div className="p-0">
{type === "text" && (
// Maybe in future we give markdown support
<div className="mt-[-8px] line-clamp-6 font-sans text-sm font-[400px] text-neutral-600">
{content}
</div>
)}
{type === "image" &&
(mediaUrl ? (
<div className="relative h-[200px] w-full">
<Image
src={mediaUrl}
alt={title}
fill
className="rounded-lg object-cover"
/>
</div>
) : (
<div className="flex h-[244px] w-full items-center justify-center rounded-lg bg-[#D9D9D9]">
<ImageIcon
className="h-[138px] w-[138px] text-neutral-400"
strokeWidth={1}
/>
</div>
))}
{type === "video" && (
<div className="space-y-4">
{mediaUrl ? (
<video src={mediaUrl} controls className="w-full rounded-lg" />
) : (
<div className="flex h-[219px] w-[398px] items-center justify-center rounded-lg bg-[#D9D9D9]">
<PlayCircle
className="h-16 w-16 text-neutral-500"
strokeWidth={1}
/>
</div>
)}
</div>
)}
{type === "audio" && (
<div className="flex gap-2">
<CirclePlayIcon
className="h-10 w-10 rounded-full bg-neutral-800 text-white"
strokeWidth={1}
/>
<div className="flex flex-1 items-center justify-between">
{/* <audio src={mediaUrl} controls className="w-full" /> */}
{barHeights.map((h, i) => {
return (
<div
key={i}
className={`rounded-[8px] bg-neutral-500`}
style={{
height: `${h}px`,
width: "3px",
}}
/>
);
})}
</div>
</div>
)}
</div>
<div className="flex justify-between gap-2 p-0">
<div className="space-x-3">
<Button
variant="outline"
onClick={() => {
navigator.share({
title,
text: content,
url: mediaUrl,
});
}}
className="h-10 w-10 rounded-full border-neutral-800 p-0"
>
<Share2 className="h-5 w-5" strokeWidth={1} />
</Button>
<Button
variant="outline"
onClick={() =>
navigator.clipboard.writeText(content || mediaUrl || "")
}
className="h-10 w-10 rounded-full border-neutral-800 p-0"
>
<ClipboardCopy className="h-5 w-5" strokeWidth={1} />
</Button>
</div>
<Button className="h-[40px] rounded-[52px] bg-neutral-800 px-4 py-2">
See run
</Button>
</div>
</div>
);
};
export default NotificationCard;

View File

@ -4,25 +4,63 @@ import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Button } from "./Button";
import { BellIcon, X } from "lucide-react";
import { motion, useAnimationControls } from "framer-motion";
import { useState } from "react";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "../ui/card";
import { motion, useAnimationControls, useScroll } from "framer-motion";
import { useState, useEffect } from "react";
import LibraryNotificationCard, {
NotificationCardData,
} from "./LibraryNotificationCard";
import { cn } from "@/lib/utils";
export const LibraryNotificationDropdown = () => {
const controls = useAnimationControls();
const [open, setOpen] = useState(false);
const [notifications, setNotifications] = useState<
NotificationCardData[] | null
>(null);
const { scrollY } = useScroll();
const [scrollPosition, setScrollPosition] = useState(0);
useEffect(() => {
const unsubscribe = scrollY.onChange((currentY) => {
setScrollPosition(currentY);
});
return () => unsubscribe();
}, [scrollY]);
const initialNotificationData = [
{
type: "audio" as "audio",
title: "Audio Processing Complete",
id: "4",
},
{
type: "text" as "text",
title: "LinkedIn Post Generator: YouTube to Professional Content",
id: "1",
content:
"As artificial intelligence (AI) continues to evolve, it's increasingly clear that AI isn't just a trend—it's reshaping the way we work, innovate, and solve complex problems. However, for many professionals, the question remains: How can I leverage AI to drive meaningful results in my own field? In this article, we'll explore how AI can empower businesses and individuals alike to be more efficient, make better decisions, and unlock new opportunities. Whether you're in tech, finance, healthcare, or any other industry, understanding the potential of AI can set you apart.",
},
{
type: "image" as "image",
title: "New Image Upload",
id: "2",
},
{
type: "video" as "video",
title: "Video Processing Complete",
id: "3",
},
] as NotificationCardData[];
useEffect(() => {
if (initialNotificationData) {
setNotifications(initialNotificationData);
}
}, []);
const handleHoverStart = () => {
controls.start({
@ -30,25 +68,49 @@ export const LibraryNotificationDropdown = () => {
transition: { duration: 0.5 },
});
};
return (
<DropdownMenu open={open} onOpenChange={setOpen}>
<DropdownMenuTrigger asChild>
<DropdownMenuTrigger className="sm:flex-1" asChild>
<Button
variant={open ? "library_primary" : "library_outline"}
size="library"
onMouseEnter={handleHoverStart}
onMouseLeave={handleHoverStart}
className="w-[161px]"
className={cn(
"z-50 max-w-[161px] transition-all duration-200 ease-in-out",
scrollY.get() > 30 ? "w-fit max-w-fit" : "w-fit sm:w-[161px]",
)}
>
<motion.div animate={controls}>
<BellIcon className="mr-2 h-5 w-5" strokeWidth={2} />
<BellIcon
className={cn(
"h-5 w-5 transition-all duration-200 ease-in-out",
scrollY.get() <= 30 && "sm:mr-2",
)}
strokeWidth={2}
/>
</motion.div>
Your updates
<span className="ml-2 text-[14px]">2</span>
{scrollY.get() <= 30 && (
<motion.div
initial={{ opacity: 1 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="hidden items-center transition-opacity duration-300 sm:inline-flex"
>
Your updates
<span className="ml-2 text-[14px]">
{notifications?.length || 0}
</span>
</motion.div>
)}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="scroll-none relative left-[16px] h-[80vh] w-fit overflow-y-auto rounded-[26px] bg-[#a1a1aa]/60 p-5">
<DropdownMenuLabel className="mb-4 font-sans text-[18px] text-white">
<DropdownMenuContent
sideOffset={22}
className="scroll-none relative left-[16px] h-[80vh] w-fit overflow-y-auto rounded-[26px] bg-[#C5C5CA] p-5"
>
<DropdownMenuLabel className="z-10 mb-4 font-sans text-[18px] text-white">
Agent run updates
</DropdownMenuLabel>
<button
@ -57,53 +119,23 @@ export const LibraryNotificationDropdown = () => {
>
<X className="h-6 w-6 text-white hover:text-white/60" />
</button>
<DropdownMenuItem>
<LibraryNotificationCard />
</DropdownMenuItem>
<DropdownMenuItem>
<LibraryNotificationCard />
</DropdownMenuItem>
<DropdownMenuItem>
<LibraryNotificationCard />
</DropdownMenuItem>
<div className="space-y-[12px]">
{notifications && notifications.length ? (
notifications.map((notification) => (
<DropdownMenuItem key={notification.id} className="p-0">
<LibraryNotificationCard
{...notification}
setNotifications={setNotifications}
/>
</DropdownMenuItem>
))
) : (
<div className="w-[464px] py-4 text-center text-white">
No notifications present
</div>
)}
</div>
</DropdownMenuContent>
</DropdownMenu>
);
};
const LibraryNotificationCard = () => {
return (
<Card className="w-[424px] rounded-[14px] border border-neutral-100 p-[16px] pt-[12px]">
<CardHeader>
<CardTitle>Latest Agent Updates</CardTitle>
<CardDescription>View your latest workflow changes</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex items-center gap-4">
<div className="h-10 w-10 rounded-full bg-neutral-100" />
<div>
<p className="font-medium">Agent Run #1234</p>
<p className="text-sm text-neutral-500">Updated 2 hours ago</p>
</div>
</div>
<div className="flex items-center gap-4">
<div className="h-10 w-10 rounded-full bg-neutral-100" />
<div>
<p className="font-medium">Workflow Changes</p>
<p className="text-sm text-neutral-500">3 new changes detected</p>
</div>
</div>
</div>
</CardContent>
<CardFooter>
<Button variant="outline" className="w-full">
View All Updates
</Button>
</CardFooter>
</Card>
);
};

View File

@ -0,0 +1,104 @@
"use client";
import { Search, X } from "lucide-react";
import { Input } from "../ui/input";
import { Dispatch, SetStateAction, useRef, useState, useCallback } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { GraphMeta } from "@/lib/autogpt-server-api";
import debounce from "lodash/debounce";
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
export const LibrarySearchBar = ({
setAgents,
setAgentLoading,
}: {
setAgents: Dispatch<SetStateAction<GraphMeta[]>>;
setAgentLoading: Dispatch<SetStateAction<boolean>>;
}) => {
const inputRef = useRef<HTMLInputElement>(null);
const [isFocused, setIsFocused] = useState(false);
const api = useBackendAPI();
const debouncedSearch = useCallback(
debounce(async (searchTerm: string) => {
try {
setAgentLoading(true);
await new Promise((resolve) => setTimeout(resolve, 1000));
const response = await api.librarySearchAgent(searchTerm);
setAgents(response.agents);
setAgentLoading(false);
} catch (error) {
console.error("Search failed:", error);
}
}, 300),
[setAgents],
);
const handleSearchInput = (e: React.ChangeEvent<HTMLInputElement>) => {
const searchTerm = e.target.value;
debouncedSearch(searchTerm);
};
return (
<div
onClick={() => inputRef.current?.focus()}
className="relative z-[21] mx-auto flex h-[50px] w-full max-w-[500px] flex-1 cursor-pointer items-center rounded-[45px] bg-[#EDEDED] px-[24px] py-[10px]"
>
<div className="w-[30px] overflow-hidden">
<AnimatePresence mode="wait">
{!isFocused ? (
<motion.div
key="search"
initial={{ x: -50 }}
animate={{ x: 0 }}
exit={{ x: -50 }}
transition={{
duration: 0.2,
ease: "easeInOut",
}}
>
<Search
className="h-[29px] w-[29px] text-neutral-900"
strokeWidth={1.25}
/>
</motion.div>
) : (
<motion.div
key="close"
initial={{ x: 50 }}
animate={{ x: 0 }}
exit={{ x: 50 }}
transition={{
duration: 0.2,
ease: "easeInOut",
}}
>
<X
className="h-[29px] w-[29px] cursor-pointer text-neutral-900"
strokeWidth={1.25}
onClick={(e) => {
if (inputRef.current) {
debouncedSearch("");
inputRef.current.value = "";
inputRef.current.blur();
e.preventDefault();
}
setIsFocused(false);
}}
/>
</motion.div>
)}
</AnimatePresence>
</div>
<Input
ref={inputRef}
onFocus={() => setIsFocused(true)}
onBlur={() => !inputRef.current?.value && setIsFocused(false)}
onChange={handleSearchInput}
className="border-none font-sans text-[16px] font-normal leading-7 shadow-none focus:shadow-none"
type="text"
placeholder="Search agents"
/>
</div>
);
};

View File

@ -0,0 +1,168 @@
"use client";
import { Upload, X } from "lucide-react";
import { Button } from "./Button";
import { useEffect, useState } from "react";
import { motion, useAnimation } from "framer-motion";
import { cn } from "@/lib/utils";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "../ui/dialog";
import { Input } from "../ui/input";
import { FileUploader } from "react-drag-drop-files";
const fileTypes = ["JSON"];
export const LibraryUploadAgent = () => {
const [scrolled, setScrolled] = useState(false);
const [file, setFile] = useState<File | null>(null);
const [isDroped, setisDroped] = useState(false);
const controls = useAnimation();
const handleChange = (file: File) => {
setTimeout(() => {
setisDroped(false);
}, 2000);
setFile(file);
setisDroped(false);
};
useEffect(() => {
const handleScroll = () => {
if (window.scrollY > 30) {
setScrolled(true);
} else {
setScrolled(false);
}
};
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
const handleUpload = () => {
// Add upload logic here
if (file) {
console.log("Uploading file:", file);
}
};
return (
<Dialog>
<DialogTrigger asChild>
<Button
variant="library_primary"
size="library"
className={cn(
"max-w-[177px] transition-all duration-200 ease-in-out",
scrolled ? "w-fit max-w-fit" : "w-fit sm:w-[177px]",
)}
>
<motion.div animate={controls}>
<Upload
className={cn(
"h-5 w-5 transition-all duration-200 ease-in-out",
!scrolled && "sm:mr-2",
)}
/>
</motion.div>
{!scrolled && (
<motion.div
initial={{ opacity: 1 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="hidden items-center transition-opacity duration-300 sm:inline-flex"
>
Upload an agent
</motion.div>
)}
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle className="mb-8 text-center">Upload Agent </DialogTitle>
</DialogHeader>
<div className="relative flex flex-col gap-4">
<Input placeholder="Agent name" className="w-full rounded-[10px]" />
<Input placeholder="Description" className="w-full rounded-[10px]" />
{file ? (
<div className="flex rounded-[10px] border p-2 font-sans text-sm font-medium text-[#525252] outline-none">
<span className="line-clamp-1">{file.name}</span>
<Button
onClick={() => setFile(null)}
className="absolute left-[-10px] top-[-16px] mt-2 h-fit border-none bg-red-200 p-1"
size="library"
>
<X
className="m-0 h-[12px] w-[12px] text-red-600"
strokeWidth={3}
/>
</Button>
</div>
) : (
<FileUploader
handleChange={handleChange}
name="file"
types={fileTypes}
label={"Upload your agent here..!!"}
uploadedLabel={"Uploading Successful"}
required={true}
hoverTitle={"Drop your agent here...!!"}
maxSize={10}
classes={"drop-style"}
onDrop={() => {
setisDroped(true);
}}
onSelect={() => {
setisDroped(true);
}}
children={
<div
style={{
minHeight: "150px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
outline: "none",
fontFamily: "var(--font-geist-sans)",
color: "#525252",
fontSize: "14px",
fontWeight: "500",
}}
>
{isDroped ? (
<div className="flex items-center justify-center py-4">
<div className="h-8 w-8 animate-spin rounded-full border-b-2 border-t-2 border-neutral-800"></div>
</div>
) : (
<>
<span>Drop your agent here</span>
<span>or</span>
<span>Click to upload</span>
</>
)}
</div>
}
/>
)}
<Button
onClick={handleUpload}
variant="library_primary"
size="library"
className="mt-2 self-end"
disabled={!file}
>
Upload Agent
</Button>
</div>
</DialogContent>
</Dialog>
);
};

View File

@ -57,7 +57,7 @@ export const Navbar = async ({ links, menuItemGroups }: NavbarProps) => {
return (
<>
<nav className="sticky top-0 z-50 mx-[16px] hidden h-16 w-full max-w-[1600px] items-center justify-between rounded-bl-2xl rounded-br-2xl border border-white/50 bg-white/5 py-3 pl-6 pr-3 backdrop-blur-[26px] dark:border-gray-700 dark:bg-gray-900 md:inline-flex">
<nav className="sticky top-0 z-50 mx-[16px] hidden h-16 w-full max-w-[1600px] items-center justify-between rounded-bl-2xl rounded-br-2xl border border-white/50 bg-neutral-50 py-3 pl-6 pr-3 backdrop-blur-[26px] dark:border-gray-700 dark:bg-gray-900 md:inline-flex">
<div className="flex items-center gap-11">
<div className="relative h-10 w-[88.87px]">
<IconAutoGPTLogo className="h-full w-full" />

View File

@ -1,26 +1,140 @@
"use client";
import { LibrarySearchBar } from "@/components/agptui/LibrarySearchBar";
import { LibraryNotificationDropdown } from "../LibraryNotificationDropdown";
import { LibraryUploadAgent } from "../LibraryUploadAgent";
import { motion, useScroll, useTransform } from "framer-motion";
import { cn } from "@/lib/utils";
import {
Dispatch,
SetStateAction,
useEffect,
useState,
useCallback,
} from "react";
import { GraphMeta } from "@/lib/autogpt-server-api";
import LibraryAgentFilter from "../LibraryAgentFilter";
const LibraryActionHeader: React.FC = () => {
return (
<div className="flex w-screen items-center justify-between px-4 pt-6">
<LibraryNotificationDropdown />
<LibrarySearchBar />
<LibraryUploadAgent />
</div>
interface LibraryActionHeaderProps {
setAgents: Dispatch<SetStateAction<GraphMeta[]>>;
setAgentLoading: Dispatch<SetStateAction<boolean>>;
numberOfAgents: number;
}
// Constants for header animation behavior
const SCROLL_THRESHOLD = 30;
const INITIAL_HEIGHT = 100;
const COLLAPSED_HEIGHT = 50;
const TRANSITION_DURATION = 0.3;
/**
* LibraryActionHeader component - Renders a sticky header with search, notifications and filters
* Animates and collapses based on scroll position
*/
const LibraryActionHeader: React.FC<LibraryActionHeaderProps> = ({
setAgents,
setAgentLoading,
numberOfAgents,
}) => {
const { scrollY } = useScroll();
const [scrollPosition, setScrollPosition] = useState(0);
const height = useTransform(
scrollY,
[0, 100],
[INITIAL_HEIGHT, COLLAPSED_HEIGHT],
);
};
const LibrarySearchBar = () => {
const handleScroll = useCallback((currentY: number) => {
setScrollPosition(currentY);
}, []);
useEffect(() => {
const unsubscribe = scrollY.on("change", handleScroll);
return () => unsubscribe();
}, [scrollY, handleScroll]);
// Calculate animation offsets based on scroll position
const getScrollAnimation = (offsetX: number, offsetY: number) => ({
x: scrollPosition > SCROLL_THRESHOLD ? offsetX : 0,
y: scrollPosition > SCROLL_THRESHOLD ? offsetY : 0,
});
return (
<div>
SearchBar
{/* Search bar content */}
</div>
);
};
<>
<div className="sticky top-16 z-[10] hidden items-start justify-between bg-neutral-50 pb-4 md:flex">
<motion.div
className={cn("relative flex-1 space-y-[32px]")}
style={{ height }}
transition={{ duration: TRANSITION_DURATION }}
>
<LibraryNotificationDropdown />
const LibraryUploadAgent = () => {
return <div>Uploading Agent</div>;
<motion.div
className="flex items-center gap-[10px] p-2"
animate={getScrollAnimation(60, -76)}
>
<span className="w-[96px] font-poppin text-[18px] font-semibold leading-[28px] text-neutral-800">
My agents
</span>
<span className="w-[70px] font-sans text-[14px] font-normal leading-6">
{numberOfAgents} agents
</span>
</motion.div>
</motion.div>
<LibrarySearchBar
setAgents={setAgents}
setAgentLoading={setAgentLoading}
/>
<motion.div
className="flex flex-1 flex-col items-end space-y-[32px]"
style={{ height }}
transition={{ duration: TRANSITION_DURATION }}
>
<LibraryUploadAgent />
<motion.div
className="flex items-center gap-[10px] pl-2 pr-2 font-sans text-[14px] font-[500] leading-[24px] text-neutral-600"
animate={getScrollAnimation(-60, -68)}
>
<LibraryAgentFilter
setAgents={setAgents}
setAgentLoading={setAgentLoading}
/>
</motion.div>
</motion.div>
</div>
{/* Mobile and tablet */}
<div className="flex flex-col gap-4 bg-neutral-50 p-4 pt-[52px] md:hidden">
<div className="flex w-full justify-between">
<LibraryNotificationDropdown />
<LibraryUploadAgent />
</div>
<div className="flex items-center justify-center">
<LibrarySearchBar
setAgents={setAgents}
setAgentLoading={setAgentLoading}
/>
</div>
<div className="flex w-full justify-between">
<div className="flex items-center gap-2">
<span className="font-poppin text-[18px] font-semibold leading-[28px] text-neutral-800">
My agents
</span>
<span className="font-sans text-[14px] font-normal leading-6">
{numberOfAgents} agents
</span>
</div>
<LibraryAgentFilter
setAgents={setAgents}
setAgentLoading={setAgentLoading}
/>
</div>
</div>
</>
);
};
export default LibraryActionHeader;

View File

@ -0,0 +1,120 @@
"use client";
import {
useEffect,
useState,
useCallback,
Dispatch,
SetStateAction,
} from "react";
import { LibraryAgentCard } from "../LibraryAgentCard";
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
import { GraphMeta } from "@/lib/autogpt-server-api";
import { useThreshold } from "@/hooks/useThreshold";
interface LibraryAgentListContainerProps {
setAgents: Dispatch<SetStateAction<GraphMeta[]>>;
agents: GraphMeta[];
setAgentLoading: Dispatch<SetStateAction<boolean>>;
agentLoading: boolean;
}
export type AgentStatus =
| "healthy"
| "something wrong"
| "waiting for trigger"
| "Nothing running";
/**
* LibraryAgentListContainer is a React component that displays a grid of library agents with infinite scroll functionality.
*/
const LibraryAgentListContainer: React.FC<LibraryAgentListContainerProps> = ({
setAgents,
agents,
setAgentLoading,
agentLoading,
}) => {
const [nextToken, setNextToken] = useState<string | null>(null);
const [loadingMore, setLoadingMore] = useState(false);
const api = useBackendAPI();
const fetchAgents = useCallback(
async (paginationToken?: string) => {
try {
const response = await api.listLibraryAgents(paginationToken);
setAgents((prevAgents) =>
paginationToken
? [...prevAgents, ...response.agents]
: response.agents,
);
setNextToken(response.next_token);
} finally {
setAgentLoading(false);
setLoadingMore(false);
}
},
[api, setAgents, setAgentLoading],
);
useEffect(() => {
fetchAgents();
}, [fetchAgents]);
const handleInfiniteScroll = useCallback(
(scrollY: number) => {
if (!nextToken || loadingMore) return;
const { scrollHeight, clientHeight } = document.documentElement;
const SCROLL_THRESHOLD = 20;
const FETCH_DELAY = 1000;
if (scrollY + clientHeight >= scrollHeight - SCROLL_THRESHOLD) {
setLoadingMore(true);
setTimeout(() => fetchAgents(nextToken), FETCH_DELAY);
}
},
[nextToken, loadingMore, fetchAgents],
);
useThreshold(handleInfiniteScroll, 50);
const LoadingSpinner = () => (
<div className="h-8 w-8 animate-spin rounded-full border-b-2 border-t-2 border-neutral-800" />
);
return (
<div className="space-y-[10px] p-2">
{agentLoading ? (
<div className="flex h-[200px] items-center justify-center">
<LoadingSpinner />
</div>
) : (
<>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3">
{agents?.map((agent) => (
<LibraryAgentCard
key={agent.id}
id={agent.id}
name={agent.name}
isCreatedByUser={agent.isCreatedByUser}
input_schema={agent.input_schema}
output_schema={agent.output_schema}
is_active={agent.is_active}
version={agent.version}
description={agent.description}
/>
))}
</div>
{loadingMore && (
<div className="flex items-center justify-center py-4">
<LoadingSpinner />
</div>
)}
</>
)}
</div>
);
};
export default LibraryAgentListContainer;

View File

@ -0,0 +1,37 @@
import { useEffect, useState } from "react";
interface ThresholdCallback<T> {
(value: T): void;
}
export const useThreshold = <T>(
callback: ThresholdCallback<T>,
threshold: number,
): boolean => {
const [prevValue, setPrevValue] = useState<T | null>(null);
const [isThresholdMet, setIsThresholdMet] = useState(false);
useEffect(() => {
const handleScroll = () => {
const { scrollY } = window;
if (scrollY >= threshold) {
setIsThresholdMet(true);
} else {
setIsThresholdMet(false);
}
if (scrollY >= threshold && (!prevValue || prevValue !== scrollY)) {
callback(scrollY as T);
setPrevValue(scrollY as T);
}
};
window.addEventListener("scroll", handleScroll);
handleScroll();
return () => window.removeEventListener("scroll", handleScroll);
}, [callback, threshold, prevValue]);
return isThresholdMet;
};

View File

@ -311,7 +311,6 @@ export default class BackendAPI {
"/store/submissions/generate_image?agent_id=" + agent_id,
);
}
c;
deleteStoreSubmission(submission_id: string): Promise<boolean> {
return this._request("DELETE", `/store/submissions/${submission_id}`);
}
@ -352,11 +351,34 @@ export default class BackendAPI {
/////////// V2 LIBRARY API //////////////
/////////////////////////////////////////
async listLibraryAgents(): Promise<GraphMeta[]> {
return this._get("/library/agents");
async listLibraryAgents(
paginationToken?: string,
): Promise<{ agents: GraphMeta[]; next_token: string | null }> {
return this._get(
"/library/agents",
paginationToken ? { pagination_token: paginationToken } : undefined,
);
}
async librarySearchAgent(
search: string,
paginationToken?: string,
filter?:
| "most_recent"
| "highest_runtime"
| "most_runs"
| "alphabetical"
| "last_modified",
): Promise<{ agents: GraphMeta[]; next_token: string | null }> {
return this._get("/library/agents/search", {
search_term: search,
...(paginationToken && { pagination_token: paginationToken }),
...(filter && { sort_by: filter }),
});
}
async addAgentToLibrary(storeListingVersionId: string): Promise<void> {
console.log("Adding to the library");
await this._request("POST", `/library/agents/${storeListingVersionId}`);
}

View File

@ -206,6 +206,7 @@ export type GraphMeta = {
version: number;
is_active: boolean;
name: string;
isCreatedByUser?: boolean;
description: string;
input_schema: BlockIOObjectSubSchema;
output_schema: BlockIOObjectSubSchema;

View File

@ -19,6 +19,7 @@ const config = {
// Include the custom font family
neue: ['"PP Neue Montreal TT"', "sans-serif"],
poppin: ["var(--font-poppins)"],
inter: ["var(--font-inter)"],
},
colors: {
border: "hsl(var(--border))",

View File

@ -1041,6 +1041,23 @@
dependencies:
tslib "^2.4.0"
"@emotion/is-prop-valid@1.2.2":
version "1.2.2"
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz#d4175076679c6a26faa92b03bb786f9e52612337"
integrity sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==
dependencies:
"@emotion/memoize" "^0.8.1"
"@emotion/memoize@^0.8.1":
version "0.8.1"
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17"
integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==
"@emotion/unitless@0.8.1":
version "0.8.1"
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.1.tgz#182b5a4704ef8ad91bde93f7a860a88fd92c79a3"
integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==
"@esbuild/aix-ppc64@0.24.0":
version "0.24.0"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz#b57697945b50e99007b4c2521507dc613d4a648c"
@ -3685,6 +3702,11 @@
resolved "https://registry.yarnpkg.com/@types/junit-report-builder/-/junit-report-builder-3.0.2.tgz#17cc131d14ceff59dcf14e5847bd971b96f2cbe0"
integrity sha512-R5M+SYhMbwBeQcNXYWNCZkl09vkVfAtcPIaCGdzIkkbeaTrVbGQ7HVgi4s+EmM/M1K4ZuWQH0jGcvMvNePfxYA==
"@types/lodash@^4.17.13":
version "4.17.13"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.13.tgz#786e2d67cfd95e32862143abe7463a7f90c300eb"
integrity sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==
"@types/mdast@^4.0.0":
version "4.0.4"
resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-4.0.4.tgz#7ccf72edd2f1aa7dd3437e180c64373585804dd6"
@ -3813,6 +3835,11 @@
resolved "https://registry.yarnpkg.com/@types/statuses/-/statuses-2.0.5.tgz#f61ab46d5352fd73c863a1ea4e1cef3b0b51ae63"
integrity sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==
"@types/stylis@4.2.5":
version "4.2.5"
resolved "https://registry.yarnpkg.com/@types/stylis/-/stylis-4.2.5.tgz#1daa6456f40959d06157698a653a9ab0a70281df"
integrity sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==
"@types/tedious@^4.0.14":
version "4.0.14"
resolved "https://registry.yarnpkg.com/@types/tedious/-/tedious-4.0.14.tgz#868118e7a67808258c05158e9cad89ca58a2aec1"
@ -4884,6 +4911,11 @@ camelcase@^6.2.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
camelize@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.1.tgz#89b7e16884056331a35d6b5ad064332c91daa6c3"
integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==
caniuse-lite@^1.0.30001579, caniuse-lite@^1.0.30001669:
version "1.0.30001688"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001688.tgz#f9d3ede749f083ce0db4c13db9d828adaf2e8d0a"
@ -5344,6 +5376,11 @@ crypto-browserify@^3.12.0:
randombytes "^2.1.0"
randomfill "^1.0.4"
css-color-keywords@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05"
integrity sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==
css-loader@^6.7.1, css-loader@^6.7.3:
version "6.11.0"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.11.0.tgz#33bae3bf6363d0a7c2cf9031c96c744ff54d85ba"
@ -5369,6 +5406,15 @@ css-select@^4.1.3:
domutils "^2.8.0"
nth-check "^2.0.1"
css-to-react-native@3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-3.2.0.tgz#cdd8099f71024e149e4f6fe17a7d46ecd55f1e32"
integrity sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==
dependencies:
camelize "^1.0.0"
css-color-keywords "^1.0.0"
postcss-value-parser "^4.0.2"
css-what@^6.0.1:
version "6.1.0"
resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4"
@ -5384,7 +5430,7 @@ cssesc@^3.0.0:
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
csstype@^3.0.2:
csstype@3.1.3, csstype@^3.0.2:
version "3.1.3"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
@ -9643,7 +9689,7 @@ postcss-selector-parser@^7.0.0:
cssesc "^3.0.0"
util-deprecate "^1.0.2"
postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
@ -9657,6 +9703,15 @@ postcss@8.4.31:
picocolors "^1.0.0"
source-map-js "^1.0.2"
postcss@8.4.38:
version "8.4.38"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e"
integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==
dependencies:
nanoid "^3.3.7"
picocolors "^1.0.0"
source-map-js "^1.2.0"
postcss@^8, postcss@^8.2.14, postcss@^8.4.33, postcss@^8.4.38, postcss@^8.4.47:
version "8.4.49"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.49.tgz#4ea479048ab059ab3ae61d082190fabfd994fe19"
@ -9932,6 +9987,14 @@ react-docgen@^7.0.0:
loose-envify "^1.1.0"
scheduler "^0.23.2"
react-drag-drop-files@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/react-drag-drop-files/-/react-drag-drop-files-2.4.0.tgz#d4c4f14cf3e76bb7fb2734aed2174e7120e56733"
integrity sha512-MGPV3HVVnwXEXq3gQfLtSU3jz5j5jrabvGedokpiSEMoONrDHgYl/NpIOlfsqGQ4zBv1bzzv7qbKURZNOX32PA==
dependencies:
prop-types "^15.7.2"
styled-components "^6.1.11"
react-hook-form@^7.54.0:
version "7.54.1"
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.54.1.tgz#e99c2a55a5e4859fb47a8f55adf66b34d6ac331d"
@ -10524,6 +10587,11 @@ sha.js@^2.4.0, sha.js@^2.4.8:
inherits "^2.0.1"
safe-buffer "^5.0.1"
shallowequal@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==
sharp@^0.33.3:
version "0.33.5"
resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.33.5.tgz#13e0e4130cc309d6a9497596715240b2ec0c594e"
@ -10656,7 +10724,7 @@ slash@^5.0.0:
resolved "https://registry.yarnpkg.com/slash/-/slash-5.1.0.tgz#be3adddcdf09ac38eebe8dcdc7b1a57a75b095ce"
integrity sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==
source-map-js@^1.0.2, source-map-js@^1.2.1:
source-map-js@^1.0.2, source-map-js@^1.2.0, source-map-js@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
@ -10982,6 +11050,21 @@ style-to-object@^1.0.0:
dependencies:
inline-style-parser "0.2.4"
styled-components@^6.1.11:
version "6.1.13"
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-6.1.13.tgz#2d777750b773b31469bd79df754a32479e9f475e"
integrity sha512-M0+N2xSnAtwcVAQeFEsGWFFxXDftHUD7XrKla06QbpUMmbmtFBMMTcKWvFXtWxuD5qQkB8iU5gk6QASlx2ZRMw==
dependencies:
"@emotion/is-prop-valid" "1.2.2"
"@emotion/unitless" "0.8.1"
"@types/stylis" "4.2.5"
css-to-react-native "3.2.0"
csstype "3.1.3"
postcss "8.4.38"
shallowequal "1.1.0"
stylis "4.3.2"
tslib "2.6.2"
styled-jsx@5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.1.1.tgz#839a1c3aaacc4e735fed0781b8619ea5d0009d1f"
@ -10996,6 +11079,11 @@ styled-jsx@^5.1.6:
dependencies:
client-only "0.0.1"
stylis@4.3.2:
version "4.3.2"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.2.tgz#8f76b70777dd53eb669c6f58c997bf0a9972e444"
integrity sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==
sucrase@^3.35.0:
version "3.35.0"
resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.0.tgz#57f17a3d7e19b36d8995f06679d121be914ae263"
@ -11245,6 +11333,11 @@ tsconfig-paths@^4.0.0, tsconfig-paths@^4.1.2, tsconfig-paths@^4.2.0:
minimist "^1.2.6"
strip-bom "^3.0.0"
tslib@2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0:
version "2.8.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"