mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-02-13 16:25:05 -05:00
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:
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user