implement agent run "Delete" option

This commit is contained in:
Reinier van der Leer
2025-02-26 16:41:50 +01:00
parent af8ea93260
commit e982fe99ac
7 changed files with 70 additions and 60 deletions

View File

@@ -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,
@@ -331,28 +330,14 @@ 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) -> None:
delete_count = await AgentGraphExecution.prisma().delete_many(
where={"id": graph_exec_id, "userId": user_id}
)
if delete_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]:

View File

@@ -591,6 +591,7 @@ 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},

View File

@@ -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,
@@ -630,6 +632,19 @@ async def get_graph_execution(
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 ########################
########################################################

View File

@@ -98,20 +98,30 @@ 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, agent]);
// =========================== ACTIONS ============================
const deleteRun = useCallback(
async (graphExecID: string) => {
await api.deleteGraphExecution(graphExecID);
setAgentRuns(agentRuns.filter((r) => r.execution_id !== graphExecID));
},
[agentRuns, api],
);
const deleteSchedule = useCallback(
async (scheduleID: string) => {
const removedSchedule = await api.deleteSchedule(scheduleID);
setSchedules(schedules.filter((s) => s.id !== removedSchedule.id));
},
[schedules, api],
);
const agentActions: { label: string; callback: () => void }[] = useMemo(
() => [
{
@@ -139,7 +149,9 @@ export default function AgentRunsPage(): React.ReactElement {
selectedView={selectedView}
onSelectRun={selectRun}
onSelectSchedule={selectSchedule}
onDraftNewRun={openRunDraftView}
onSelectDraftNewRun={openRunDraftView}
onDeleteRun={(id) => deleteRun(id)}
onDeleteSchedule={(id) => deleteSchedule(id)}
/>
<div className="flex-1">

View File

@@ -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}
</h3>
{/* <DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-5 w-5 p-0">
<MoreVertical className="h-5 w-5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
// TODO: implement
>
Pin into a template
{/* {onPinAsPreset && (
<DropdownMenuItem onClick={onPinAsPreset}>
Pin as a preset
</DropdownMenuItem>
)} */}
<DropdownMenuItem
// TODO: implement
>
Rename
</DropdownMenuItem>
{/* <DropdownMenuItem onClick={onRename}>Rename</DropdownMenuItem> */}
<DropdownMenuItem
// TODO: implement
>
Delete
</DropdownMenuItem>
<DropdownMenuItem onClick={onDelete}>Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu> */}
</DropdownMenu>
</div>
<p

View File

@@ -23,7 +23,8 @@ interface AgentRunsSelectorListProps {
selectedView: { type: "run" | "schedule"; id?: string };
onSelectRun: (id: string) => void;
onSelectSchedule: (schedule: Schedule) => void;
onDraftNewRun: () => void;
onSelectDraftNewRun: () => void;
onDeleteSchedule: (id: string) => void;
className?: string;
}
@@ -34,7 +35,9 @@ export default function AgentRunsSelectorList({
selectedView,
onSelectRun,
onSelectSchedule,
onDraftNewRun,
onSelectDraftNewRun,
onDeleteRun,
onDeleteSchedule,
className,
}: AgentRunsSelectorListProps): React.ReactElement {
const [activeListTab, setActiveListTab] = useState<"runs" | "scheduled">(
@@ -51,7 +54,7 @@ export default function AgentRunsSelectorList({
? "agpt-card-selected text-accent"
: "")
}
onClick={onDraftNewRun}
onClick={onSelectDraftNewRun}
>
<Plus className="h-6 w-6" />
<span>New run</span>
@@ -91,7 +94,7 @@ export default function AgentRunsSelectorList({
? "agpt-card-selected text-accent"
: "")
}
onClick={onDraftNewRun}
onClick={onSelectDraftNewRun}
>
<Plus className="h-6 w-6" />
<span>New run</span>
@@ -102,13 +105,12 @@ export default function AgentRunsSelectorList({
<AgentRunSummaryCard
className="h-28 w-72 lg:h-32 xl:w-80"
key={i}
agentID={run.graph_id}
agentRunID={run.execution_id}
status={agentRunStatusMap[run.status]}
title={agent.name}
timestamp={run.started_at}
selected={selectedView.id === run.execution_id}
onClick={() => onSelectRun(run.execution_id)}
onDelete={() => onDeleteRun(run.execution_id)}
/>
))
: schedules
@@ -117,13 +119,12 @@ export default function AgentRunsSelectorList({
<AgentRunSummaryCard
className="h-28 w-72 lg:h-32 xl:w-80"
key={i}
agentID={schedule.graph_id}
agentRunID={schedule.id}
status="scheduled"
title={schedule.name}
timestamp={schedule.next_run_time}
selected={selectedView.id === schedule.id}
onClick={() => onSelectSchedule(schedule)}
onDelete={() => onDeleteSchedule(schedule.id)}
/>
))}
</div>

View File

@@ -246,6 +246,10 @@ export default class BackendAPI {
return result;
}
async deleteGraphExecution(runID: string): Promise<void> {
await this._request("DELETE", `/executions/${runID}`);
}
oAuthLogin(
provider: string,
scopes?: string[],