mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-09 15:17:59 -05:00
feat(platform): Library Agent "Delete" functionality (#9546)
- Resolves #9545 ### Changes 🏗️ - fix(platform): Make "Delete" button on `/monitoring` soft-delete the `LibraryAgent` instead of hard-deleting the corresponding `AgentGraph` - feat(frontend): Add "Delete agent" button on `/library/agents/[id]` Technical: - fix(backend): Accept partial input on `update_library_agent` endpoint - feat(backend): Add `GET /api/library/agents/{library_agent_id}` endpoint - refactor(frontend): Replace use of `GraphMeta` by `LibraryAgent` where possible on `/library/agents/[id]` Also, out of scope but important: - fix(frontend): Hide buttons that require direct graph access depending on `agent.can_access_graph` ### 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: - Go to the Library and select an agent - [x] -> `/library/agents/[id]` should load normally - Save this URL for later (especially the ID)! - Click "Delete agent" on `/library/agents/[id]` - [x] -> should show a confirmation dialog - Click "Delete" to confirm - [x] -> should redirect back to `/library` - [x] -> deleted agent should no longer be listed in the Library - Click "Delete agent" on `/monitoring` - [x] -> should show a confirmation dialog - Click "Delete" to confirm - [x] -> agent should disappear from agent list - [x] -> views should reset / deselect the deleted agent where applicable
This commit is contained in:
committed by
GitHub
parent
ef00ab51e0
commit
27a5635607
@@ -17,7 +17,7 @@ import backend.server.v2.store.media as store_media
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def get_library_agents(
|
||||
async def list_library_agents(
|
||||
user_id: str,
|
||||
search_term: Optional[str] = None,
|
||||
sort_by: library_model.LibraryAgentSort = library_model.LibraryAgentSort.UPDATED_AT,
|
||||
@@ -125,6 +125,49 @@ async def get_library_agents(
|
||||
raise store_exceptions.DatabaseError("Failed to fetch library agents") from e
|
||||
|
||||
|
||||
async def get_library_agent(id: str, user_id: str) -> library_model.LibraryAgent:
|
||||
"""
|
||||
Get a specific agent from the user's library.
|
||||
|
||||
Args:
|
||||
library_agent_id: ID of the library agent to retrieve.
|
||||
user_id: ID of the authenticated user.
|
||||
|
||||
Returns:
|
||||
The requested LibraryAgent.
|
||||
|
||||
Raises:
|
||||
AgentNotFoundError: If the specified agent does not exist.
|
||||
DatabaseError: If there's an error during retrieval.
|
||||
"""
|
||||
try:
|
||||
library_agent = await prisma.models.LibraryAgent.prisma().find_first(
|
||||
where={
|
||||
"id": id,
|
||||
"userId": user_id,
|
||||
"isDeleted": False,
|
||||
},
|
||||
include={
|
||||
"Agent": {
|
||||
"include": {
|
||||
**backend.data.includes.AGENT_GRAPH_INCLUDE,
|
||||
"AgentGraphExecution": {"where": {"userId": user_id}},
|
||||
}
|
||||
},
|
||||
"Creator": True,
|
||||
},
|
||||
)
|
||||
|
||||
if not library_agent:
|
||||
raise store_exceptions.AgentNotFoundError(f"Library agent #{id} not found")
|
||||
|
||||
return library_model.LibraryAgent.from_db(library_agent)
|
||||
|
||||
except prisma.errors.PrismaError as e:
|
||||
logger.error(f"Database error fetching library agent: {e}")
|
||||
raise store_exceptions.DatabaseError("Failed to fetch library agent") from e
|
||||
|
||||
|
||||
async def create_library_agent(
|
||||
agent_id: str,
|
||||
agent_version: int,
|
||||
@@ -249,10 +292,10 @@ async def update_agent_version_in_library(
|
||||
async def update_library_agent(
|
||||
library_agent_id: str,
|
||||
user_id: str,
|
||||
auto_update_version: bool = False,
|
||||
is_favorite: bool = False,
|
||||
is_archived: bool = False,
|
||||
is_deleted: bool = False,
|
||||
auto_update_version: Optional[bool] = None,
|
||||
is_favorite: Optional[bool] = None,
|
||||
is_archived: Optional[bool] = None,
|
||||
is_deleted: Optional[bool] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Updates the specified LibraryAgent record.
|
||||
@@ -273,15 +316,19 @@ async def update_library_agent(
|
||||
f"auto_update_version={auto_update_version}, is_favorite={is_favorite}, "
|
||||
f"is_archived={is_archived}, is_deleted={is_deleted}"
|
||||
)
|
||||
update_fields: prisma.types.LibraryAgentUpdateManyMutationInput = {}
|
||||
if auto_update_version is not None:
|
||||
update_fields["useGraphIsActiveVersion"] = auto_update_version
|
||||
if is_favorite is not None:
|
||||
update_fields["isFavorite"] = is_favorite
|
||||
if is_archived is not None:
|
||||
update_fields["isArchived"] = is_archived
|
||||
if is_deleted is not None:
|
||||
update_fields["isDeleted"] = is_deleted
|
||||
|
||||
try:
|
||||
await prisma.models.LibraryAgent.prisma().update_many(
|
||||
where={"id": library_agent_id, "userId": user_id},
|
||||
data={
|
||||
"useGraphIsActiveVersion": auto_update_version,
|
||||
"isFavorite": is_favorite,
|
||||
"isArchived": is_archived,
|
||||
"isDeleted": is_deleted,
|
||||
},
|
||||
where={"id": library_agent_id, "userId": user_id}, data=update_fields
|
||||
)
|
||||
except prisma.errors.PrismaError as e:
|
||||
logger.error(f"Database error updating library agent: {str(e)}")
|
||||
|
||||
@@ -74,7 +74,7 @@ async def test_get_library_agents(mocker):
|
||||
mock_library_agent.return_value.count = mocker.AsyncMock(return_value=1)
|
||||
|
||||
# Call function
|
||||
result = await db.get_library_agents("test-user")
|
||||
result = await db.list_library_agents("test-user")
|
||||
|
||||
# Verify results
|
||||
assert len(result.agents) == 1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import datetime
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
from typing import Any, Optional
|
||||
|
||||
import prisma.enums
|
||||
import prisma.models
|
||||
@@ -245,11 +245,15 @@ class LibraryAgentUpdateRequest(pydantic.BaseModel):
|
||||
archiving, or deleting.
|
||||
"""
|
||||
|
||||
auto_update_version: bool = pydantic.Field(
|
||||
False, description="Auto-update the agent version"
|
||||
auto_update_version: Optional[bool] = pydantic.Field(
|
||||
default=None, description="Auto-update the agent version"
|
||||
)
|
||||
is_favorite: bool = pydantic.Field(
|
||||
False, description="Mark the agent as a favorite"
|
||||
is_favorite: Optional[bool] = pydantic.Field(
|
||||
default=None, description="Mark the agent as a favorite"
|
||||
)
|
||||
is_archived: Optional[bool] = pydantic.Field(
|
||||
default=None, description="Archive the agent"
|
||||
)
|
||||
is_deleted: Optional[bool] = pydantic.Field(
|
||||
default=None, description="Delete the agent"
|
||||
)
|
||||
is_archived: bool = pydantic.Field(False, description="Archive the agent")
|
||||
is_deleted: bool = pydantic.Field(False, description="Delete the agent")
|
||||
|
||||
@@ -24,14 +24,14 @@ router = APIRouter(
|
||||
500: {"description": "Server error", "content": {"application/json": {}}},
|
||||
},
|
||||
)
|
||||
async def get_library_agents(
|
||||
async def list_library_agents(
|
||||
user_id: str = Depends(autogpt_auth_lib.depends.get_user_id),
|
||||
search_term: Optional[str] = Query(
|
||||
None, description="Search term to filter agents"
|
||||
),
|
||||
sort_by: library_model.LibraryAgentSort = Query(
|
||||
library_model.LibraryAgentSort.UPDATED_AT,
|
||||
description="Sort results by criteria",
|
||||
description="Criteria to sort results by",
|
||||
),
|
||||
page: int = Query(
|
||||
1,
|
||||
@@ -62,7 +62,7 @@ async def get_library_agents(
|
||||
HTTPException: If a server/database error occurs.
|
||||
"""
|
||||
try:
|
||||
return await library_db.get_library_agents(
|
||||
return await library_db.list_library_agents(
|
||||
user_id=user_id,
|
||||
search_term=search_term,
|
||||
sort_by=sort_by,
|
||||
@@ -77,6 +77,14 @@ async def get_library_agents(
|
||||
) from e
|
||||
|
||||
|
||||
@router.get("/{library_agent_id}")
|
||||
async def get_library_agent(
|
||||
library_agent_id: str,
|
||||
user_id: str = Depends(autogpt_auth_lib.depends.get_user_id),
|
||||
) -> library_model.LibraryAgent:
|
||||
return await library_db.get_library_agent(id=library_agent_id, user_id=user_id)
|
||||
|
||||
|
||||
@router.post(
|
||||
"",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
|
||||
@@ -6,22 +6,26 @@ import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||
import {
|
||||
GraphExecution,
|
||||
GraphExecutionMeta,
|
||||
GraphID,
|
||||
GraphMeta,
|
||||
LibraryAgent,
|
||||
LibraryAgentID,
|
||||
Schedule,
|
||||
} from "@/lib/autogpt-server-api";
|
||||
|
||||
import type { ButtonAction } from "@/components/agptui/types";
|
||||
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: GraphID } = useParams();
|
||||
const { id: agentID }: { id: LibraryAgentID } = useParams();
|
||||
const router = useRouter();
|
||||
const api = useBackendAPI();
|
||||
|
||||
const [agent, setAgent] = useState<GraphMeta | null>(null);
|
||||
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<{
|
||||
@@ -35,6 +39,8 @@ export default function AgentRunsPage(): React.ReactElement {
|
||||
null,
|
||||
);
|
||||
const [isFirstLoad, setIsFirstLoad] = useState<boolean>(true);
|
||||
const [agentDeleteDialogOpen, setAgentDeleteDialogOpen] =
|
||||
useState<boolean>(false);
|
||||
|
||||
const openRunDraftView = useCallback(() => {
|
||||
selectView({ type: "run" });
|
||||
@@ -50,22 +56,28 @@ export default function AgentRunsPage(): React.ReactElement {
|
||||
}, []);
|
||||
|
||||
const fetchAgents = useCallback(() => {
|
||||
api.getGraph(agentID).then(setAgent);
|
||||
api.getGraphExecutions(agentID).then((agentRuns) => {
|
||||
const sortedRuns = agentRuns.toSorted(
|
||||
(a, b) => b.started_at - a.started_at,
|
||||
);
|
||||
setAgentRuns(sortedRuns);
|
||||
api.getLibraryAgent(agentID).then((agent) => {
|
||||
setAgent(agent);
|
||||
|
||||
if (!selectedView.id && isFirstLoad && sortedRuns.length > 0) {
|
||||
// only for first load or first execution
|
||||
setIsFirstLoad(false);
|
||||
selectView({ type: "run", id: sortedRuns[0].execution_id });
|
||||
setSelectedRun(sortedRuns[0]);
|
||||
}
|
||||
api.getGraph(agent.agent_id).then(setGraph);
|
||||
api.getGraphExecutions(agent.agent_id).then((agentRuns) => {
|
||||
const sortedRuns = agentRuns.toSorted(
|
||||
(a, b) => b.started_at - a.started_at,
|
||||
);
|
||||
setAgentRuns(sortedRuns);
|
||||
|
||||
if (!selectedView.id && isFirstLoad && sortedRuns.length > 0) {
|
||||
// only for first load or first execution
|
||||
setIsFirstLoad(false);
|
||||
selectView({ type: "run", id: sortedRuns[0].execution_id });
|
||||
setSelectedRun(sortedRuns[0]);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (selectedView.type == "run" && selectedView.id) {
|
||||
api.getGraphExecutionInfo(agentID, selectedView.id).then(setSelectedRun);
|
||||
if (selectedView.type == "run" && selectedView.id && agent) {
|
||||
api
|
||||
.getGraphExecutionInfo(agent.agent_id, selectedView.id)
|
||||
.then(setSelectedRun);
|
||||
}
|
||||
}, [api, agentID, selectedView, isFirstLoad]);
|
||||
|
||||
@@ -75,7 +87,7 @@ export default function AgentRunsPage(): React.ReactElement {
|
||||
|
||||
// load selectedRun based on selectedView
|
||||
useEffect(() => {
|
||||
if (selectedView.type != "run" || !selectedView.id) return;
|
||||
if (selectedView.type != "run" || !selectedView.id || !agent) return;
|
||||
|
||||
// pull partial data from "cache" while waiting for the rest to load
|
||||
if (selectedView.id !== selectedRun?.execution_id) {
|
||||
@@ -84,15 +96,19 @@ export default function AgentRunsPage(): React.ReactElement {
|
||||
);
|
||||
}
|
||||
|
||||
api.getGraphExecutionInfo(agentID, selectedView.id).then(setSelectedRun);
|
||||
}, [api, selectedView, agentRuns, agentID]);
|
||||
api
|
||||
.getGraphExecutionInfo(agent.agent_id, selectedView.id)
|
||||
.then(setSelectedRun);
|
||||
}, [api, selectedView, agentID]);
|
||||
|
||||
const fetchSchedules = useCallback(async () => {
|
||||
if (!agent) return;
|
||||
|
||||
// TODO: filter in backend - https://github.com/Significant-Gravitas/AutoGPT/issues/9183
|
||||
setSchedules(
|
||||
(await api.listSchedules()).filter((s) => s.graph_id == agentID),
|
||||
(await api.listSchedules()).filter((s) => s.graph_id == agent.agent_id),
|
||||
);
|
||||
}, [api, agentID]);
|
||||
}, [api, agent]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchSchedules();
|
||||
@@ -110,19 +126,24 @@ export default function AgentRunsPage(): React.ReactElement {
|
||||
useEffect(() => {
|
||||
const intervalId = setInterval(() => fetchAgents(), 5000);
|
||||
return () => clearInterval(intervalId);
|
||||
}, [fetchAgents, agent]);
|
||||
}, [fetchAgents]);
|
||||
|
||||
const agentActions: { label: string; callback: () => void }[] = useMemo(
|
||||
const agentActions: ButtonAction[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
label: "Open in builder",
|
||||
callback: () => agent && router.push(`/build?flowID=${agent.id}`),
|
||||
callback: () => agent && router.push(`/build?flowID=${agent.agent_id}`),
|
||||
},
|
||||
{
|
||||
label: "Delete agent",
|
||||
variant: "destructive",
|
||||
callback: () => setAgentDeleteDialogOpen(true),
|
||||
},
|
||||
],
|
||||
[agent, router],
|
||||
);
|
||||
|
||||
if (!agent) {
|
||||
if (!agent || !graph) {
|
||||
/* TODO: implement loading indicators / skeleton page */
|
||||
return <span>Loading...</span>;
|
||||
}
|
||||
@@ -156,27 +177,38 @@ export default function AgentRunsPage(): React.ReactElement {
|
||||
{(selectedView.type == "run" && selectedView.id ? (
|
||||
selectedRun && (
|
||||
<AgentRunDetailsView
|
||||
agent={agent}
|
||||
graph={graph}
|
||||
run={selectedRun}
|
||||
agentActions={agentActions}
|
||||
/>
|
||||
)
|
||||
) : selectedView.type == "run" ? (
|
||||
<AgentRunDraftView
|
||||
agent={agent}
|
||||
graph={graph}
|
||||
onRun={(runID) => selectRun(runID)}
|
||||
agentActions={agentActions}
|
||||
/>
|
||||
) : selectedView.type == "schedule" ? (
|
||||
selectedSchedule && (
|
||||
<AgentScheduleDetailsView
|
||||
agent={agent}
|
||||
graph={graph}
|
||||
schedule={selectedSchedule}
|
||||
onForcedRun={(runID) => selectRun(runID)}
|
||||
agentActions={agentActions}
|
||||
/>
|
||||
)
|
||||
) : null) || <p>Loading...</p>}
|
||||
|
||||
<AgentDeleteConfirmDialog
|
||||
open={agentDeleteDialogOpen}
|
||||
onOpenChange={setAgentDeleteDialogOpen}
|
||||
onDoDelete={() =>
|
||||
agent &&
|
||||
api
|
||||
.updateLibraryAgent(agent.id, { is_deleted: true })
|
||||
.then(() => router.push("/library"))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { Button } from "@/components/agptui/Button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
|
||||
export default function AgentDeleteConfirmDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
onDoDelete,
|
||||
className,
|
||||
}: {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onDoDelete: () => void;
|
||||
className?: string;
|
||||
}): React.ReactNode {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className={className}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Delete Agent</DialogTitle>
|
||||
<DialogDescription>
|
||||
Are you sure you want to delete this agent? <br />
|
||||
This action cannot be undone.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button onClick={() => onOpenChange(false)}>Cancel</Button>
|
||||
<Button variant="destructive" onClick={onDoDelete}>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
GraphMeta,
|
||||
} from "@/lib/autogpt-server-api";
|
||||
|
||||
import type { ButtonAction } from "@/components/agptui/types";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/agptui/Button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
@@ -19,13 +20,13 @@ import {
|
||||
} from "@/components/agents/agent-run-status-chip";
|
||||
|
||||
export default function AgentRunDetailsView({
|
||||
agent,
|
||||
graph,
|
||||
run,
|
||||
agentActions,
|
||||
}: {
|
||||
agent: GraphMeta;
|
||||
graph: GraphMeta;
|
||||
run: GraphExecution | GraphExecutionMeta;
|
||||
agentActions: { label: string; callback: () => void }[];
|
||||
agentActions: ButtonAction[];
|
||||
}): React.ReactNode {
|
||||
const api = useBackendAPI();
|
||||
|
||||
@@ -64,25 +65,25 @@ export default function AgentRunDetailsView({
|
||||
Object.entries(run.inputs).map(([k, v]) => [
|
||||
k,
|
||||
{
|
||||
title: agent.input_schema.properties[k].title,
|
||||
// type: agent.input_schema.properties[k].type, // TODO: implement typed graph inputs
|
||||
title: graph.input_schema.properties[k].title,
|
||||
// type: graph.input_schema.properties[k].type, // TODO: implement typed graph inputs
|
||||
value: v,
|
||||
},
|
||||
]),
|
||||
);
|
||||
}, [agent, run]);
|
||||
}, [graph, run]);
|
||||
|
||||
const runAgain = useCallback(
|
||||
() =>
|
||||
agentRunInputs &&
|
||||
api.executeGraph(
|
||||
agent.id,
|
||||
agent.version,
|
||||
graph.id,
|
||||
graph.version,
|
||||
Object.fromEntries(
|
||||
Object.entries(agentRunInputs).map(([k, v]) => [k, v.value]),
|
||||
),
|
||||
),
|
||||
[api, agent, agentRunInputs],
|
||||
[api, graph, agentRunInputs],
|
||||
);
|
||||
|
||||
const agentRunOutputs:
|
||||
@@ -100,13 +101,13 @@ export default function AgentRunDetailsView({
|
||||
Object.entries(run.outputs).map(([k, v]) => [
|
||||
k,
|
||||
{
|
||||
title: agent.output_schema.properties[k].title,
|
||||
title: graph.output_schema.properties[k].title,
|
||||
/* type: agent.output_schema.properties[k].type */
|
||||
values: v,
|
||||
},
|
||||
]),
|
||||
);
|
||||
}, [agent, run, runStatus]);
|
||||
}, [graph, run, runStatus]);
|
||||
|
||||
const runActions: { label: string; callback: () => void }[] = useMemo(
|
||||
() => [{ label: "Run again", callback: () => runAgain() }],
|
||||
@@ -198,7 +199,11 @@ export default function AgentRunDetailsView({
|
||||
<div className="flex flex-col gap-3">
|
||||
<h3 className="text-sm font-medium">Agent actions</h3>
|
||||
{agentActions.map((action, i) => (
|
||||
<Button key={i} variant="outline" onClick={action.callback}>
|
||||
<Button
|
||||
key={i}
|
||||
variant={action.variant ?? "outline"}
|
||||
onClick={action.callback}
|
||||
>
|
||||
{action.label}
|
||||
</Button>
|
||||
))}
|
||||
|
||||
@@ -4,30 +4,31 @@ import React, { useCallback, useMemo, useState } from "react";
|
||||
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||
import { GraphMeta } from "@/lib/autogpt-server-api";
|
||||
|
||||
import type { ButtonAction } from "@/components/agptui/types";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button, ButtonProps } from "@/components/agptui/Button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
||||
export default function AgentRunDraftView({
|
||||
agent,
|
||||
graph,
|
||||
onRun,
|
||||
agentActions,
|
||||
}: {
|
||||
agent: GraphMeta;
|
||||
graph: GraphMeta;
|
||||
onRun: (runID: string) => void;
|
||||
agentActions: { label: string; callback: () => void }[];
|
||||
agentActions: ButtonAction[];
|
||||
}): React.ReactNode {
|
||||
const api = useBackendAPI();
|
||||
|
||||
const agentInputs = agent.input_schema.properties;
|
||||
const agentInputs = graph.input_schema.properties;
|
||||
const [inputValues, setInputValues] = useState<Record<string, any>>({});
|
||||
|
||||
const doRun = useCallback(
|
||||
() =>
|
||||
api
|
||||
.executeGraph(agent.id, agent.version, inputValues)
|
||||
.executeGraph(graph.id, graph.version, inputValues)
|
||||
.then((newRun) => onRun(newRun.graph_exec_id)),
|
||||
[api, agent, inputValues, onRun],
|
||||
[api, graph, inputValues, onRun],
|
||||
);
|
||||
|
||||
const runActions: {
|
||||
@@ -87,7 +88,11 @@ export default function AgentRunDraftView({
|
||||
<div className="flex flex-col gap-3">
|
||||
<h3 className="text-sm font-medium">Agent actions</h3>
|
||||
{agentActions.map((action, i) => (
|
||||
<Button key={i} variant="outline" onClick={action.callback}>
|
||||
<Button
|
||||
key={i}
|
||||
variant={action.variant ?? "outline"}
|
||||
onClick={action.callback}
|
||||
>
|
||||
{action.label}
|
||||
</Button>
|
||||
))}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Plus } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
GraphExecutionMeta,
|
||||
GraphMeta,
|
||||
LibraryAgent,
|
||||
Schedule,
|
||||
} from "@/lib/autogpt-server-api";
|
||||
|
||||
@@ -17,7 +17,7 @@ import { agentRunStatusMap } from "@/components/agents/agent-run-status-chip";
|
||||
import AgentRunSummaryCard from "@/components/agents/agent-run-summary-card";
|
||||
|
||||
interface AgentRunsSelectorListProps {
|
||||
agent: GraphMeta;
|
||||
agent: LibraryAgent;
|
||||
agentRuns: GraphExecutionMeta[];
|
||||
schedules: Schedule[];
|
||||
selectedView: { type: "run" | "schedule"; id?: string };
|
||||
@@ -74,7 +74,7 @@ export default function AgentRunsSelectorList({
|
||||
>
|
||||
<span>Scheduled</span>
|
||||
<span className="text-neutral-600">
|
||||
{schedules.filter((s) => s.graph_id === agent.id).length}
|
||||
{schedules.filter((s) => s.graph_id === agent.agent_id).length}
|
||||
</span>
|
||||
</Badge>
|
||||
</div>
|
||||
@@ -112,7 +112,7 @@ export default function AgentRunsSelectorList({
|
||||
/>
|
||||
))
|
||||
: schedules
|
||||
.filter((schedule) => schedule.graph_id === agent.id)
|
||||
.filter((schedule) => schedule.graph_id === agent.agent_id)
|
||||
.map((schedule, i) => (
|
||||
<AgentRunSummaryCard
|
||||
className="h-28 w-72 lg:h-32 xl:w-80"
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
"use client";
|
||||
import React, { useCallback, useMemo } from "react";
|
||||
|
||||
import { BlockIOSubType, GraphMeta, Schedule } from "@/lib/autogpt-server-api";
|
||||
import { GraphMeta, Schedule } from "@/lib/autogpt-server-api";
|
||||
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||
|
||||
import type { ButtonAction } from "@/components/agptui/types";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { AgentRunStatus } from "@/components/agents/agent-run-status-chip";
|
||||
import { Button } from "@/components/agptui/Button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
||||
export default function AgentScheduleDetailsView({
|
||||
agent,
|
||||
graph,
|
||||
schedule,
|
||||
onForcedRun,
|
||||
agentActions,
|
||||
}: {
|
||||
agent: GraphMeta;
|
||||
graph: GraphMeta;
|
||||
schedule: Schedule;
|
||||
onForcedRun: (runID: string) => void;
|
||||
agentActions: { label: string; callback: () => void }[];
|
||||
agentActions: ButtonAction[];
|
||||
}): React.ReactNode {
|
||||
const api = useBackendAPI();
|
||||
|
||||
@@ -50,20 +51,20 @@ export default function AgentScheduleDetailsView({
|
||||
Object.entries(schedule.input_data).map(([k, v]) => [
|
||||
k,
|
||||
{
|
||||
title: agent.input_schema.properties[k].title,
|
||||
title: graph.input_schema.properties[k].title,
|
||||
/* TODO: type: agent.input_schema.properties[k].type */
|
||||
value: v,
|
||||
},
|
||||
]),
|
||||
);
|
||||
}, [agent, schedule]);
|
||||
}, [graph, schedule]);
|
||||
|
||||
const runNow = useCallback(
|
||||
() =>
|
||||
api
|
||||
.executeGraph(agent.id, agent.version, schedule.input_data)
|
||||
.executeGraph(graph.id, graph.version, schedule.input_data)
|
||||
.then((run) => onForcedRun(run.graph_exec_id)),
|
||||
[api, agent, schedule, onForcedRun],
|
||||
[api, graph, schedule, onForcedRun],
|
||||
);
|
||||
|
||||
const runActions: { label: string; callback: () => void }[] = useMemo(
|
||||
@@ -129,7 +130,11 @@ export default function AgentScheduleDetailsView({
|
||||
<div className="flex flex-col gap-3">
|
||||
<h3 className="text-sm font-medium">Agent actions</h3>
|
||||
{agentActions.map((action, i) => (
|
||||
<Button key={i} variant="outline" onClick={action.callback}>
|
||||
<Button
|
||||
key={i}
|
||||
variant={action.variant ?? "outline"}
|
||||
onClick={action.callback}
|
||||
>
|
||||
{action.label}
|
||||
</Button>
|
||||
))}
|
||||
|
||||
7
autogpt_platform/frontend/src/components/agptui/types.ts
Normal file
7
autogpt_platform/frontend/src/components/agptui/types.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { ButtonProps } from "@/components/agptui/Button";
|
||||
|
||||
export type ButtonAction = {
|
||||
label: string;
|
||||
variant?: ButtonProps["variant"];
|
||||
callback: () => void;
|
||||
};
|
||||
@@ -17,7 +17,7 @@ export default function LibraryAgentCard({
|
||||
agent: LibraryAgent;
|
||||
}): React.ReactNode {
|
||||
return (
|
||||
<Link href={`/library/agents/${agent_id}`}>
|
||||
<Link href={`/library/agents/${id}`}>
|
||||
<div className="inline-flex w-full max-w-[434px] cursor-pointer flex-col items-start justify-start gap-2.5 rounded-[26px] bg-white transition-all duration-300 hover:shadow-lg dark:bg-transparent dark:hover:shadow-gray-700">
|
||||
<div className="relative h-[200px] w-full overflow-hidden rounded-[20px]">
|
||||
{!image_url ? (
|
||||
|
||||
@@ -227,32 +227,36 @@ export const FlowInfo: React.FC<
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
<Link
|
||||
className={buttonVariants({ variant: "default" })}
|
||||
href={`/build?flowID=${flow.agent_id}&flowVersion=${flow.agent_version}`}
|
||||
>
|
||||
<Pencil2Icon className="mr-2" />
|
||||
Open in Builder
|
||||
</Link>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="px-2.5"
|
||||
title="Export to a JSON-file"
|
||||
data-testid="export-button"
|
||||
onClick={async () =>
|
||||
exportAsJSONFile(
|
||||
safeCopyGraph(
|
||||
flowVersions!.find(
|
||||
(v) => v.version == selectedFlowVersion!.version,
|
||||
)!,
|
||||
await api.getBlocks(),
|
||||
),
|
||||
`${flow.name}_v${selectedFlowVersion!.version}.json`,
|
||||
)
|
||||
}
|
||||
>
|
||||
<ExitIcon className="mr-2" /> Export
|
||||
</Button>
|
||||
{flow.can_access_graph && (
|
||||
<Link
|
||||
className={buttonVariants({ variant: "default" })}
|
||||
href={`/build?flowID=${flow.agent_id}&flowVersion=${flow.agent_version}`}
|
||||
>
|
||||
<Pencil2Icon className="mr-2" />
|
||||
Open in Builder
|
||||
</Link>
|
||||
)}
|
||||
{flow.can_access_graph && (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="px-2.5"
|
||||
title="Export to a JSON-file"
|
||||
data-testid="export-button"
|
||||
onClick={async () =>
|
||||
exportAsJSONFile(
|
||||
safeCopyGraph(
|
||||
flowVersions!.find(
|
||||
(v) => v.version == selectedFlowVersion!.version,
|
||||
)!,
|
||||
await api.getBlocks(),
|
||||
),
|
||||
`${flow.name}_v${selectedFlowVersion!.version}.json`,
|
||||
)
|
||||
}
|
||||
>
|
||||
<ExitIcon className="mr-2" /> Export
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="bg-purple-500 text-white hover:bg-purple-700"
|
||||
@@ -263,14 +267,16 @@ export const FlowInfo: React.FC<
|
||||
<PlayIcon className="mr-2" />
|
||||
{isRunning ? "Stop Agent" : "Run Agent"}
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => setIsDeleteModalOpen(true)}
|
||||
data-testid="delete-button"
|
||||
>
|
||||
<TrashIcon className="mr-2" />
|
||||
Delete Agent
|
||||
</Button>
|
||||
{flow.can_access_graph && (
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => setIsDeleteModalOpen(true)}
|
||||
data-testid="delete-button"
|
||||
>
|
||||
<TrashIcon className="mr-2" />
|
||||
Delete Agent
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
@@ -303,10 +309,12 @@ export const FlowInfo: React.FC<
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => {
|
||||
api.deleteGraph(flow.agent_id).then(() => {
|
||||
setIsDeleteModalOpen(false);
|
||||
refresh();
|
||||
});
|
||||
api
|
||||
.updateLibraryAgent(flow.id, { is_deleted: true })
|
||||
.then(() => {
|
||||
setIsDeleteModalOpen(false);
|
||||
refresh();
|
||||
});
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
|
||||
@@ -18,9 +18,11 @@ import {
|
||||
GraphCreatable,
|
||||
GraphExecution,
|
||||
GraphExecutionMeta,
|
||||
GraphID,
|
||||
GraphMeta,
|
||||
GraphUpdateable,
|
||||
LibraryAgent,
|
||||
LibraryAgentID,
|
||||
LibraryAgentPreset,
|
||||
LibraryAgentPresetResponse,
|
||||
LibraryAgentResponse,
|
||||
@@ -167,7 +169,7 @@ export default class BackendAPI {
|
||||
}
|
||||
|
||||
getGraph(
|
||||
id: string,
|
||||
id: GraphID,
|
||||
version?: number,
|
||||
hide_credentials?: boolean,
|
||||
): Promise<Graph> {
|
||||
@@ -181,7 +183,7 @@ export default class BackendAPI {
|
||||
return this._get(`/graphs/${id}`, query);
|
||||
}
|
||||
|
||||
getGraphAllVersions(id: string): Promise<Graph[]> {
|
||||
getGraphAllVersions(id: GraphID): Promise<Graph[]> {
|
||||
return this._get(`/graphs/${id}/versions`);
|
||||
}
|
||||
|
||||
@@ -191,22 +193,22 @@ export default class BackendAPI {
|
||||
return this._request("POST", "/graphs", requestBody);
|
||||
}
|
||||
|
||||
updateGraph(id: string, graph: GraphUpdateable): Promise<Graph> {
|
||||
updateGraph(id: GraphID, graph: GraphUpdateable): Promise<Graph> {
|
||||
return this._request("PUT", `/graphs/${id}`, graph);
|
||||
}
|
||||
|
||||
deleteGraph(id: string): Promise<void> {
|
||||
deleteGraph(id: GraphID): Promise<void> {
|
||||
return this._request("DELETE", `/graphs/${id}`);
|
||||
}
|
||||
|
||||
setGraphActiveVersion(id: string, version: number): Promise<Graph> {
|
||||
setGraphActiveVersion(id: GraphID, version: number): Promise<Graph> {
|
||||
return this._request("PUT", `/graphs/${id}/versions/active`, {
|
||||
active_graph_version: version,
|
||||
});
|
||||
}
|
||||
|
||||
executeGraph(
|
||||
id: string,
|
||||
id: GraphID,
|
||||
version: number,
|
||||
inputData: { [key: string]: any } = {},
|
||||
): Promise<{ graph_exec_id: string }> {
|
||||
@@ -217,12 +219,12 @@ export default class BackendAPI {
|
||||
return this._get(`/executions`);
|
||||
}
|
||||
|
||||
getGraphExecutions(graphID: string): Promise<GraphExecutionMeta[]> {
|
||||
getGraphExecutions(graphID: GraphID): Promise<GraphExecutionMeta[]> {
|
||||
return this._get(`/graphs/${graphID}/executions`);
|
||||
}
|
||||
|
||||
async getGraphExecutionInfo(
|
||||
graphID: string,
|
||||
graphID: GraphID,
|
||||
runID: string,
|
||||
): Promise<GraphExecution> {
|
||||
const result = await this._get(`/graphs/${graphID}/executions/${runID}`);
|
||||
@@ -233,7 +235,7 @@ export default class BackendAPI {
|
||||
}
|
||||
|
||||
async stopGraphExecution(
|
||||
graphID: string,
|
||||
graphID: GraphID,
|
||||
runID: string,
|
||||
): Promise<GraphExecution> {
|
||||
const result = await this._request(
|
||||
@@ -491,6 +493,10 @@ export default class BackendAPI {
|
||||
return this._get("/library/agents", params);
|
||||
}
|
||||
|
||||
getLibraryAgent(id: LibraryAgentID): Promise<LibraryAgent> {
|
||||
return this._get(`/library/agents/${id}`);
|
||||
}
|
||||
|
||||
addMarketplaceAgentToLibrary(
|
||||
storeListingVersionID: string,
|
||||
): Promise<LibraryAgent> {
|
||||
@@ -500,7 +506,7 @@ export default class BackendAPI {
|
||||
}
|
||||
|
||||
async updateLibraryAgent(
|
||||
libraryAgentId: string,
|
||||
libraryAgentId: LibraryAgentID,
|
||||
params: {
|
||||
auto_update_version?: boolean;
|
||||
is_favorite?: boolean;
|
||||
@@ -541,7 +547,7 @@ export default class BackendAPI {
|
||||
|
||||
executeLibraryAgentPreset(
|
||||
presetId: string,
|
||||
graphId: string,
|
||||
graphId: GraphID,
|
||||
graphVersion: number,
|
||||
nodeInput: { [key: string]: any },
|
||||
): Promise<{ id: string }> {
|
||||
|
||||
Reference in New Issue
Block a user