From 3b53c6953af97ff09af24e0cd09ed1e5184835ba Mon Sep 17 00:00:00 2001 From: Reinier van der Leer Date: Wed, 5 Mar 2025 17:14:59 +0100 Subject: [PATCH] feat(platform): Agent Run "Stop" + "Delete" functionality (#9547) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Resolves #9542 ### Changes 🏗️ - feat(platform): Add "Delete run" button on `/library/agents/[id]` w/ confirm dialog - Add `AgentGraphExecution.isDeleted` column for soft delete - Add `DELETE /api/executions/{graph_exec_id}` endpoint - feat(frontend): Add "Stop run" button on `/library/agents/[id]` Technical improvements: - refactor(frontend): Generalize `AgentDeleteConfirmDialog` -> `DeleteConfirmDialog` - refactor(frontend): Brand `GraphExecution.execution_id` and `Schedule.id` to prevent mixing ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - Click "Delete run" under "Run actions" in the right sidebar - [x] -> Confirmation dialog should show - Click "Delete" - [x] -> run should disappear from list - [x] -> view should switch to "Draft new run" view - [x] -> refresh page -> run should not be in list - Click "Delete" in the menu on a run in the list - [x] -> Confirmation dialog should show - Click "Delete" - [x] -> run should disappear from list - [x] -> refresh page -> run should not be in list --------- Co-authored-by: Krzysztof Czerwinski <34861343+kcze@users.noreply.github.com> --- .../backend/backend/data/execution.py | 53 ++++++-------- .../backend/backend/data/graph.py | 9 +-- .../backend/backend/server/routers/v1.py | 19 ++++- .../migration.sql | 6 ++ autogpt_platform/backend/schema.prisma | 2 + .../src/app/library/agents/[id]/page.tsx | 71 ++++++++++++++----- .../frontend/src/app/monitoring/page.tsx | 3 +- .../frontend/src/components/Flow.tsx | 15 +++- .../agents/agent-run-details-view.tsx | 33 +++++++-- .../agents/agent-run-draft-view.tsx | 4 +- .../agents/agent-run-summary-card.tsx | 32 ++++----- .../agents/agent-runs-selector-list.tsx | 22 +++--- .../agents/agent-schedule-details-view.tsx | 8 ++- .../delete-confirm-dialog.tsx} | 24 +++++-- .../src/components/monitor/scheduleTable.tsx | 6 +- .../frontend/src/hooks/useAgentGraph.tsx | 5 +- .../src/lib/autogpt-server-api/client.ts | 32 ++++++--- .../src/lib/autogpt-server-api/types.ts | 14 ++-- 18 files changed, 239 insertions(+), 119 deletions(-) create mode 100644 autogpt_platform/backend/migrations/20250228161607_agent_graph_execution_soft_delete/migration.sql rename autogpt_platform/frontend/src/components/{agents/agent-delete-confirm-dialog.tsx => agptui/delete-confirm-dialog.tsx} (57%) diff --git a/autogpt_platform/backend/backend/data/execution.py b/autogpt_platform/backend/backend/data/execution.py index ca6926ee1c..aef43bc9ae 100644 --- a/autogpt_platform/backend/backend/data/execution.py +++ b/autogpt_platform/backend/backend/data/execution.py @@ -1,11 +1,10 @@ from collections import defaultdict from datetime import datetime, timezone from multiprocessing import Manager -from typing import Any, AsyncGenerator, Generator, Generic, Optional, Type, TypeVar +from typing import Any, AsyncGenerator, Generator, Generic, Type, TypeVar from prisma import Json from prisma.enums import AgentExecutionStatus -from prisma.errors import PrismaError from prisma.models import ( AgentGraphExecution, AgentNodeExecution, @@ -342,28 +341,21 @@ async def update_execution_status( return ExecutionResult.from_db(res) -async def get_execution( - execution_id: str, user_id: str -) -> Optional[AgentNodeExecution]: - """ - Get an execution by ID. Returns None if not found. - - Args: - execution_id: The ID of the execution to retrieve - - Returns: - The execution if found, None otherwise - """ - try: - execution = await AgentNodeExecution.prisma().find_unique( - where={ - "id": execution_id, - "userId": user_id, - } +async def delete_execution( + graph_exec_id: str, user_id: str, soft_delete: bool = True +) -> None: + if soft_delete: + deleted_count = await AgentGraphExecution.prisma().update_many( + where={"id": graph_exec_id, "userId": user_id}, data={"isDeleted": True} + ) + else: + deleted_count = await AgentGraphExecution.prisma().delete_many( + where={"id": graph_exec_id, "userId": user_id} + ) + if deleted_count < 1: + raise DatabaseError( + f"Could not delete graph execution #{graph_exec_id}: not found" ) - return execution - except PrismaError: - return None async def get_execution_results(graph_exec_id: str) -> list[ExecutionResult]: @@ -385,15 +377,12 @@ async def get_executions_in_timerange( try: executions = await AgentGraphExecution.prisma().find_many( where={ - "AND": [ - { - "startedAt": { - "gte": datetime.fromisoformat(start_time), - "lte": datetime.fromisoformat(end_time), - } - }, - {"userId": user_id}, - ] + "startedAt": { + "gte": datetime.fromisoformat(start_time), + "lte": datetime.fromisoformat(end_time), + }, + "userId": user_id, + "isDeleted": False, }, include=GRAPH_EXECUTION_INCLUDE, ) diff --git a/autogpt_platform/backend/backend/data/graph.py b/autogpt_platform/backend/backend/data/graph.py index 1857c5fd51..a6a04b0ceb 100644 --- a/autogpt_platform/backend/backend/data/graph.py +++ b/autogpt_platform/backend/backend/data/graph.py @@ -597,9 +597,10 @@ async def get_graphs( return graph_models +# TODO: move execution stuff to .execution async def get_graphs_executions(user_id: str) -> list[GraphExecutionMeta]: executions = await AgentGraphExecution.prisma().find_many( - where={"userId": user_id}, + where={"isDeleted": False, "userId": user_id}, order={"createdAt": "desc"}, ) return [GraphExecutionMeta.from_db(execution) for execution in executions] @@ -607,7 +608,7 @@ async def get_graphs_executions(user_id: str) -> list[GraphExecutionMeta]: async def get_graph_executions(graph_id: str, user_id: str) -> list[GraphExecutionMeta]: executions = await AgentGraphExecution.prisma().find_many( - where={"agentGraphId": graph_id, "userId": user_id}, + where={"agentGraphId": graph_id, "isDeleted": False, "userId": user_id}, order={"createdAt": "desc"}, ) return [GraphExecutionMeta.from_db(execution) for execution in executions] @@ -617,14 +618,14 @@ async def get_execution_meta( user_id: str, execution_id: str ) -> GraphExecutionMeta | None: execution = await AgentGraphExecution.prisma().find_first( - where={"id": execution_id, "userId": user_id} + where={"id": execution_id, "isDeleted": False, "userId": user_id} ) return GraphExecutionMeta.from_db(execution) if execution else None async def get_execution(user_id: str, execution_id: str) -> GraphExecution | None: execution = await AgentGraphExecution.prisma().find_first( - where={"id": execution_id, "userId": user_id}, + where={"id": execution_id, "isDeleted": False, "userId": user_id}, include={ "AgentNodeExecutions": { "include": {"AgentNode": True, "Input": True, "Output": True}, diff --git a/autogpt_platform/backend/backend/server/routers/v1.py b/autogpt_platform/backend/backend/server/routers/v1.py index bf0408bcad..0863e651ba 100644 --- a/autogpt_platform/backend/backend/server/routers/v1.py +++ b/autogpt_platform/backend/backend/server/routers/v1.py @@ -10,12 +10,14 @@ from autogpt_libs.auth.middleware import auth_middleware from autogpt_libs.feature_flag.client import feature_flag from autogpt_libs.utils.cache import thread_cached from fastapi import APIRouter, Body, Depends, HTTPException, Request, Response +from starlette.status import HTTP_204_NO_CONTENT from typing_extensions import Optional, TypedDict import backend.data.block import backend.server.integrations.router import backend.server.routers.analytics import backend.server.v2.library.db as library_db +from backend.data import execution as execution_db from backend.data import graph as graph_db from backend.data.api_key import ( APIKeyError, @@ -621,11 +623,26 @@ async def get_graph_execution( result = await graph_db.get_execution(execution_id=graph_exec_id, user_id=user_id) if not result: - raise HTTPException(status_code=404, detail=f"Graph #{graph_id} not found.") + raise HTTPException( + status_code=404, detail=f"Graph execution #{graph_exec_id} not found." + ) return result +@v1_router.delete( + path="/executions/{graph_exec_id}", + tags=["graphs"], + dependencies=[Depends(auth_middleware)], + status_code=HTTP_204_NO_CONTENT, +) +async def delete_graph_execution( + graph_exec_id: str, + user_id: Annotated[str, Depends(get_user_id)], +) -> None: + await execution_db.delete_execution(graph_exec_id=graph_exec_id, user_id=user_id) + + ######################################################## ##################### Schedules ######################## ######################################################## diff --git a/autogpt_platform/backend/migrations/20250228161607_agent_graph_execution_soft_delete/migration.sql b/autogpt_platform/backend/migrations/20250228161607_agent_graph_execution_soft_delete/migration.sql new file mode 100644 index 0000000000..ef091b9c80 --- /dev/null +++ b/autogpt_platform/backend/migrations/20250228161607_agent_graph_execution_soft_delete/migration.sql @@ -0,0 +1,6 @@ +-- Add isDeleted column to AgentGraphExecution +ALTER TABLE "AgentGraphExecution" +ADD COLUMN "isDeleted" + BOOLEAN + NOT NULL + DEFAULT false; diff --git a/autogpt_platform/backend/schema.prisma b/autogpt_platform/backend/schema.prisma index 0373544e2d..12196be673 100644 --- a/autogpt_platform/backend/schema.prisma +++ b/autogpt_platform/backend/schema.prisma @@ -289,6 +289,8 @@ model AgentGraphExecution { updatedAt DateTime? @updatedAt startedAt DateTime? + isDeleted Boolean @default(false) + executionStatus AgentExecutionStatus @default(COMPLETED) agentGraphId String diff --git a/autogpt_platform/frontend/src/app/library/agents/[id]/page.tsx b/autogpt_platform/frontend/src/app/library/agents/[id]/page.tsx index c4f246eda3..8e242e6433 100644 --- a/autogpt_platform/frontend/src/app/library/agents/[id]/page.tsx +++ b/autogpt_platform/frontend/src/app/library/agents/[id]/page.tsx @@ -5,33 +5,37 @@ import { useParams, useRouter } from "next/navigation"; import { useBackendAPI } from "@/lib/autogpt-server-api/context"; import { GraphExecution, + GraphExecutionID, GraphExecutionMeta, GraphMeta, LibraryAgent, LibraryAgentID, Schedule, + ScheduleID, } from "@/lib/autogpt-server-api"; import type { ButtonAction } from "@/components/agptui/types"; +import DeleteConfirmDialog from "@/components/agptui/delete-confirm-dialog"; import AgentRunDraftView from "@/components/agents/agent-run-draft-view"; import AgentRunDetailsView from "@/components/agents/agent-run-details-view"; import AgentRunsSelectorList from "@/components/agents/agent-runs-selector-list"; import AgentScheduleDetailsView from "@/components/agents/agent-schedule-details-view"; -import AgentDeleteConfirmDialog from "@/components/agents/agent-delete-confirm-dialog"; export default function AgentRunsPage(): React.ReactElement { const { id: agentID }: { id: LibraryAgentID } = useParams(); const router = useRouter(); const api = useBackendAPI(); + // ============================ STATE ============================= + const [graph, setGraph] = useState(null); const [agent, setAgent] = useState(null); const [agentRuns, setAgentRuns] = useState([]); const [schedules, setSchedules] = useState([]); - const [selectedView, selectView] = useState<{ - type: "run" | "schedule"; - id?: string; - }>({ type: "run" }); + const [selectedView, selectView] = useState< + | { type: "run"; id?: GraphExecutionID } + | { type: "schedule"; id: ScheduleID } + >({ type: "run" }); const [selectedRun, setSelectedRun] = useState< GraphExecution | GraphExecutionMeta | null >(null); @@ -41,12 +45,14 @@ export default function AgentRunsPage(): React.ReactElement { const [isFirstLoad, setIsFirstLoad] = useState(true); const [agentDeleteDialogOpen, setAgentDeleteDialogOpen] = useState(false); + const [confirmingDeleteAgentRun, setConfirmingDeleteAgentRun] = + useState(null); const openRunDraftView = useCallback(() => { selectView({ type: "run" }); }, []); - const selectRun = useCallback((id: string) => { + const selectRun = useCallback((id: GraphExecutionID) => { selectView({ type: "run", id }); }, []); @@ -114,20 +120,40 @@ export default function AgentRunsPage(): React.ReactElement { fetchSchedules(); }, [fetchSchedules]); - const removeSchedule = useCallback( - async (scheduleId: string) => { - const removedSchedule = await api.deleteSchedule(scheduleId); - setSchedules(schedules.filter((s) => s.id !== removedSchedule.id)); - }, - [schedules, api], - ); - /* TODO: use websockets instead of polling - https://github.com/Significant-Gravitas/AutoGPT/issues/8782 */ useEffect(() => { const intervalId = setInterval(() => fetchAgents(), 5000); return () => clearInterval(intervalId); }, [fetchAgents]); + // =========================== ACTIONS ============================ + + const deleteRun = useCallback( + async (run: GraphExecutionMeta) => { + if (run.status == "RUNNING" || run.status == "QUEUED") { + await api.stopGraphExecution(run.graph_id, run.execution_id); + } + await api.deleteGraphExecution(run.execution_id); + + setConfirmingDeleteAgentRun(null); + if (selectedView.type == "run" && selectedView.id == run.execution_id) { + openRunDraftView(); + } + setAgentRuns( + agentRuns.filter((r) => r.execution_id !== run.execution_id), + ); + }, + [agentRuns, api, selectedView, openRunDraftView], + ); + + const deleteSchedule = useCallback( + async (scheduleID: ScheduleID) => { + const removedSchedule = await api.deleteSchedule(scheduleID); + setSchedules(schedules.filter((s) => s.id !== removedSchedule.id)); + }, + [schedules, api], + ); + const agentActions: ButtonAction[] = useMemo( () => [ { @@ -160,7 +186,9 @@ export default function AgentRunsPage(): React.ReactElement { selectedView={selectedView} onSelectRun={selectRun} onSelectSchedule={selectSchedule} - onDraftNewRun={openRunDraftView} + onSelectDraftNewRun={openRunDraftView} + onDeleteRun={setConfirmingDeleteAgentRun} + onDeleteSchedule={(id) => deleteSchedule(id)} />
@@ -180,6 +208,7 @@ export default function AgentRunsPage(): React.ReactElement { graph={graph} run={selectedRun} agentActions={agentActions} + deleteRun={() => setConfirmingDeleteAgentRun(selectedRun)} /> ) ) : selectedView.type == "run" ? ( @@ -199,7 +228,8 @@ export default function AgentRunsPage(): React.ReactElement { ) ) : null) ||

