From 784c025938a59aad40af7ae9e94576cdf8e8cd9b Mon Sep 17 00:00:00 2001 From: abhi1992002 Date: Fri, 13 Feb 2026 20:55:44 +0530 Subject: [PATCH] feat(library): refine folder filtering and enhance animation handling in LibraryAgentList - Updated `list_library_agents` function to improve folder filtering logic, ensuring it only applies when not searching. - Enhanced animation handling in `LibraryAgentList` by implementing explicit initial and animate states for items, improving visibility during dynamic updates. - Adjusted transition timings for smoother animations, particularly when items are added or removed. These changes enhance the user experience by providing clearer folder management and more responsive animations in the library interface. --- .../backend/api/features/library/db.py | 6 +- .../LibraryAgentList/LibraryAgentList.tsx | 110 ++++++++++-------- .../LibraryAgentList/useLibraryAgentList.ts | 16 +-- .../LibraryFolderCreationDialog.tsx | 16 --- .../LibraryFolderEditDialog.tsx | 20 +--- 5 files changed, 76 insertions(+), 92 deletions(-) diff --git a/autogpt_platform/backend/backend/api/features/library/db.py b/autogpt_platform/backend/backend/api/features/library/db.py index 4e9ac5bd2b..1eb224521a 100644 --- a/autogpt_platform/backend/backend/api/features/library/db.py +++ b/autogpt_platform/backend/backend/api/features/library/db.py @@ -87,10 +87,10 @@ async def list_library_agents( "isArchived": False, } - # Apply folder filter - if folder_id is not None: + # Apply folder filter (skip when searching — search spans all folders) + if folder_id is not None and not search_term: where_clause["folderId"] = folder_id - elif include_root_only: + elif include_root_only and not search_term: where_clause["folderId"] = None # Build search filter if applicable diff --git a/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryAgentList/LibraryAgentList.tsx b/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryAgentList/LibraryAgentList.tsx index fd5163d8e4..41ad465db0 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryAgentList/LibraryAgentList.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryAgentList/LibraryAgentList.tsx @@ -21,62 +21,57 @@ import { LibraryFolderEditDialog } from "../LibraryFolderEditDialog/LibraryFolde import { LibraryFolderDeleteDialog } from "../LibraryFolderDeleteDialog/LibraryFolderDeleteDialog"; import { useLibraryAgentList } from "./useLibraryAgentList"; +// Spring-based enter/exit animations (Emil Kowalski principles) +// Springs are naturally interruptible — switching tabs mid-animation +// cancels the current spring and starts a new one from current state. const containerVariants = { hidden: {}, - show: { - transition: { - staggerChildren: 0.04, - delayChildren: 0.04, - }, - }, + show: {}, exit: { opacity: 0, filter: "blur(4px)", - transition: { - duration: 0.15, - ease: [0.25, 0.1, 0.25, 1], - }, - }, -}; - -const itemVariants = { - hidden: { - opacity: 0, - y: 8, - scale: 0.96, - filter: "blur(4px)", - }, - show: { - opacity: 1, - y: 0, - scale: 1, - filter: "blur(0px)", - transition: { - duration: 0.25, - ease: [0.25, 0.1, 0.25, 1], - }, + transition: { duration: 0.12 }, }, }; +// Reduced motion fallback const reducedContainerVariants = { hidden: {}, - show: { - transition: { staggerChildren: 0.02 }, - }, + show: {}, exit: { opacity: 0, - transition: { duration: 0.15 }, + transition: { duration: 0.12 }, }, }; -const reducedItemVariants = { - hidden: { opacity: 0 }, - show: { - opacity: 1, - transition: { duration: 0.2 }, - }, +// Per-item animation values (explicit initial/animate, not variant-based). +// This ensures items animate in on mount regardless of parent state — fixes +// the bug where dynamically added children (e.g. folders reappearing after +// search is cleared) stayed invisible with variant inheritance. +const itemInitial = { + opacity: 0, + y: 8, + filter: "blur(4px)", }; +const itemAnimate = { + opacity: 1, + y: 0, + filter: "blur(0px)", +}; + +const itemTransition = { + type: "spring" as const, + stiffness: 300, + damping: 25, + opacity: { duration: 0.2 }, + filter: { duration: 0.15 }, +}; + +const reducedItemInitial = { opacity: 0 }; +const reducedItemAnimate = { opacity: 1 }; +const reducedItemTransition = { duration: 0.15 }; + interface Props { searchTerm: string; librarySort: LibraryAgentSort; @@ -102,9 +97,11 @@ export function LibraryAgentList({ const activeContainerVariants = shouldReduceMotion ? reducedContainerVariants : containerVariants; - const activeItemVariants = shouldReduceMotion - ? reducedItemVariants - : itemVariants; + const activeInitial = shouldReduceMotion ? reducedItemInitial : itemInitial; + const activeAnimate = shouldReduceMotion ? reducedItemAnimate : itemAnimate; + const activeTransition = shouldReduceMotion + ? reducedItemTransition + : itemTransition; const { isFavoritesTab, @@ -181,7 +178,7 @@ export function LibraryAgentList({ loader={} > - + {showFolders && - foldersData?.folders.map((folder) => ( - + foldersData?.folders.map((folder, i) => ( + ))} - {agents.map((agent) => ( - + {agents.map((agent, i) => ( + ))} diff --git a/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryAgentList/useLibraryAgentList.ts b/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryAgentList/useLibraryAgentList.ts index b9ef9ae594..65f22eef5c 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryAgentList/useLibraryAgentList.ts +++ b/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryAgentList/useLibraryAgentList.ts @@ -19,7 +19,7 @@ import { import { useToast } from "@/components/molecules/Toast/use-toast"; import { useFavoriteAgents } from "../../hooks/useFavoriteAgents"; import { getQueryClient } from "@/lib/react-query/queryClient"; -import { useQueryClient } from "@tanstack/react-query"; +import { keepPreviousData, useQueryClient } from "@tanstack/react-query"; import { useEffect, useRef, useState } from "react"; interface Props { @@ -50,8 +50,6 @@ export function useLibraryAgentList({ null, ); - // --- Agent list fetching --- - const { data: agentsQueryData, fetchNextPage, @@ -112,10 +110,13 @@ export function useLibraryAgentList({ // --- Folders --- - const { data: foldersData } = useGetV2ListLibraryFolders(undefined, { + const { data: rawFoldersData } = useGetV2ListLibraryFolders(undefined, { query: { select: okData }, }); + // When searching, suppress folder data so only agent results show + const foldersData = searchTerm ? undefined : rawFoldersData; + const { mutate: moveAgentToFolder } = usePostV2BulkMoveAgents({ mutation: { onMutate: async ({ data }) => { @@ -126,9 +127,10 @@ export function useLibraryAgentList({ queryKey: getGetV2ListLibraryAgentsQueryKey(), }); - const previousFolders = queryClient.getQueriesData< - getV2ListLibraryFoldersResponseSuccess - >({ queryKey: getGetV2ListLibraryFoldersQueryKey() }); + const previousFolders = + queryClient.getQueriesData({ + queryKey: getGetV2ListLibraryFoldersQueryKey(), + }); if (data.folder_id) { queryClient.setQueriesData( diff --git a/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryFolderCreationDialog/LibraryFolderCreationDialog.tsx b/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryFolderCreationDialog/LibraryFolderCreationDialog.tsx index 7ac9fca52c..2fe0078896 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryFolderCreationDialog/LibraryFolderCreationDialog.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryFolderCreationDialog/LibraryFolderCreationDialog.tsx @@ -19,10 +19,8 @@ import { z } from "zod"; import { EmojiPicker } from "@ferrucc-io/emoji-picker"; import { usePostV2CreateFolder, - useGetV2ListLibraryFolders, getGetV2ListLibraryFoldersQueryKey, } from "@/app/api/__generated__/endpoints/folders/folders"; -import { okData } from "@/app/api/helpers"; import { useToast } from "@/components/molecules/Toast/use-toast"; import { useQueryClient } from "@tanstack/react-query"; @@ -45,10 +43,6 @@ export default function LibraryFolderCreationDialog() { const queryClient = useQueryClient(); const { toast } = useToast(); - const { data: foldersData } = useGetV2ListLibraryFolders(undefined, { - query: { select: okData }, - }); - const { mutate: createFolder, isPending } = usePostV2CreateFolder({ mutation: { onSuccess: () => { @@ -80,16 +74,6 @@ export default function LibraryFolderCreationDialog() { }); function onSubmit(values: z.infer) { - const existingNames = (foldersData?.folders ?? []).map((f) => - f.name.toLowerCase(), - ); - if (existingNames.includes(values.folderName.trim().toLowerCase())) { - form.setError("folderName", { - message: "A folder with this name already exists", - }); - return; - } - createFolder({ data: { name: values.folderName.trim(), diff --git a/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryFolderEditDialog/LibraryFolderEditDialog.tsx b/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryFolderEditDialog/LibraryFolderEditDialog.tsx index 1ff16652a7..a0c73b3aca 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryFolderEditDialog/LibraryFolderEditDialog.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryFolderEditDialog/LibraryFolderEditDialog.tsx @@ -20,10 +20,8 @@ import { z } from "zod"; import { EmojiPicker } from "@ferrucc-io/emoji-picker"; import { usePatchV2UpdateFolder, - useGetV2ListLibraryFolders, getGetV2ListLibraryFoldersQueryKey, } from "@/app/api/__generated__/endpoints/folders/folders"; -import { okData } from "@/app/api/helpers"; import { useQueryClient } from "@tanstack/react-query"; import type { LibraryFolder } from "@/app/api/__generated__/models/libraryFolder"; import type { getV2ListLibraryFoldersResponseSuccess } from "@/app/api/__generated__/endpoints/folders/folders"; @@ -52,10 +50,6 @@ export function LibraryFolderEditDialog({ folder, isOpen, setIsOpen }: Props) { const queryClient = useQueryClient(); const { toast } = useToast(); - const { data: foldersData } = useGetV2ListLibraryFolders(undefined, { - query: { select: okData }, - }); - const form = useForm>({ resolver: zodResolver(editFolderSchema), defaultValues: { @@ -154,22 +148,10 @@ export function LibraryFolderEditDialog({ folder, isOpen, setIsOpen }: Props) { }); function onSubmit(values: z.infer) { - const trimmedName = values.folderName.trim(); - const existingNames = (foldersData?.folders ?? []) - .filter((f) => f.id !== folder.id) - .map((f) => f.name.toLowerCase()); - - if (existingNames.includes(trimmedName.toLowerCase())) { - form.setError("folderName", { - message: "A folder with this name already exists", - }); - return; - } - updateFolder({ folderId: folder.id, data: { - name: trimmedName, + name: values.folderName.trim(), color: values.folderColor, icon: values.folderIcon, },