mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-09 15:17:59 -05:00
feat(platform): Agent Run "Stop" + "Delete" functionality (#9547)
- 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>
This commit is contained in:
committed by
GitHub
parent
37331d09e4
commit
3b53c6953a
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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 ########################
|
||||
########################################################
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
-- Add isDeleted column to AgentGraphExecution
|
||||
ALTER TABLE "AgentGraphExecution"
|
||||
ADD COLUMN "isDeleted"
|
||||
BOOLEAN
|
||||
NOT NULL
|
||||
DEFAULT false;
|
||||
@@ -289,6 +289,8 @@ model AgentGraphExecution {
|
||||
updatedAt DateTime? @updatedAt
|
||||
startedAt DateTime?
|
||||
|
||||
isDeleted Boolean @default(false)
|
||||
|
||||
executionStatus AgentExecutionStatus @default(COMPLETED)
|
||||
|
||||
agentGraphId String
|
||||
|
||||
@@ -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<GraphMeta | null>(null);
|
||||
const [agent, setAgent] = useState<LibraryAgent | null>(null);
|
||||
const [agentRuns, setAgentRuns] = useState<GraphExecutionMeta[]>([]);
|
||||
const [schedules, setSchedules] = useState<Schedule[]>([]);
|
||||
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<boolean>(true);
|
||||
const [agentDeleteDialogOpen, setAgentDeleteDialogOpen] =
|
||||
useState<boolean>(false);
|
||||
const [confirmingDeleteAgentRun, setConfirmingDeleteAgentRun] =
|
||||
useState<GraphExecutionMeta | null>(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)}
|
||||
/>
|
||||
|
||||
<div className="flex-1">
|
||||
@@ -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) || <p>Loading...</p>}
|
||||
|
||||
<AgentDeleteConfirmDialog
|
||||
<DeleteConfirmDialog
|
||||
entityType="agent"
|
||||
open={agentDeleteDialogOpen}
|
||||
onOpenChange={setAgentDeleteDialogOpen}
|
||||
onDoDelete={() =>
|
||||
@@ -209,6 +239,15 @@ export default function AgentRunsPage(): React.ReactElement {
|
||||
.then(() => router.push("/library"))
|
||||
}
|
||||
/>
|
||||
|
||||
<DeleteConfirmDialog
|
||||
entityType="agent run"
|
||||
open={!!confirmingDeleteAgentRun}
|
||||
onOpenChange={(open) => !open && setConfirmingDeleteAgentRun(null)}
|
||||
onDoDelete={() =>
|
||||
confirmingDeleteAgentRun && deleteRun(confirmingDeleteAgentRun)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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));
|
||||
},
|
||||
|
||||
@@ -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<string | undefined>();
|
||||
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(() => {
|
||||
|
||||
@@ -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({
|
||||
<div className="flex flex-col gap-3">
|
||||
<h3 className="text-sm font-medium">Run actions</h3>
|
||||
{runActions.map((action, i) => (
|
||||
<Button key={i} variant="outline" onClick={action.callback}>
|
||||
<Button
|
||||
key={i}
|
||||
variant={action.variant ?? "outline"}
|
||||
onClick={action.callback}
|
||||
>
|
||||
{action.label}
|
||||
</Button>
|
||||
))}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,9 +4,11 @@ import { Plus } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
GraphExecutionID,
|
||||
GraphExecutionMeta,
|
||||
LibraryAgent,
|
||||
Schedule,
|
||||
ScheduleID,
|
||||
} from "@/lib/autogpt-server-api";
|
||||
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
@@ -21,9 +23,11 @@ interface AgentRunsSelectorListProps {
|
||||
agentRuns: GraphExecutionMeta[];
|
||||
schedules: Schedule[];
|
||||
selectedView: { type: "run" | "schedule"; id?: string };
|
||||
onSelectRun: (id: string) => 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}
|
||||
>
|
||||
<Plus className="h-6 w-6" />
|
||||
<span>New run</span>
|
||||
@@ -91,7 +97,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 +108,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)}
|
||||
/>
|
||||
))
|
||||
: schedules
|
||||
@@ -117,13 +122,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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className={className}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Delete Agent</DialogTitle>
|
||||
<DialogTitle>
|
||||
Delete {displayType} {entityName && `"${entityName}"`}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Are you sure you want to delete this agent? <br />
|
||||
This action cannot be undone.
|
||||
Are you sure you want to delete this {entityType}?
|
||||
{isIrreversible && (
|
||||
<b>
|
||||
<br /> This action cannot be undone.
|
||||
</b>
|
||||
)}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
@@ -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({
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<GraphExecution> {
|
||||
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<GraphExecution> {
|
||||
const result = await this._request(
|
||||
"POST",
|
||||
@@ -248,6 +250,10 @@ export default class BackendAPI {
|
||||
return result;
|
||||
}
|
||||
|
||||
async deleteGraphExecution(runID: GraphExecutionID): Promise<void> {
|
||||
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<string, any>) {
|
||||
return this._request("GET", path, query);
|
||||
}
|
||||
//////////////////////////////////
|
||||
/////////// SCHEDULES ////////////
|
||||
//////////////////////////////////
|
||||
|
||||
async createSchedule(schedule: ScheduleCreatable): Promise<Schedule> {
|
||||
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<string, any>) {
|
||||
return this._request("GET", path, query);
|
||||
}
|
||||
|
||||
private async _uploadFile(path: string, file: File): Promise<string> {
|
||||
// Get session with retry logic
|
||||
let token = "no-token-found";
|
||||
|
||||
@@ -218,7 +218,7 @@ export type LinkCreatable = Omit<Link, "id" | "is_static"> & {
|
||||
|
||||
/* 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<string, "GraphExecutionID">;
|
||||
|
||||
/* Mirror of backend/data/graph.py:GraphExecution */
|
||||
export type GraphExecution = GraphExecutionMeta & {
|
||||
inputs: Record<string, any>;
|
||||
@@ -287,7 +289,7 @@ export type GraphCreatable = Omit<GraphUpdateable, "id"> & { 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<string, "ScheduleID">;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user