Loading...

} - @@ -209,6 +239,15 @@ export default function AgentRunsPage(): React.ReactElement { .then(() => router.push("/library")) } /> + + !open && setConfirmingDeleteAgentRun(null)} + onDoDelete={() => + confirmingDeleteAgentRun && deleteRun(confirmingDeleteAgentRun) + } + />
); diff --git a/autogpt_platform/frontend/src/app/monitoring/page.tsx b/autogpt_platform/frontend/src/app/monitoring/page.tsx index dd1cf2c7c0..251a194abc 100644 --- a/autogpt_platform/frontend/src/app/monitoring/page.tsx +++ b/autogpt_platform/frontend/src/app/monitoring/page.tsx @@ -5,6 +5,7 @@ import { GraphExecutionMeta, Schedule, LibraryAgent, + ScheduleID, } from "@/lib/autogpt-server-api"; import { Card } from "@/components/ui/card"; @@ -35,7 +36,7 @@ const Monitor = () => { }, [api]); const removeSchedule = useCallback( - async (scheduleId: string) => { + async (scheduleId: ScheduleID) => { const removedSchedule = await api.deleteSchedule(scheduleId); setSchedules(schedules.filter((s) => s.id !== removedSchedule.id)); }, diff --git a/autogpt_platform/frontend/src/components/Flow.tsx b/autogpt_platform/frontend/src/components/Flow.tsx index 7f3cf14157..9b62ac9507 100644 --- a/autogpt_platform/frontend/src/components/Flow.tsx +++ b/autogpt_platform/frontend/src/components/Flow.tsx @@ -26,7 +26,12 @@ import { import "@xyflow/react/dist/style.css"; import { CustomNode } from "./CustomNode"; import "./flow.css"; -import { BlockUIType, formatEdgeID, GraphID } from "@/lib/autogpt-server-api"; +import { + BlockUIType, + formatEdgeID, + GraphExecutionID, + GraphID, +} from "@/lib/autogpt-server-api"; import { getTypeColor, findNewlyAddedBlockCoordinates } from "@/lib/utils"; import { history } from "./history"; import { CustomEdge } from "./CustomEdge"; @@ -86,7 +91,9 @@ const FlowEditor: React.FC<{ const [visualizeBeads, setVisualizeBeads] = useState< "no" | "static" | "animate" >("animate"); - const [flowExecutionID, setFlowExecutionID] = useState(); + const [flowExecutionID, setFlowExecutionID] = useState< + GraphExecutionID | undefined + >(); const { agentName, setAgentName, @@ -164,7 +171,9 @@ const FlowEditor: React.FC<{ if (params.get("open_scheduling") === "true") { setOpenCron(true); } - setFlowExecutionID(params.get("flowExecutionID") || undefined); + setFlowExecutionID( + (params.get("flowExecutionID") as GraphExecutionID) || undefined, + ); }, [params]); useEffect(() => { diff --git a/autogpt_platform/frontend/src/components/agents/agent-run-details-view.tsx b/autogpt_platform/frontend/src/components/agents/agent-run-details-view.tsx index c6df19ce90..ccdef84575 100644 --- a/autogpt_platform/frontend/src/components/agents/agent-run-details-view.tsx +++ b/autogpt_platform/frontend/src/components/agents/agent-run-details-view.tsx @@ -23,10 +23,12 @@ export default function AgentRunDetailsView({ graph, run, agentActions, + deleteRun, }: { graph: GraphMeta; run: GraphExecution | GraphExecutionMeta; agentActions: ButtonAction[]; + deleteRun: () => void; }): React.ReactNode { const api = useBackendAPI(); @@ -86,6 +88,11 @@ export default function AgentRunDetailsView({ [api, graph, agentRunInputs], ); + const stopRun = useCallback( + () => api.stopGraphExecution(graph.id, run.execution_id), + [api, graph.id, run.execution_id], + ); + const agentRunOutputs: | Record< string, @@ -109,9 +116,23 @@ export default function AgentRunDetailsView({ ); }, [graph, run, runStatus]); - const runActions: { label: string; callback: () => void }[] = useMemo( - () => [{ label: "Run again", callback: () => runAgain() }], - [runAgain], + const runActions: ButtonAction[] = useMemo( + () => [ + ...(["running", "queued"].includes(runStatus) + ? ([ + { + label: "Stop run", + variant: "secondary", + callback: stopRun, + }, + ] satisfies ButtonAction[]) + : []), + ...(["success", "failed", "stopped"].includes(runStatus) + ? [{ label: "Run again", callback: runAgain }] + : []), + { label: "Delete run", variant: "secondary", callback: deleteRun }, + ], + [runStatus, runAgain, stopRun, deleteRun], ); return ( @@ -190,7 +211,11 @@ export default function AgentRunDetailsView({

Run actions

{runActions.map((action, i) => ( - ))} diff --git a/autogpt_platform/frontend/src/components/agents/agent-run-draft-view.tsx b/autogpt_platform/frontend/src/components/agents/agent-run-draft-view.tsx index a4f096780f..50ba007c96 100644 --- a/autogpt_platform/frontend/src/components/agents/agent-run-draft-view.tsx +++ b/autogpt_platform/frontend/src/components/agents/agent-run-draft-view.tsx @@ -2,7 +2,7 @@ import React, { useCallback, useMemo, useState } from "react"; import { useBackendAPI } from "@/lib/autogpt-server-api/context"; -import { GraphMeta } from "@/lib/autogpt-server-api"; +import { GraphExecutionID, GraphMeta } from "@/lib/autogpt-server-api"; import type { ButtonAction } from "@/components/agptui/types"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; @@ -15,7 +15,7 @@ export default function AgentRunDraftView({ agentActions, }: { graph: GraphMeta; - onRun: (runID: string) => void; + onRun: (runID: GraphExecutionID) => void; agentActions: ButtonAction[]; }): React.ReactNode { const api = useBackendAPI(); diff --git a/autogpt_platform/frontend/src/components/agents/agent-run-summary-card.tsx b/autogpt_platform/frontend/src/components/agents/agent-run-summary-card.tsx index 7336835438..912569a8cf 100644 --- a/autogpt_platform/frontend/src/components/agents/agent-run-summary-card.tsx +++ b/autogpt_platform/frontend/src/components/agents/agent-run-summary-card.tsx @@ -18,24 +18,24 @@ import AgentRunStatusChip, { } from "@/components/agents/agent-run-status-chip"; export type AgentRunSummaryProps = { - agentID: string; - agentRunID: string; status: AgentRunStatus; title: string; timestamp: number | Date; selected?: boolean; onClick?: () => void; + // onRename: () => void; + onDelete: () => void; className?: string; }; export default function AgentRunSummaryCard({ - agentID, - agentRunID, status, title, timestamp, selected = false, onClick, + // onRename, + onDelete, className, }: AgentRunSummaryProps): React.ReactElement { return ( @@ -55,32 +55,24 @@ export default function AgentRunSummaryCard({ {title} - {/* + - - Pin into a template + {/* {onPinAsPreset && ( + + Pin as a preset + )} */} - - Rename - + {/* Rename */} - - Delete - + Delete - */} +

void; + onSelectRun: (id: GraphExecutionID) => void; onSelectSchedule: (schedule: Schedule) => void; - onDraftNewRun: () => void; + onSelectDraftNewRun: () => void; + onDeleteRun: (id: GraphExecutionMeta) => void; + onDeleteSchedule: (id: ScheduleID) => void; className?: string; } @@ -34,7 +38,9 @@ export default function AgentRunsSelectorList({ selectedView, onSelectRun, onSelectSchedule, - onDraftNewRun, + onSelectDraftNewRun, + onDeleteRun, + onDeleteSchedule, className, }: AgentRunsSelectorListProps): React.ReactElement { const [activeListTab, setActiveListTab] = useState<"runs" | "scheduled">( @@ -51,7 +57,7 @@ export default function AgentRunsSelectorList({ ? "agpt-card-selected text-accent" : "") } - onClick={onDraftNewRun} + onClick={onSelectDraftNewRun} > New run @@ -91,7 +97,7 @@ export default function AgentRunsSelectorList({ ? "agpt-card-selected text-accent" : "") } - onClick={onDraftNewRun} + onClick={onSelectDraftNewRun} > New run @@ -102,13 +108,12 @@ export default function AgentRunsSelectorList({ onSelectRun(run.execution_id)} + onDelete={() => onDeleteRun(run)} /> )) : schedules @@ -117,13 +122,12 @@ export default function AgentRunsSelectorList({ onSelectSchedule(schedule)} + onDelete={() => onDeleteSchedule(schedule.id)} /> ))} diff --git a/autogpt_platform/frontend/src/components/agents/agent-schedule-details-view.tsx b/autogpt_platform/frontend/src/components/agents/agent-schedule-details-view.tsx index 2133cea3bf..520188fad4 100644 --- a/autogpt_platform/frontend/src/components/agents/agent-schedule-details-view.tsx +++ b/autogpt_platform/frontend/src/components/agents/agent-schedule-details-view.tsx @@ -1,7 +1,11 @@ "use client"; import React, { useCallback, useMemo } from "react"; -import { GraphMeta, Schedule } from "@/lib/autogpt-server-api"; +import { + GraphExecutionID, + GraphMeta, + Schedule, +} from "@/lib/autogpt-server-api"; import { useBackendAPI } from "@/lib/autogpt-server-api/context"; import type { ButtonAction } from "@/components/agptui/types"; @@ -18,7 +22,7 @@ export default function AgentScheduleDetailsView({ }: { graph: GraphMeta; schedule: Schedule; - onForcedRun: (runID: string) => void; + onForcedRun: (runID: GraphExecutionID) => void; agentActions: ButtonAction[]; }): React.ReactNode { const api = useBackendAPI(); diff --git a/autogpt_platform/frontend/src/components/agents/agent-delete-confirm-dialog.tsx b/autogpt_platform/frontend/src/components/agptui/delete-confirm-dialog.tsx similarity index 57% rename from autogpt_platform/frontend/src/components/agents/agent-delete-confirm-dialog.tsx rename to autogpt_platform/frontend/src/components/agptui/delete-confirm-dialog.tsx index c8934e06eb..7d09502dd2 100644 --- a/autogpt_platform/frontend/src/components/agents/agent-delete-confirm-dialog.tsx +++ b/autogpt_platform/frontend/src/components/agptui/delete-confirm-dialog.tsx @@ -8,25 +8,41 @@ import { DialogTitle, } from "@/components/ui/dialog"; -export default function AgentDeleteConfirmDialog({ +export default function DeleteConfirmDialog({ + entityType, + entityName, open, onOpenChange, onDoDelete, + isIrreversible = true, className, }: { + entityType: string; + entityName?: string; open: boolean; onOpenChange: (open: boolean) => void; onDoDelete: () => void; + isIrreversible?: boolean; className?: string; }): React.ReactNode { + const displayType = entityType + .split(" ") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "); return (

- Delete Agent + + Delete {displayType} {entityName && `"${entityName}"`} + - Are you sure you want to delete this agent?
- This action cannot be undone. + Are you sure you want to delete this {entityType}? + {isIrreversible && ( + +
This action cannot be undone. +
+ )}
diff --git a/autogpt_platform/frontend/src/components/monitor/scheduleTable.tsx b/autogpt_platform/frontend/src/components/monitor/scheduleTable.tsx index e1df96dc2d..2c28bdc1ca 100644 --- a/autogpt_platform/frontend/src/components/monitor/scheduleTable.tsx +++ b/autogpt_platform/frontend/src/components/monitor/scheduleTable.tsx @@ -1,4 +1,4 @@ -import { LibraryAgent, Schedule } from "@/lib/autogpt-server-api"; +import { LibraryAgent, Schedule, ScheduleID } from "@/lib/autogpt-server-api"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; import { @@ -36,7 +36,7 @@ import { Label } from "../ui/label"; interface SchedulesTableProps { schedules: Schedule[]; agents: LibraryAgent[]; - onRemoveSchedule: (scheduleId: string, enabled: boolean) => void; + onRemoveSchedule: (scheduleId: ScheduleID, enabled: boolean) => void; sortColumn: keyof Schedule; sortDirection: "asc" | "desc"; onSort: (column: keyof Schedule) => void; @@ -73,7 +73,7 @@ export const SchedulesTable = ({ return String(bValue).localeCompare(String(aValue)); }); - const handleToggleSchedule = (scheduleId: string, enabled: boolean) => { + const handleToggleSchedule = (scheduleId: ScheduleID, enabled: boolean) => { onRemoveSchedule(scheduleId, enabled); if (!enabled) { toast({ diff --git a/autogpt_platform/frontend/src/hooks/useAgentGraph.tsx b/autogpt_platform/frontend/src/hooks/useAgentGraph.tsx index 41ea169162..1ce8aa389f 100644 --- a/autogpt_platform/frontend/src/hooks/useAgentGraph.tsx +++ b/autogpt_platform/frontend/src/hooks/useAgentGraph.tsx @@ -6,6 +6,7 @@ import BackendAPI, { BlockUIType, formatEdgeID, Graph, + GraphExecutionID, GraphID, NodeExecutionResult, } from "@/lib/autogpt-server-api"; @@ -29,7 +30,7 @@ const ajv = new Ajv({ strict: false, allErrors: true }); export default function useAgentGraph( flowID?: GraphID, flowVersion?: number, - flowExecutionID?: string, + flowExecutionID?: GraphExecutionID, passDataToBeads?: boolean, ) { const { toast } = useToast(); @@ -65,7 +66,7 @@ export default function useAgentGraph( | { request: "run" | "stop"; state: "running" | "stopping" | "error"; - activeExecutionID?: string; + activeExecutionID?: GraphExecutionID; } >({ request: "none", diff --git a/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts b/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts index 32f9f2ebb4..f14d28c757 100644 --- a/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts +++ b/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts @@ -17,6 +17,7 @@ import { Graph, GraphCreatable, GraphExecution, + GraphExecutionID, GraphExecutionMeta, GraphID, GraphMeta, @@ -35,6 +36,7 @@ import { RefundRequest, Schedule, ScheduleCreatable, + ScheduleID, StoreAgentDetails, StoreAgentsResponse, StoreReview, @@ -211,7 +213,7 @@ export default class BackendAPI { id: GraphID, version: number, inputData: { [key: string]: any } = {}, - ): Promise<{ graph_exec_id: string }> { + ): Promise<{ graph_exec_id: GraphExecutionID }> { return this._request("POST", `/graphs/${id}/execute/${version}`, inputData); } @@ -225,7 +227,7 @@ export default class BackendAPI { async getGraphExecutionInfo( graphID: GraphID, - runID: string, + runID: GraphExecutionID, ): Promise { const result = await this._get(`/graphs/${graphID}/executions/${runID}`); result.node_executions = result.node_executions.map( @@ -236,7 +238,7 @@ export default class BackendAPI { async stopGraphExecution( graphID: GraphID, - runID: string, + runID: GraphExecutionID, ): Promise { const result = await this._request( "POST", @@ -248,6 +250,10 @@ export default class BackendAPI { return result; } + async deleteGraphExecution(runID: GraphExecutionID): Promise { + await this._request("DELETE", `/executions/${runID}`); + } + oAuthLogin( provider: string, scopes?: string[], @@ -558,13 +564,9 @@ export default class BackendAPI { }); } - /////////////////////////////////////////// - /////////// INTERNAL FUNCTIONS //////////// - //////////////////////////////??/////////// - - private _get(path: string, query?: Record) { - return this._request("GET", path, query); - } + ////////////////////////////////// + /////////// SCHEDULES //////////// + ////////////////////////////////// async createSchedule(schedule: ScheduleCreatable): Promise { return this._request("POST", `/schedules`, schedule).then( @@ -572,7 +574,7 @@ export default class BackendAPI { ); } - async deleteSchedule(scheduleId: string): Promise<{ id: string }> { + async deleteSchedule(scheduleId: ScheduleID): Promise<{ id: string }> { return this._request("DELETE", `/schedules/${scheduleId}`); } @@ -582,6 +584,14 @@ export default class BackendAPI { ); } + /////////////////////////////////////////// + /////////// INTERNAL FUNCTIONS //////////// + //////////////////////////////??/////////// + + private _get(path: string, query?: Record) { + return this._request("GET", path, query); + } + private async _uploadFile(path: string, file: File): Promise { // Get session with retry logic let token = "no-token-found"; diff --git a/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts b/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts index de0f1da8ba..1d9bfd5c61 100644 --- a/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts +++ b/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts @@ -218,7 +218,7 @@ export type LinkCreatable = Omit & { /* Mirror of backend/data/graph.py:GraphExecutionMeta */ export type GraphExecutionMeta = { - execution_id: string; + execution_id: GraphExecutionID; started_at: number; ended_at: number; cost?: number; @@ -230,6 +230,8 @@ export type GraphExecutionMeta = { preset_id?: string; }; +export type GraphExecutionID = Brand; + /* Mirror of backend/data/graph.py:GraphExecution */ export type GraphExecution = GraphExecutionMeta & { inputs: Record; @@ -287,7 +289,7 @@ export type GraphCreatable = Omit & { id?: string }; export type NodeExecutionResult = { graph_id: GraphID; graph_version: number; - graph_exec_id: string; + graph_exec_id: GraphExecutionID; node_exec_id: string; node_id: string; block_id: string; @@ -624,7 +626,7 @@ export type ProfileDetails = { }; export type Schedule = { - id: string; + id: ScheduleID; name: string; cron: string; user_id: string; @@ -634,6 +636,8 @@ export type Schedule = { next_run_time: Date; }; +export type ScheduleID = Brand; + export type ScheduleCreatable = { cron: string; graph_id: GraphID; @@ -642,7 +646,7 @@ export type ScheduleCreatable = { }; export type MyAgent = { - agent_id: string; + agent_id: GraphID; agent_version: number; agent_name: string; last_edited: string; @@ -706,7 +710,7 @@ export interface CreditTransaction { balance: number; description: string; usage_graph_id: GraphID; - usage_execution_id: string; + usage_execution_id: GraphExecutionID; usage_node_count: number; usage_starting_time: Date; }