feat(frontend/library): Add "Open in builder" run action (#9755)

- Resolves #9730

### Changes 🏗️

- feat: Add "Open in builder" run action

- refactor: Add `ActionButtonGroup` to replace boilerplate code in
`AgentRunDetailsView`, `AgentRunDraftView`, `AgentScheduleDetailsView`
  - feat: Add link support to `ActionButtonGroup`, `ButtonAction`

### 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 `/library/agents/[id]`
    - [x] "Run again" button works
    - [x] "Open in builder" button-link works
This commit is contained in:
Reinier van der Leer
2025-04-03 23:34:33 +02:00
committed by GitHub
parent ce98925d58
commit 8ceb03ce1a
6 changed files with 78 additions and 82 deletions

View File

@@ -226,12 +226,8 @@ export default function AgentRunsPage(): React.ReactElement {
...(agent?.can_access_graph
? [
{
label: "Open in builder",
callback: () =>
agent &&
router.push(
`/build?flowID=${agent.agent_id}&flowVersion=${agent.agent_version}`,
),
label: "Open graph in builder",
href: `/build?flowID=${agent.agent_id}&flowVersion=${agent.agent_version}`,
},
{ label: "Export agent to file", callback: downloadGraph },
]
@@ -242,7 +238,7 @@ export default function AgentRunsPage(): React.ReactElement {
callback: () => setAgentDeleteDialogOpen(true),
},
],
[agent, router, downloadGraph],
[agent, downloadGraph],
);
if (!agent || !graph) {
@@ -282,6 +278,7 @@ export default function AgentRunsPage(): React.ReactElement {
{(selectedView.type == "run" && selectedView.id ? (
selectedRun && (
<AgentRunDetailsView
agent={agent}
graph={graphVersions[selectedRun.graph_version] ?? graph}
run={selectedRun}
agentActions={agentActions}

View File

@@ -8,13 +8,14 @@ import {
GraphExecution,
GraphExecutionID,
GraphExecutionMeta,
LibraryAgent,
} from "@/lib/autogpt-server-api";
import type { ButtonAction } from "@/components/agptui/types";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { IconRefresh, IconSquare } from "@/components/ui/icons";
import { useToastOnFail } from "@/components/ui/use-toast";
import { Button } from "@/components/agptui/Button";
import ActionButtonGroup from "@/components/agptui/action-button-group";
import { Input } from "@/components/ui/input";
import {
@@ -23,12 +24,14 @@ import {
} from "@/components/agents/agent-run-status-chip";
export default function AgentRunDetailsView({
agent,
graph,
run,
agentActions,
onRun,
deleteRun,
}: {
agent: LibraryAgent;
graph: Graph;
run: GraphExecution | GraphExecutionMeta;
agentActions: ButtonAction[];
@@ -174,9 +177,27 @@ export default function AgentRunDetailsView({
},
]
: []),
...(agent.can_access_graph
? [
{
label: "Open run in builder",
href: `/build?flowID=${run.graph_id}&flowVersion=${run.graph_version}&flowExecutionID=${run.id}`,
},
]
: []),
{ label: "Delete run", variant: "secondary", callback: deleteRun },
],
[runStatus, runAgain, stopRun, deleteRun],
[
runStatus,
runAgain,
stopRun,
deleteRun,
graph.has_webhook_trigger,
agent.can_access_graph,
run.graph_id,
run.graph_version,
run.id,
],
);
return (
@@ -254,31 +275,9 @@ export default function AgentRunDetailsView({
{/* Run / Agent Actions */}
<aside className="w-48 xl:w-56">
<div className="flex flex-col gap-8">
<div className="flex flex-col gap-3">
<h3 className="text-sm font-medium">Run actions</h3>
{runActions.map((action, i) => (
<Button
key={i}
variant={action.variant ?? "outline"}
onClick={action.callback}
>
{action.label}
</Button>
))}
</div>
<ActionButtonGroup title="Run actions" actions={runActions} />
<div className="flex flex-col gap-3">
<h3 className="text-sm font-medium">Agent actions</h3>
{agentActions.map((action, i) => (
<Button
key={i}
variant={action.variant ?? "outline"}
onClick={action.callback}
>
{action.label}
</Button>
))}
</div>
<ActionButtonGroup title="Agent actions" actions={agentActions} />
</div>
</aside>
</div>

View File

@@ -8,9 +8,9 @@ import type { ButtonAction } from "@/components/agptui/types";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { TypeBasedInput } from "@/components/type-based-input";
import { useToastOnFail } from "@/components/ui/use-toast";
import ActionButtonGroup from "@/components/agptui/action-button-group";
import SchemaTooltip from "@/components/SchemaTooltip";
import { IconPlay } from "@/components/ui/icons";
import { Button } from "@/components/agptui/Button";
export default function AgentRunDraftView({
graph,
@@ -87,31 +87,9 @@ export default function AgentRunDraftView({
{/* Actions */}
<aside className="w-48 xl:w-56">
<div className="flex flex-col gap-8">
<div className="flex flex-col gap-3">
<h3 className="text-sm font-medium">Run actions</h3>
{runActions.map((action, i) => (
<Button
key={i}
variant={action.variant ?? "outline"}
onClick={action.callback}
>
{action.label}
</Button>
))}
</div>
<ActionButtonGroup title="Run actions" actions={runActions} />
<div className="flex flex-col gap-3">
<h3 className="text-sm font-medium">Agent actions</h3>
{agentActions.map((action, i) => (
<Button
key={i}
variant={action.variant ?? "outline"}
onClick={action.callback}
>
{action.label}
</Button>
))}
</div>
<ActionButtonGroup title="Agent actions" actions={agentActions} />
</div>
</aside>
</div>

View File

@@ -12,7 +12,7 @@ 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 { useToastOnFail } from "@/components/ui/use-toast";
import { Button } from "@/components/agptui/Button";
import ActionButtonGroup from "@/components/agptui/action-button-group";
import { Input } from "@/components/ui/input";
export default function AgentScheduleDetailsView({
@@ -75,7 +75,7 @@ export default function AgentScheduleDetailsView({
[api, graph, schedule, onForcedRun, toastOnFail],
);
const runActions: { label: string; callback: () => void }[] = useMemo(
const runActions: ButtonAction[] = useMemo(
() => [{ label: "Run now", callback: () => runNow() }],
[runNow],
);
@@ -126,27 +126,9 @@ export default function AgentScheduleDetailsView({
{/* Run / Agent Actions */}
<aside className="w-48 xl:w-56">
<div className="flex flex-col gap-8">
<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}>
{action.label}
</Button>
))}
</div>
<ActionButtonGroup title="Run actions" actions={runActions} />
<div className="flex flex-col gap-3">
<h3 className="text-sm font-medium">Agent actions</h3>
{agentActions.map((action, i) => (
<Button
key={i}
variant={action.variant ?? "outline"}
onClick={action.callback}
>
{action.label}
</Button>
))}
</div>
<ActionButtonGroup title="Agent actions" actions={agentActions} />
</div>
</aside>
</div>

View File

@@ -0,0 +1,41 @@
import React from "react";
import { cn } from "@/lib/utils";
import type { ButtonAction } from "@/components/agptui/types";
import { Button, buttonVariants } from "@/components/agptui/Button";
import Link from "next/link";
export default function ActionButtonGroup({
title,
actions,
className,
}: {
title: React.ReactNode;
actions: ButtonAction[];
className?: string;
}): React.ReactElement {
return (
<div className={cn("flex flex-col gap-3", className)}>
<h3 className="text-sm font-medium">{title}</h3>
{actions.map((action, i) =>
"callback" in action ? (
<Button
key={i}
variant={action.variant ?? "outline"}
onClick={action.callback}
>
{action.label}
</Button>
) : (
<Link
key={i}
className={buttonVariants({ variant: action.variant })}
href={action.href}
>
{action.label}
</Link>
),
)}
</div>
);
}

View File

@@ -4,5 +4,4 @@ import React from "react";
export type ButtonAction = {
label: React.ReactNode;
variant?: ButtonProps["variant"];
callback: () => void;
};
} & ({ callback: () => void } | { href: string });