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:
Reinier van der Leer
2025-03-05 17:14:59 +01:00
committed by GitHub
parent 37331d09e4
commit 3b53c6953a
18 changed files with 239 additions and 119 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,
@@ -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,
)

View File

@@ -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},

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,
@@ -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 ########################
########################################################

View File

@@ -0,0 +1,6 @@
-- Add isDeleted column to AgentGraphExecution
ALTER TABLE "AgentGraphExecution"
ADD COLUMN "isDeleted"
BOOLEAN
NOT NULL
DEFAULT false;

View File

@@ -289,6 +289,8 @@ model AgentGraphExecution {
updatedAt DateTime? @updatedAt
startedAt DateTime?
isDeleted Boolean @default(false)
executionStatus AgentExecutionStatus @default(COMPLETED)
agentGraphId String

View File

@@ -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>
);

View File

@@ -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));
},

View File

@@ -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(() => {

View File

@@ -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>
))}

View File

@@ -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();

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

@@ -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>

View File

@@ -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();

View File

@@ -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>

View File

@@ -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({

View File

@@ -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",

View File

@@ -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";

View File

@@ -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;
}