feat(library): enhance folder and agent management with error handling improvements

- Added support for updating the folder ID in the `update_library_agent` function, allowing agents to be moved to different folders.
- Implemented cleanup of schedules and webhooks for affected agents during folder deletion, ensuring proper resource management.
- Improved error handling in various folder-related API endpoints, standardizing error messages to provide clearer feedback to users.
- Updated the `LibraryFolderEditDialog` to handle API errors more effectively, enhancing user experience during folder operations.

These changes improve the functionality and reliability of folder and agent management within the library, providing users with a smoother experience when organizing their agents.
This commit is contained in:
abhi1992002
2026-02-13 21:23:52 +05:30
parent d7d571f1be
commit 94bd91388f
4 changed files with 59 additions and 28 deletions

View File

@@ -688,6 +688,9 @@ async def update_library_agent(
update_fields["isDeleted"] = is_deleted
if settings is not None:
update_fields["settings"] = SafeJson(settings.model_dump())
if folder_id is not None:
# Empty string means "move to root" (no folder)
update_fields["folderId"] = None if folder_id == "" else folder_id
try:
# If graph_version is provided, update to that specific version
@@ -1510,11 +1513,36 @@ async def delete_folder(
if not existing:
raise NotFoundError(f"Folder #{folder_id} not found")
# Collect all folder IDs (target + descendants) before the transaction
async with transaction() as tx:
# Get all descendant folders recursively
descendant_ids = await _get_descendant_folder_ids(folder_id, user_id, tx)
all_folder_ids = [folder_id] + descendant_ids
all_folder_ids = [folder_id] + descendant_ids
if soft_delete:
# Clean up schedules/webhooks for each affected agent before
# soft-deleting, matching what delete_library_agent() does.
affected_agents = await prisma.models.LibraryAgent.prisma().find_many(
where={
"folderId": {"in": all_folder_ids},
"userId": user_id,
"isDeleted": False,
},
)
for agent in affected_agents:
try:
await _cleanup_schedules_for_graph(
graph_id=agent.agentGraphId, user_id=user_id
)
await _cleanup_webhooks_for_graph(
graph_id=agent.agentGraphId, user_id=user_id
)
except Exception as e:
logger.warning(
f"Cleanup failed for agent {agent.id} "
f"(graph {agent.agentGraphId}): {e}"
)
async with transaction() as tx:
if soft_delete:
# Soft-delete all agents in these folders
await prisma.models.LibraryAgent.prisma(tx).update_many(

View File

@@ -194,16 +194,20 @@ async def update_library_agent(
detail=str(e),
) from e
except DatabaseError as e:
logger.error(f"Database error while updating library agent: {e}")
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": str(e), "hint": "Verify DB connection."},
detail={"message": "Internal server error", "hint": "Contact support"},
) from e
except Exception as e:
logger.error(f"Unexpected error while updating library agent: {e}")
logger.error(
f"Unexpected error while updating library agent: {e}", exc_info=True
)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail={"message": str(e), "hint": "Check server logs."},
detail={"message": "Internal server error", "hint": "Contact support"},
) from e

View File

@@ -69,7 +69,7 @@ async def list_folders(
logger.error(f"Could not list folders for user #{user_id}: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e),
detail="Internal server error",
) from e
@@ -101,7 +101,7 @@ async def get_folder_tree(
logger.error(f"Could not get folder tree for user #{user_id}: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e),
detail="Internal server error",
) from e
@@ -140,7 +140,7 @@ async def get_folder(
logger.error(f"Could not get folder #{folder_id}: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e),
detail="Internal server error",
) from e
@@ -198,7 +198,7 @@ async def create_folder(
logger.error(f"Database error creating folder: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e),
detail="Internal server error",
) from e
@@ -257,7 +257,7 @@ async def update_folder(
logger.error(f"Database error updating folder #{folder_id}: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e),
detail="Internal server error",
) from e
@@ -314,7 +314,7 @@ async def move_folder(
logger.error(f"Database error moving folder #{folder_id}: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e),
detail="Internal server error",
) from e
@@ -358,7 +358,7 @@ async def delete_folder(
logger.error(f"Database error deleting folder #{folder_id}: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e),
detail="Internal server error",
) from e
@@ -404,5 +404,5 @@ async def bulk_move_agents(
logger.error(f"Database error bulk moving agents: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e),
detail="Internal server error",
) from e

View File

@@ -25,6 +25,7 @@ import {
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";
import { ApiError } from "@/lib/autogpt-server-api/helpers";
const FOLDER_COLORS = [
{ value: "#3B82F6", label: "Blue" },
@@ -106,25 +107,23 @@ export function LibraryFolderEditDialog({ folder, isOpen, setIsOpen }: Props) {
return { previousData };
},
onError: (
error: { detail?: string; response?: { detail?: string } },
_variables,
context,
) => {
onError: (error: unknown, _variables, context) => {
if (context?.previousData) {
for (const [queryKey, data] of context.previousData) {
queryClient.setQueryData(queryKey, data);
}
}
const detail = error.detail ?? error.response?.detail ?? "";
if (
typeof detail === "string" &&
detail.toLowerCase().includes("already exists")
) {
form.setError("folderName", {
message: "A folder with this name already exists",
});
return;
if (error instanceof ApiError) {
const detail = (error.response as any)?.detail ?? "";
if (
typeof detail === "string" &&
detail.toLowerCase().includes("already exists")
) {
form.setError("folderName", {
message: "A folder with this name already exists",
});
return;
}
}
toast({
title: "Error",