diff --git a/autogpt_platform/backend/backend/api/features/library/db.py b/autogpt_platform/backend/backend/api/features/library/db.py index 6561c341bd..ea9b97bfd7 100644 --- a/autogpt_platform/backend/backend/api/features/library/db.py +++ b/autogpt_platform/backend/backend/api/features/library/db.py @@ -7,12 +7,12 @@ import prisma.errors import prisma.models import prisma.types -from backend.api.features.library.exceptions import FolderValidationError import backend.api.features.store.exceptions as store_exceptions import backend.api.features.store.image_gen as store_image_gen import backend.api.features.store.media as store_media import backend.data.graph as graph_db import backend.data.integrations as integrations_db +from backend.api.features.library.exceptions import FolderValidationError from backend.data.db import transaction from backend.data.execution import get_graph_execution from backend.data.graph import GraphSettings @@ -1528,7 +1528,8 @@ async def delete_folder( "isDeleted": False, }, ) - for agent in affected_agents: + + async def _cleanup_agent(agent: prisma.models.LibraryAgent) -> None: try: await _cleanup_schedules_for_graph( graph_id=agent.agentGraphId, user_id=user_id @@ -1542,6 +1543,8 @@ async def delete_folder( f"(graph {agent.agentGraphId}): {e}" ) + await asyncio.gather(*[_cleanup_agent(a) for a in affected_agents]) + async with transaction() as tx: if soft_delete: # Soft-delete all agents in these folders diff --git a/autogpt_platform/backend/backend/api/features/library/exceptions.py b/autogpt_platform/backend/backend/api/features/library/exceptions.py index 1288f0807c..bd9d307b80 100644 --- a/autogpt_platform/backend/backend/api/features/library/exceptions.py +++ b/autogpt_platform/backend/backend/api/features/library/exceptions.py @@ -1,4 +1,4 @@ class FolderValidationError(Exception): """Raised when folder operations fail validation.""" - pass \ No newline at end of file + pass diff --git a/autogpt_platform/backend/backend/api/features/library/routes/agents.py b/autogpt_platform/backend/backend/api/features/library/routes/agents.py index 801a5a703b..eee7849451 100644 --- a/autogpt_platform/backend/backend/api/features/library/routes/agents.py +++ b/autogpt_platform/backend/backend/api/features/library/routes/agents.py @@ -1,3 +1,4 @@ +import logging from typing import Literal, Optional import autogpt_libs.auth as autogpt_auth_lib @@ -6,10 +7,13 @@ from fastapi.responses import Response from prisma.enums import OnboardingStep from backend.data.onboarding import complete_onboarding_step +from backend.util.exceptions import DatabaseError, NotFoundError from .. import db as library_db from .. import model as library_model +logger = logging.getLogger(__name__) + router = APIRouter( prefix="/agents", tags=["library", "private"], @@ -194,9 +198,7 @@ async def update_library_agent( detail=str(e), ) from e except DatabaseError as e: - logger.error( - f"Database error while updating library agent: {e}", exc_info=True - ) + logger.error(f"Database error while updating library agent: {e}", exc_info=True) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail={"message": "Internal server error", "hint": "Contact support"}, diff --git a/autogpt_platform/frontend/src/app/(platform)/library/components/FavoritesSection/FavoritesSection.tsx b/autogpt_platform/frontend/src/app/(platform)/library/components/FavoritesSection/FavoritesSection.tsx index a9ed3bdc14..f81b34ee92 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/components/FavoritesSection/FavoritesSection.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/components/FavoritesSection/FavoritesSection.tsx @@ -19,7 +19,13 @@ interface Props { setLibrarySort: (value: LibraryAgentSort) => void; } -export function FavoritesSection({ searchTerm, tabs, activeTab, onTabChange, setLibrarySort }: Props) { +export function FavoritesSection({ + searchTerm, + tabs, + activeTab, + onTabChange, + setLibrarySort, +}: Props) { const { allAgents: favoriteAgents, agentLoading: isLoading, @@ -31,8 +37,15 @@ export function FavoritesSection({ searchTerm, tabs, activeTab, onTabChange, set return ( <> - - + + {isLoading ? (
diff --git a/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryAgentCard/LibraryAgentCard.tsx b/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryAgentCard/LibraryAgentCard.tsx index 6db91fe4f9..b7d74e712d 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryAgentCard/LibraryAgentCard.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryAgentCard/LibraryAgentCard.tsx @@ -22,10 +22,7 @@ interface Props { draggable?: boolean; } -export function LibraryAgentCard({ - agent, - draggable = true, -}: Props) { +export function LibraryAgentCard({ agent, draggable = true }: Props) { const { id, name, graph_id, can_access_graph, image_url } = agent; const { triggerFavoriteAnimation } = useFavoriteAnimation(); @@ -63,95 +60,95 @@ export function LibraryAgentCard({ }} style={{ willChange: "transform" }} > - -
- - - {name.charAt(0)} - - - {isFromMarketplace ? "FROM MARKETPLACE" : "Built by you"} - -
-
- - + +
+ + + {name.charAt(0)} + + + {isFromMarketplace ? "FROM MARKETPLACE" : "Built by you"} + +
+
+ + -
- - - {name} - - - {!image_url ? ( -
- ) : ( - {`${name} - )} - - -
+
- See runs + + {name} + + + {!image_url ? ( +
+ ) : ( + {`${name} + )} - {can_access_graph && ( +
- Open in builder + See runs - )} + + {can_access_graph && ( + + Open in builder + + )} +
-
); diff --git a/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryAgentCard/components/FavoriteButton.tsx b/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryAgentCard/components/FavoriteButton.tsx index a003d64f59..2289ab918b 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryAgentCard/components/FavoriteButton.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryAgentCard/components/FavoriteButton.tsx @@ -8,7 +8,10 @@ import { motion, AnimatePresence } from "framer-motion"; interface FavoriteButtonProps { isFavorite: boolean; - onClick: (e: MouseEvent, position: { x: number; y: number }) => void; + onClick: ( + e: MouseEvent, + position: { x: number; y: number }, + ) => void; className?: string; } @@ -22,7 +25,10 @@ export function FavoriteButton({ function handleClick(e: MouseEvent) { const rect = buttonRef.current?.getBoundingClientRect(); const position = rect - ? { x: rect.left + rect.width / 2 - 12, y: rect.top + rect.height / 2 - 12 } + ? { + x: rect.left + rect.width / 2 - 12, + y: rect.top + rect.height / 2 - 12, + } : { x: 0, y: 0 }; onClick(e, position); } diff --git a/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryAgentCard/useLibraryAgentCard.ts b/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryAgentCard/useLibraryAgentCard.ts index 1b1feccdd4..4362da1ea8 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryAgentCard/useLibraryAgentCard.ts +++ b/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryAgentCard/useLibraryAgentCard.ts @@ -51,7 +51,7 @@ export function useLibraryAgentCard({ agent, onFavoriteAdd }: Props) { async function handleToggleFavorite( e: React.MouseEvent, - position: { x: number; y: number } + position: { x: number; y: number }, ) { e.preventDefault(); e.stopPropagation(); 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 e625fbaee2..532e0a02b1 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 @@ -217,7 +217,9 @@ export function LibraryAgentList({ transition={{ ...activeTransition, delay: - ((showFolders ? foldersData?.folders.length ?? 0 : 0) + + ((showFolders + ? (foldersData?.folders.length ?? 0) + : 0) + i) * 0.04, }} 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 65f22eef5c..2304a13e3e 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 { keepPreviousData, useQueryClient } from "@tanstack/react-query"; +import { useQueryClient } from "@tanstack/react-query"; import { useEffect, useRef, useState } from "react"; interface Props { @@ -86,8 +86,6 @@ export function useLibraryAgentList({ : []; const allAgentsCount = getPaginatedTotalCount(agentsQueryData); - // --- Favorites --- - const favoriteAgentsData = useFavoriteAgents({ searchTerm }); const { @@ -108,13 +106,10 @@ export function useLibraryAgentList({ fetchNextPage: fetchNextPage, }; - // --- Folders --- - 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({ diff --git a/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryFolder/FolderIcon.tsx b/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryFolder/FolderIcon.tsx index a7be232caf..55aff18726 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryFolder/FolderIcon.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryFolder/FolderIcon.tsx @@ -1,4 +1,3 @@ -import { useState } from "react"; import { motion } from "framer-motion"; import { Text } from "@/components/atoms/Text/Text"; 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 2fe0078896..4606a7baa9 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 @@ -46,7 +46,9 @@ export default function LibraryFolderCreationDialog() { const { mutate: createFolder, isPending } = usePostV2CreateFolder({ mutation: { onSuccess: () => { - queryClient.invalidateQueries({ queryKey: getGetV2ListLibraryFoldersQueryKey() }); + queryClient.invalidateQueries({ + queryKey: getGetV2ListLibraryFoldersQueryKey(), + }); setIsOpen(false); form.reset(); toast({ @@ -110,7 +112,7 @@ export default function LibraryFolderCreationDialog() {
onSubmit(values)} - className="flex flex-col justify-center px-1 gap-2" + className="flex flex-col justify-center gap-2 px-1" > @@ -189,7 +191,6 @@ export default function LibraryFolderCreationDialog() {
- { field.onChange(emoji); @@ -198,7 +199,10 @@ export default function LibraryFolderCreationDialog() { className="w-full rounded-2xl px-2" > - +
diff --git a/autogpt_platform/frontend/src/app/(platform)/library/components/LibrarySubSection/LibrarySubSection.tsx b/autogpt_platform/frontend/src/app/(platform)/library/components/LibrarySubSection/LibrarySubSection.tsx index 1d0f1e6e5e..f69f9f57ac 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/components/LibrarySubSection/LibrarySubSection.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/components/LibrarySubSection/LibrarySubSection.tsx @@ -9,9 +9,13 @@ interface Props { export function LibrarySubSection({ tabs, activeTab, onTabChange }: Props) { return ( -
- +
+
); -} \ No newline at end of file +} diff --git a/autogpt_platform/frontend/src/app/(platform)/library/context/FavoriteAnimationContext.tsx b/autogpt_platform/frontend/src/app/(platform)/library/context/FavoriteAnimationContext.tsx index 144e491d1c..f2d7fa2051 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/context/FavoriteAnimationContext.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/context/FavoriteAnimationContext.tsx @@ -1,6 +1,12 @@ "use client"; -import { createContext, useContext, useState, useCallback, useRef } from "react"; +import { + createContext, + useContext, + useState, + useCallback, + useRef, +} from "react"; import { FlyingHeart } from "../components/FlyingHeart/FlyingHeart"; interface FavoriteAnimationContextType { @@ -8,7 +14,8 @@ interface FavoriteAnimationContextType { registerFavoritesTabRef: (element: HTMLElement | null) => void; } -const FavoriteAnimationContext = createContext(null); +const FavoriteAnimationContext = + createContext(null); interface FavoriteAnimationProviderProps { children: React.ReactNode; @@ -44,7 +51,7 @@ export function FavoriteAnimationProvider({ setAnimationState({ startPosition, targetPosition }); } }, - [] + [], ); function handleAnimationComplete() { @@ -70,7 +77,7 @@ export function useFavoriteAnimation() { const context = useContext(FavoriteAnimationContext); if (!context) { throw new Error( - "useFavoriteAnimation must be used within FavoriteAnimationProvider" + "useFavoriteAnimation must be used within FavoriteAnimationProvider", ); } return context; diff --git a/autogpt_platform/frontend/src/app/(platform)/library/page.tsx b/autogpt_platform/frontend/src/app/(platform)/library/page.tsx index 128ba609dd..2510bd8f33 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/page.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/page.tsx @@ -34,7 +34,9 @@ export default function LibraryPage() { }, []); return ( - +