mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-10 15:47:59 -05:00
feat(platform/builder): Hide action buttons on triggered graphs (#10218)
- Resolves #10217 https://github.com/user-attachments/assets/26a402f5-6f43-453b-8c83-481380bde853 ### Changes 🏗️ Frontend: - Show message instead of action buttons ("Run" etc) when graph has webhook node(s) - Fix check for webhook nodes used in `BlocksControl` and `FlowEditor` - Clean up `PrimaryActionBar` implementation - Add `accent` variant to `ui/button:Button` API: - Add `GET /library/agents/by-graph/{graph_id}` endpoint ### 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 Builder - Add a trigger block - [x] -> action buttons disappear; message shows in their place - Save the graph - Click the "Agent Library" link in the message - [x] -> app navigates to `/library/agents/[id]` for the newly created agent
This commit is contained in:
committed by
GitHub
parent
b5c7f381c1
commit
f3202fa776
@@ -215,6 +215,32 @@ async def get_library_agent_by_store_version_id(
|
||||
return None
|
||||
|
||||
|
||||
async def get_library_agent_by_graph_id(
|
||||
user_id: str,
|
||||
graph_id: str,
|
||||
graph_version: Optional[int] = None,
|
||||
) -> library_model.LibraryAgent | None:
|
||||
try:
|
||||
filter: prisma.types.LibraryAgentWhereInput = {
|
||||
"agentGraphId": graph_id,
|
||||
"userId": user_id,
|
||||
"isDeleted": False,
|
||||
}
|
||||
if graph_version is not None:
|
||||
filter["agentGraphVersion"] = graph_version
|
||||
|
||||
agent = await prisma.models.LibraryAgent.prisma().find_first(
|
||||
where=filter,
|
||||
include=library_agent_include(user_id),
|
||||
)
|
||||
if not agent:
|
||||
return None
|
||||
return library_model.LibraryAgent.from_db(agent)
|
||||
except prisma.errors.PrismaError as e:
|
||||
logger.error(f"Database error fetching library agent by graph ID: {e}")
|
||||
raise store_exceptions.DatabaseError("Failed to fetch library agent") from e
|
||||
|
||||
|
||||
async def add_generated_agent_image(
|
||||
graph: graph_db.GraphModel,
|
||||
library_agent_id: str,
|
||||
|
||||
@@ -92,6 +92,23 @@ async def get_library_agent(
|
||||
return await library_db.get_library_agent(id=library_agent_id, user_id=user_id)
|
||||
|
||||
|
||||
@router.get("/by-graph/{graph_id}")
|
||||
async def get_library_agent_by_graph_id(
|
||||
graph_id: str,
|
||||
version: Optional[int] = Query(default=None),
|
||||
user_id: str = Depends(autogpt_auth_lib.depends.get_user_id),
|
||||
) -> library_model.LibraryAgent:
|
||||
library_agent = await library_db.get_library_agent_by_graph_id(
|
||||
user_id, graph_id, version
|
||||
)
|
||||
if not library_agent:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Library agent for graph #{graph_id} and user #{user_id} not found",
|
||||
)
|
||||
return library_agent
|
||||
|
||||
|
||||
@router.get(
|
||||
"/marketplace/{store_listing_version_id}",
|
||||
summary="Get Agent By Store ID",
|
||||
|
||||
@@ -14,11 +14,13 @@ export default function BuilderPage() {
|
||||
completeStep("BUILDER_OPEN");
|
||||
}, [completeStep]);
|
||||
|
||||
const _graphVersion = query.get("flowVersion");
|
||||
const graphVersion = _graphVersion ? parseInt(_graphVersion) : undefined
|
||||
return (
|
||||
<FlowEditor
|
||||
className="flow-container"
|
||||
flowID={query.get("flowID") as GraphID | null ?? undefined}
|
||||
flowVersion={query.get("flowVersion") ?? undefined}
|
||||
flowVersion={graphVersion}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,10 +4,12 @@ import React, {
|
||||
useState,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
MouseEvent,
|
||||
Suspense,
|
||||
} from "react";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
ReactFlow,
|
||||
ReactFlowProvider,
|
||||
@@ -32,7 +34,9 @@ import {
|
||||
formatEdgeID,
|
||||
GraphExecutionID,
|
||||
GraphID,
|
||||
LibraryAgent,
|
||||
} from "@/lib/autogpt-server-api";
|
||||
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||
import { getTypeColor, findNewlyAddedBlockCoordinates } from "@/lib/utils";
|
||||
import { history } from "./history";
|
||||
import { CustomEdge } from "./CustomEdge";
|
||||
@@ -41,6 +45,7 @@ import { Control, ControlPanel } from "@/components/edit/control/ControlPanel";
|
||||
import { SaveControl } from "@/components/edit/control/SaveControl";
|
||||
import { BlocksControl } from "@/components/edit/control/BlocksControl";
|
||||
import { IconUndo2, IconRedo2 } from "@/components/ui/icons";
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||
import { startTutorial } from "./tutorial";
|
||||
import useAgentGraph from "@/hooks/useAgentGraph";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
@@ -48,11 +53,11 @@ import { useRouter, usePathname, useSearchParams } from "next/navigation";
|
||||
import RunnerUIWrapper, {
|
||||
RunnerUIWrapperRef,
|
||||
} from "@/components/RunnerUIWrapper";
|
||||
import { CronSchedulerDialog } from "@/components/cron-scheduler-dialog";
|
||||
import PrimaryActionBar from "@/components/PrimaryActionButton";
|
||||
import OttoChatWidget from "@/components/OttoChatWidget";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { useCopyPaste } from "../hooks/useCopyPaste";
|
||||
import { CronSchedulerDialog } from "@/components/cron-scheduler-dialog";
|
||||
|
||||
// This is for the history, this is the minimum distance a block must move before it is logged
|
||||
// It helps to prevent spamming the history with small movements especially when pressing on a input in a block
|
||||
@@ -77,7 +82,7 @@ export const FlowContext = createContext<FlowContextType | null>(null);
|
||||
|
||||
const FlowEditor: React.FC<{
|
||||
flowID?: GraphID;
|
||||
flowVersion?: string;
|
||||
flowVersion?: number;
|
||||
className?: string;
|
||||
}> = ({ flowID, flowVersion, className }) => {
|
||||
const {
|
||||
@@ -118,10 +123,24 @@ const FlowEditor: React.FC<{
|
||||
setEdges,
|
||||
} = useAgentGraph(
|
||||
flowID,
|
||||
flowVersion ? parseInt(flowVersion) : undefined,
|
||||
flowVersion,
|
||||
flowExecutionID,
|
||||
visualizeBeads !== "no",
|
||||
);
|
||||
const api = useBackendAPI();
|
||||
const [libraryAgent, setLibraryAgent] = useState<LibraryAgent | null>(null);
|
||||
useEffect(() => {
|
||||
if (!flowID) return;
|
||||
api
|
||||
.getLibraryAgentByGraphID(flowID, flowVersion)
|
||||
.then((libraryAgent) => setLibraryAgent(libraryAgent))
|
||||
.catch((error) => {
|
||||
console.warn(
|
||||
`Failed to fetch LibraryAgent for graph #${flowID} v${flowVersion}`,
|
||||
error,
|
||||
);
|
||||
});
|
||||
}, [api, flowID, flowVersion]);
|
||||
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
@@ -154,6 +173,16 @@ const FlowEditor: React.FC<{
|
||||
: `Builder - AutoGPT Platform`;
|
||||
}, [savedAgent]);
|
||||
|
||||
const graphHasWebhookNodes = useMemo(
|
||||
() =>
|
||||
nodes.some((n) =>
|
||||
[BlockUIType.WEBHOOK, BlockUIType.WEBHOOK_MANUAL].includes(
|
||||
n.data.uiType,
|
||||
),
|
||||
),
|
||||
[nodes],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (params.get("resetTutorial") === "true") {
|
||||
localStorage.removeItem(TUTORIAL_STORAGE_KEY);
|
||||
@@ -707,36 +736,62 @@ const FlowEditor: React.FC<{
|
||||
/>
|
||||
}
|
||||
></ControlPanel>
|
||||
<PrimaryActionBar
|
||||
className="absolute bottom-0 left-1/2 z-20 -translate-x-1/2"
|
||||
onClickAgentOutputs={() => runnerUIRef.current?.openRunnerOutput()}
|
||||
onClickRunAgent={() => {
|
||||
if (!savedAgent) {
|
||||
toast({
|
||||
title: `Please save the agent using the button in the left sidebar before running it.`,
|
||||
duration: 2000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!isRunning) {
|
||||
runnerUIRef.current?.runOrOpenInput();
|
||||
} else {
|
||||
requestStopRun();
|
||||
}
|
||||
}}
|
||||
onClickScheduleButton={handleScheduleButton}
|
||||
isScheduling={isScheduling}
|
||||
isDisabled={!savedAgent}
|
||||
isRunning={isRunning}
|
||||
requestStopRun={requestStopRun}
|
||||
runAgentTooltip={!isRunning ? "Run Agent" : "Stop Agent"}
|
||||
/>
|
||||
<CronSchedulerDialog
|
||||
open={openCron}
|
||||
setOpen={setOpenCron}
|
||||
afterCronCreation={afterCronCreation}
|
||||
defaultScheduleName={agentName}
|
||||
/>
|
||||
{!graphHasWebhookNodes ? (
|
||||
<>
|
||||
<PrimaryActionBar
|
||||
className="absolute bottom-0 left-1/2 z-20 -translate-x-1/2"
|
||||
onClickAgentOutputs={() =>
|
||||
runnerUIRef.current?.openRunnerOutput()
|
||||
}
|
||||
onClickRunAgent={() => {
|
||||
if (isRunning) return;
|
||||
if (!savedAgent) {
|
||||
toast({
|
||||
title: `Please save the agent using the button in the left sidebar before running it.`,
|
||||
duration: 2000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
runnerUIRef.current?.runOrOpenInput();
|
||||
}}
|
||||
onClickStopRun={requestStopRun}
|
||||
onClickScheduleButton={handleScheduleButton}
|
||||
isScheduling={isScheduling}
|
||||
isDisabled={!savedAgent}
|
||||
isRunning={isRunning}
|
||||
/>
|
||||
<CronSchedulerDialog
|
||||
afterCronCreation={afterCronCreation}
|
||||
open={openCron}
|
||||
setOpen={setOpenCron}
|
||||
defaultScheduleName={agentName}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Alert className="absolute bottom-4 left-1/2 z-20 w-auto -translate-x-1/2 select-none">
|
||||
<AlertTitle>You are building a Trigger Agent</AlertTitle>
|
||||
<AlertDescription>
|
||||
Your agent{" "}
|
||||
{savedAgent?.nodes.some((node) => node.webhook)
|
||||
? "is listening"
|
||||
: "will listen"}{" "}
|
||||
for its trigger and will run when the time is right.
|
||||
<br />
|
||||
You can view its activity in your
|
||||
<Link
|
||||
href={
|
||||
libraryAgent
|
||||
? `/library/agents/${libraryAgent.id}`
|
||||
: "/library"
|
||||
}
|
||||
className="underline"
|
||||
>
|
||||
Agent Library
|
||||
</Link>
|
||||
.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
</ReactFlow>
|
||||
</div>
|
||||
<RunnerUIWrapper
|
||||
|
||||
@@ -4,41 +4,30 @@ import { Button } from "@/components/ui/button";
|
||||
import { FaSpinner } from "react-icons/fa";
|
||||
import { Clock, LogOut } from "lucide-react";
|
||||
import { IconPlay, IconSquare } from "@/components/ui/icons";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
|
||||
interface PrimaryActionBarProps {
|
||||
onClickAgentOutputs: () => void;
|
||||
onClickRunAgent: () => void;
|
||||
onClickScheduleButton: () => void;
|
||||
onClickRunAgent?: () => void;
|
||||
onClickStopRun: () => void;
|
||||
onClickScheduleButton?: () => void;
|
||||
isRunning: boolean;
|
||||
isDisabled: boolean;
|
||||
isScheduling: boolean;
|
||||
requestStopRun: () => void;
|
||||
runAgentTooltip: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const PrimaryActionBar: React.FC<PrimaryActionBarProps> = ({
|
||||
onClickAgentOutputs,
|
||||
onClickRunAgent,
|
||||
onClickStopRun,
|
||||
onClickScheduleButton,
|
||||
isRunning,
|
||||
isDisabled,
|
||||
isScheduling,
|
||||
requestStopRun,
|
||||
runAgentTooltip,
|
||||
className,
|
||||
}) => {
|
||||
const runButtonLabel = !isRunning ? "Run" : "Stop";
|
||||
|
||||
const runButtonIcon = !isRunning ? <IconPlay /> : <IconSquare />;
|
||||
|
||||
const runButtonOnClick = !isRunning ? onClickRunAgent : requestStopRun;
|
||||
|
||||
const buttonClasses =
|
||||
"flex items-center gap-2 text-sm font-medium md:text-lg";
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
@@ -47,70 +36,64 @@ const PrimaryActionBar: React.FC<PrimaryActionBarProps> = ({
|
||||
)}
|
||||
>
|
||||
<div className="flex gap-1 md:gap-4">
|
||||
<Tooltip key="ViewOutputs" delayDuration={500}>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
className="flex items-center gap-2"
|
||||
onClick={onClickAgentOutputs}
|
||||
size="primary"
|
||||
variant="outline"
|
||||
>
|
||||
<LogOut className="hidden h-5 w-5 md:flex" />
|
||||
<span className="text-sm font-medium md:text-lg">
|
||||
Agent Outputs{" "}
|
||||
</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>View agent outputs</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip key="RunAgent" delayDuration={500}>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
className="flex items-center gap-2"
|
||||
onClick={runButtonOnClick}
|
||||
size="primary"
|
||||
style={{
|
||||
background: isRunning ? "#DF4444" : "#7544DF",
|
||||
opacity: isDisabled ? 0.5 : 1,
|
||||
}}
|
||||
data-id="primary-action-run-agent"
|
||||
>
|
||||
{runButtonIcon}
|
||||
<span className="text-sm font-medium md:text-lg">
|
||||
{runButtonLabel}
|
||||
</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{runAgentTooltip}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip key="ScheduleAgent" delayDuration={500}>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
className="flex items-center gap-2"
|
||||
onClick={onClickScheduleButton}
|
||||
size="primary"
|
||||
disabled={isScheduling}
|
||||
variant="outline"
|
||||
data-id="primary-action-schedule-agent"
|
||||
>
|
||||
{isScheduling ? (
|
||||
<FaSpinner className="animate-spin" />
|
||||
) : (
|
||||
<Clock className="hidden h-5 w-5 md:flex" />
|
||||
)}
|
||||
<span className="text-sm font-medium md:text-lg">
|
||||
Schedule Run
|
||||
</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Schedule this Agent</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Button
|
||||
className={buttonClasses}
|
||||
variant="outline"
|
||||
size="primary"
|
||||
onClick={onClickAgentOutputs}
|
||||
title="View agent outputs"
|
||||
>
|
||||
<LogOut className="hidden size-5 md:flex" /> Agent Outputs
|
||||
</Button>
|
||||
|
||||
{!isRunning ? (
|
||||
<Button
|
||||
className={cn(
|
||||
buttonClasses,
|
||||
onClickRunAgent && isDisabled
|
||||
? "cursor-default opacity-50 hover:bg-accent"
|
||||
: "",
|
||||
)}
|
||||
variant="accent"
|
||||
size="primary"
|
||||
onClick={onClickRunAgent}
|
||||
disabled={!onClickRunAgent}
|
||||
title="Run the agent"
|
||||
data-id="primary-action-run-agent"
|
||||
>
|
||||
<IconPlay /> Run
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
className={buttonClasses}
|
||||
variant="destructive"
|
||||
size="primary"
|
||||
onClick={onClickStopRun}
|
||||
title="Stop the agent"
|
||||
data-id="primary-action-stop-agent"
|
||||
>
|
||||
<IconSquare /> Stop
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{onClickScheduleButton && (
|
||||
<Button
|
||||
className={buttonClasses}
|
||||
variant="outline"
|
||||
size="primary"
|
||||
onClick={onClickScheduleButton}
|
||||
disabled={isScheduling}
|
||||
title="Set up a run schedule for the agent"
|
||||
data-id="primary-action-schedule-agent"
|
||||
>
|
||||
{isScheduling ? (
|
||||
<FaSpinner className="animate-spin" />
|
||||
) : (
|
||||
<Clock className="hidden h-5 w-5 md:flex" />
|
||||
)}
|
||||
Schedule Run
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -55,8 +55,8 @@ export const BlocksControl: React.FC<BlocksControlProps> = ({
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
|
||||
|
||||
const graphHasWebhookNodes = nodes.some(
|
||||
(n) => n.data.uiType == BlockUIType.WEBHOOK,
|
||||
const graphHasWebhookNodes = nodes.some((n) =>
|
||||
[BlockUIType.WEBHOOK, BlockUIType.WEBHOOK_MANUAL].includes(n.data.uiType),
|
||||
);
|
||||
const graphHasInputNodes = nodes.some(
|
||||
(n) => n.data.uiType == BlockUIType.INPUT,
|
||||
|
||||
@@ -13,6 +13,7 @@ const buttonVariants = cva(
|
||||
"bg-neutral-900 text-neutral-50 shadow hover:bg-neutral-900/90 dark:bg-neutral-50 dark:text-neutral-900 dark:hover:bg-neutral-50/90",
|
||||
destructive:
|
||||
"bg-red-500 text-neutral-50 shadow-sm hover:bg-red-500/90 dark:bg-red-900 dark:text-neutral-50 dark:hover:bg-red-900/90",
|
||||
accent: "bg-accent text-accent-foreground hover:bg-violet-500",
|
||||
outline:
|
||||
"border border-neutral-200 bg-white shadow-sm hover:bg-neutral-100 hover:text-neutral-900 dark:border-neutral-800 dark:bg-neutral-950 dark:hover:bg-neutral-800 dark:hover:text-neutral-50",
|
||||
secondary:
|
||||
|
||||
@@ -635,6 +635,15 @@ export default class BackendAPI {
|
||||
return this._get(`/library/agents/marketplace/${storeListingVersionId}`);
|
||||
}
|
||||
|
||||
getLibraryAgentByGraphID(
|
||||
graphID: GraphID,
|
||||
graphVersion?: number,
|
||||
): Promise<LibraryAgent> {
|
||||
return this._get(`/library/agents/by-graph/${graphID}`, {
|
||||
version: graphVersion,
|
||||
});
|
||||
}
|
||||
|
||||
addMarketplaceAgentToLibrary(
|
||||
storeListingVersionID: string,
|
||||
): Promise<LibraryAgent> {
|
||||
|
||||
Reference in New Issue
Block a user