fix(frontend/library): Fix trigger UX flows in v3 library (#11589)

- Resolves #11586
- Follow-up to #11580

### Changes 🏗️

- Fix logic to include manual triggers as a possibility
- Fix input render logic to use trigger setup schema if applicable
- Fix rendering payload input for externally triggered runs
- Amend `RunAgentModal` to load preset inputs+credentials if selected
- Amend `SelectedTemplateView` to use modified input for run (if
applicable)
- Hide non-applicable buttons in `SelectedRunView` for externally
triggered runs
- Implement auto-navigation to `SelectedTriggerView` on trigger setup

### 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:
  - [x] Can set up manual triggers
    - [x] Navigates to trigger view after setup
  - [x] Can set up automatic triggers
  - [x] Can create templates from runs
  - [x] Can run templates
  - [x] Can run templates with modified input
This commit is contained in:
Reinier van der Leer
2025-12-10 16:52:02 +01:00
committed by GitHub
parent 979d7c3b74
commit 117bb05438
14 changed files with 216 additions and 116 deletions

View File

@@ -1,5 +1,6 @@
"use client"; "use client";
import { LibraryAgentPreset } from "@/app/api/__generated__/models/libraryAgentPreset";
import { Button } from "@/components/atoms/Button/Button"; import { Button } from "@/components/atoms/Button/Button";
import { Breadcrumbs } from "@/components/molecules/Breadcrumbs/Breadcrumbs"; import { Breadcrumbs } from "@/components/molecules/Breadcrumbs/Breadcrumbs";
import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard"; import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
@@ -24,11 +25,13 @@ import { useNewAgentLibraryView } from "./useNewAgentLibraryView";
export function NewAgentLibraryView() { export function NewAgentLibraryView() {
const { const {
agent,
hasAnyItems,
ready,
error,
agentId, agentId,
agent,
ready,
activeTemplate,
isTemplateLoading,
error,
hasAnyItems,
activeItem, activeItem,
sidebarLoading, sidebarLoading,
activeTab, activeTab,
@@ -38,6 +41,12 @@ export function NewAgentLibraryView() {
handleClearSelectedRun, handleClearSelectedRun,
} = useNewAgentLibraryView(); } = useNewAgentLibraryView();
function onTriggerSetup(newTrigger: LibraryAgentPreset) {
if (!agent) return;
handleSelectRun(newTrigger.id, "triggers");
}
if (error) { if (error) {
return ( return (
<ErrorCard <ErrorCard
@@ -65,7 +74,7 @@ export function NewAgentLibraryView() {
/> />
</div> </div>
<div className="flex min-h-0 flex-1"> <div className="flex min-h-0 flex-1">
<EmptyTasks agent={agent} /> <EmptyTasks agent={agent} onTriggerSetup={onTriggerSetup} />
</div> </div>
</div> </div>
); );
@@ -82,16 +91,23 @@ export function NewAgentLibraryView() {
> >
<RunAgentModal <RunAgentModal
triggerSlot={ triggerSlot={
<Button variant="primary" size="large" className="w-full"> <Button
variant="primary"
size="large"
className="w-full"
disabled={isTemplateLoading && activeTab === "templates"}
>
<PlusIcon size={20} /> New task <PlusIcon size={20} /> New task
</Button> </Button>
} }
agent={agent} agent={agent}
agentId={agent.id.toString()}
onRunCreated={(execution) => handleSelectRun(execution.id, "runs")} onRunCreated={(execution) => handleSelectRun(execution.id, "runs")}
onScheduleCreated={(schedule) => onScheduleCreated={(schedule) =>
handleSelectRun(schedule.id, "scheduled") handleSelectRun(schedule.id, "scheduled")
} }
onTriggerSetup={onTriggerSetup}
initialInputValues={activeTemplate?.inputs}
initialInputCredentials={activeTemplate?.credentials}
/> />
</div> </div>
@@ -151,7 +167,7 @@ export function NewAgentLibraryView() {
</SelectedViewLayout> </SelectedViewLayout>
) : ( ) : (
<SelectedViewLayout agentName={agent.name} agentId={agent.id}> <SelectedViewLayout agentName={agent.name} agentId={agent.id}>
<EmptyTasks agent={agent} /> <EmptyTasks agent={agent} onTriggerSetup={onTriggerSetup} />
</SelectedViewLayout> </SelectedViewLayout>
)} )}
</div> </div>

View File

@@ -1,7 +1,10 @@
"use client"; "use client";
import type { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent"; import type { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
import type { CredentialsMetaInput } from "@/lib/autogpt-server-api/types"; import type {
BlockIOSubSchema,
CredentialsMetaInput,
} from "@/lib/autogpt-server-api/types";
import { CredentialsInput } from "../CredentialsInputs/CredentialsInputs"; import { CredentialsInput } from "../CredentialsInputs/CredentialsInputs";
import { import {
getAgentCredentialsFields, getAgentCredentialsFields,
@@ -20,13 +23,21 @@ export function AgentInputsReadOnly({
inputs, inputs,
credentialInputs, credentialInputs,
}: Props) { }: Props) {
const fields = getAgentInputFields(agent); const inputFields = getAgentInputFields(agent);
const credentialFields = getAgentCredentialsFields(agent); const credentialFieldEntries = Object.entries(
const inputEntries = Object.entries(fields); getAgentCredentialsFields(agent),
const credentialEntries = Object.entries(credentialFields); );
const hasInputs = inputs && inputEntries.length > 0; // Take actual input entries as leading; augment with schema from input fields.
const hasCredentials = credentialInputs && credentialEntries.length > 0; // TODO: ensure consistent ordering.
const inputEntries =
inputs &&
Object.entries(inputs).map<[string, [BlockIOSubSchema | undefined, any]]>(
([k, v]) => [k, [inputFields[k], v]],
);
const hasInputs = inputEntries && inputEntries.length > 0;
const hasCredentials = credentialInputs && credentialFieldEntries.length > 0;
if (!hasInputs && !hasCredentials) { if (!hasInputs && !hasCredentials) {
return <div className="text-neutral-600">No input for this run.</div>; return <div className="text-neutral-600">No input for this run.</div>;
@@ -37,11 +48,13 @@ export function AgentInputsReadOnly({
{/* Regular inputs */} {/* Regular inputs */}
{hasInputs && ( {hasInputs && (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
{inputEntries.map(([key, sub]) => ( {inputEntries.map(([key, [schema, value]]) => (
<div key={key} className="flex flex-col gap-1.5"> <div key={key} className="flex flex-col gap-1.5">
<label className="text-sm font-medium">{sub?.title || key}</label> <label className="text-sm font-medium">
{schema?.title || key}
</label>
<p className="whitespace-pre-wrap break-words text-sm text-neutral-700"> <p className="whitespace-pre-wrap break-words text-sm text-neutral-700">
{renderValue((inputs as Record<string, any>)[key])} {renderValue(value)}
</p> </p>
</div> </div>
))} ))}
@@ -52,7 +65,7 @@ export function AgentInputsReadOnly({
{hasCredentials && ( {hasCredentials && (
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6">
{hasInputs && <div className="border-t border-neutral-200 pt-4" />} {hasInputs && <div className="border-t border-neutral-200 pt-4" />}
{credentialEntries.map(([key, inputSubSchema]) => { {credentialFieldEntries.map(([key, inputSubSchema]) => {
const credential = credentialInputs![key]; const credential = credentialInputs![key];
if (!credential) return null; if (!credential) return null;

View File

@@ -13,7 +13,8 @@ export function getCredentialTypeDisplayName(type: string): string {
} }
export function getAgentInputFields(agent: LibraryAgent): Record<string, any> { export function getAgentInputFields(agent: LibraryAgent): Record<string, any> {
const schema = agent.input_schema as unknown as { const schema = (agent.trigger_setup_info?.config_schema ??
agent.input_schema) as unknown as {
properties?: Record<string, any>; properties?: Record<string, any>;
} | null; } | null;
if (!schema || !schema.properties) return {}; if (!schema || !schema.properties) return {};

View File

@@ -3,6 +3,7 @@
import { GraphExecutionJobInfo } from "@/app/api/__generated__/models/graphExecutionJobInfo"; import { GraphExecutionJobInfo } from "@/app/api/__generated__/models/graphExecutionJobInfo";
import { GraphExecutionMeta } from "@/app/api/__generated__/models/graphExecutionMeta"; import { GraphExecutionMeta } from "@/app/api/__generated__/models/graphExecutionMeta";
import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent"; import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
import { LibraryAgentPreset } from "@/app/api/__generated__/models/libraryAgentPreset";
import { Button } from "@/components/atoms/Button/Button"; import { Button } from "@/components/atoms/Button/Button";
import { import {
Tooltip, Tooltip,
@@ -22,16 +23,20 @@ import { useAgentRunModal } from "./useAgentRunModal";
interface Props { interface Props {
triggerSlot: React.ReactNode; triggerSlot: React.ReactNode;
agent: LibraryAgent; agent: LibraryAgent;
agentId: string; initialInputValues?: Record<string, any>;
agentVersion?: number; initialInputCredentials?: Record<string, any>;
onRunCreated?: (execution: GraphExecutionMeta) => void; onRunCreated?: (execution: GraphExecutionMeta) => void;
onTriggerSetup?: (preset: LibraryAgentPreset) => void;
onScheduleCreated?: (schedule: GraphExecutionJobInfo) => void; onScheduleCreated?: (schedule: GraphExecutionJobInfo) => void;
} }
export function RunAgentModal({ export function RunAgentModal({
triggerSlot, triggerSlot,
agent, agent,
initialInputValues,
initialInputCredentials,
onRunCreated, onRunCreated,
onTriggerSetup,
onScheduleCreated, onScheduleCreated,
}: Props) { }: Props) {
const { const {
@@ -71,6 +76,9 @@ export function RunAgentModal({
handleRun, handleRun,
} = useAgentRunModal(agent, { } = useAgentRunModal(agent, {
onRun: onRunCreated, onRun: onRunCreated,
onSetupTrigger: onTriggerSetup,
initialInputValues,
initialInputCredentials,
}); });
const [isScheduleModalOpen, setIsScheduleModalOpen] = useState(false); const [isScheduleModalOpen, setIsScheduleModalOpen] = useState(false);

View File

@@ -26,7 +26,8 @@ export function ModalRunSection() {
return ( return (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
{defaultRunType === "automatic-trigger" ? ( {defaultRunType === "automatic-trigger" ||
defaultRunType === "manual-trigger" ? (
<ModalSection <ModalSection
title="Task Trigger" title="Task Trigger"
subtitle="Set up a trigger for the agent to run this task automatically" subtitle="Set up a trigger for the agent to run this task automatically"

View File

@@ -24,7 +24,8 @@ export function RunActions({
disabled={!isRunReady || isExecuting || isSettingUpTrigger} disabled={!isRunReady || isExecuting || isSettingUpTrigger}
loading={isExecuting || isSettingUpTrigger} loading={isExecuting || isSettingUpTrigger}
> >
{defaultRunType === "automatic-trigger" {defaultRunType === "automatic-trigger" ||
defaultRunType === "manual-trigger"
? "Set up Trigger" ? "Set up Trigger"
: "Start Task"} : "Start Task"}
</Button> </Button>

View File

@@ -6,7 +6,6 @@ import {
getGetV2ListPresetsQueryKey, getGetV2ListPresetsQueryKey,
usePostV2SetupTrigger, usePostV2SetupTrigger,
} from "@/app/api/__generated__/endpoints/presets/presets"; } from "@/app/api/__generated__/endpoints/presets/presets";
import { GraphExecutionJobInfo } from "@/app/api/__generated__/models/graphExecutionJobInfo";
import { GraphExecutionMeta } from "@/app/api/__generated__/models/graphExecutionMeta"; import { GraphExecutionMeta } from "@/app/api/__generated__/models/graphExecutionMeta";
import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent"; import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
import { LibraryAgentPreset } from "@/app/api/__generated__/models/libraryAgentPreset"; import { LibraryAgentPreset } from "@/app/api/__generated__/models/libraryAgentPreset";
@@ -14,7 +13,7 @@ import { useToast } from "@/components/molecules/Toast/use-toast";
import { isEmpty } from "@/lib/utils"; import { isEmpty } from "@/lib/utils";
import { analytics } from "@/services/analytics"; import { analytics } from "@/services/analytics";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import { useCallback, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { showExecutionErrorToast } from "./errorHelpers"; import { showExecutionErrorToast } from "./errorHelpers";
export type RunVariant = export type RunVariant =
@@ -25,8 +24,9 @@ export type RunVariant =
interface UseAgentRunModalCallbacks { interface UseAgentRunModalCallbacks {
onRun?: (execution: GraphExecutionMeta) => void; onRun?: (execution: GraphExecutionMeta) => void;
onCreateSchedule?: (schedule: GraphExecutionJobInfo) => void;
onSetupTrigger?: (preset: LibraryAgentPreset) => void; onSetupTrigger?: (preset: LibraryAgentPreset) => void;
initialInputValues?: Record<string, any>;
initialInputCredentials?: Record<string, any>;
} }
export function useAgentRunModal( export function useAgentRunModal(
@@ -36,18 +36,28 @@ export function useAgentRunModal(
const { toast } = useToast(); const { toast } = useToast();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [inputValues, setInputValues] = useState<Record<string, any>>({}); const [inputValues, setInputValues] = useState<Record<string, any>>(
callbacks?.initialInputValues || {},
);
const [inputCredentials, setInputCredentials] = useState<Record<string, any>>( const [inputCredentials, setInputCredentials] = useState<Record<string, any>>(
{}, callbacks?.initialInputCredentials || {},
); );
const [presetName, setPresetName] = useState<string>(""); const [presetName, setPresetName] = useState<string>("");
const [presetDescription, setPresetDescription] = useState<string>(""); const [presetDescription, setPresetDescription] = useState<string>("");
// Determine the default run type based on agent capabilities // Determine the default run type based on agent capabilities
const defaultRunType: RunVariant = agent.has_external_trigger const defaultRunType: RunVariant = agent.trigger_setup_info
? "automatic-trigger" ? agent.trigger_setup_info.credentials_input_name
? "automatic-trigger"
: "manual-trigger"
: "manual"; : "manual";
// Update input values/credentials if template is selected/unselected
useEffect(() => {
setInputValues(callbacks?.initialInputValues || {});
setInputCredentials(callbacks?.initialInputCredentials || {});
}, [callbacks?.initialInputValues, callbacks?.initialInputCredentials]);
// API mutations // API mutations
const executeGraphMutation = usePostV1ExecuteGraphAgent({ const executeGraphMutation = usePostV1ExecuteGraphAgent({
mutation: { mutation: {
@@ -105,11 +115,13 @@ export function useAgentRunModal(
}, },
}); });
// Input schema validation // Input schema validation (use trigger schema for triggered agents)
const agentInputSchema = useMemo( const agentInputSchema = useMemo(() => {
() => agent.input_schema || { properties: {}, required: [] }, if (agent.trigger_setup_info?.config_schema) {
[agent.input_schema], return agent.trigger_setup_info.config_schema;
); }
return agent.input_schema || { properties: {}, required: [] };
}, [agent.input_schema, agent.trigger_setup_info]);
const agentInputFields = useMemo(() => { const agentInputFields = useMemo(() => {
if ( if (
@@ -205,7 +217,10 @@ export function useAgentRunModal(
return; return;
} }
if (defaultRunType === "automatic-trigger") { if (
defaultRunType === "automatic-trigger" ||
defaultRunType === "manual-trigger"
) {
// Setup trigger // Setup trigger
if (!presetName.trim()) { if (!presetName.trim()) {
toast({ toast({
@@ -262,7 +277,7 @@ export function useAgentRunModal(
setIsOpen, setIsOpen,
// Run mode // Run mode
defaultRunType, defaultRunType: defaultRunType as RunVariant,
// Form: regular inputs // Form: regular inputs
inputValues, inputValues,

View File

@@ -2,6 +2,7 @@
import { getV1GetGraphVersion } from "@/app/api/__generated__/endpoints/graphs/graphs"; import { getV1GetGraphVersion } from "@/app/api/__generated__/endpoints/graphs/graphs";
import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent"; import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
import { LibraryAgentPreset } from "@/app/api/__generated__/models/libraryAgentPreset";
import { Button } from "@/components/atoms/Button/Button"; import { Button } from "@/components/atoms/Button/Button";
import { Text } from "@/components/atoms/Text/Text"; import { Text } from "@/components/atoms/Text/Text";
import { ShowMoreText } from "@/components/molecules/ShowMoreText/ShowMoreText"; import { ShowMoreText } from "@/components/molecules/ShowMoreText/ShowMoreText";
@@ -15,9 +16,10 @@ import { EmptyTasksIllustration } from "./EmptyTasksIllustration";
type Props = { type Props = {
agent: LibraryAgent; agent: LibraryAgent;
onTriggerSetup?: (preset: LibraryAgentPreset) => void;
}; };
export function EmptyTasks({ agent }: Props) { export function EmptyTasks({ agent, onTriggerSetup }: Props) {
const { toast } = useToast(); const { toast } = useToast();
async function handleExport() { async function handleExport() {
@@ -75,7 +77,7 @@ export function EmptyTasks({ agent }: Props) {
</Button> </Button>
} }
agent={agent} agent={agent}
agentId={agent.id.toString()} onTriggerSetup={onTriggerSetup}
/> />
</div> </div>
</div> </div>

View File

@@ -198,8 +198,8 @@ export function SelectedRunView({
<RunDetailCard title="Your input"> <RunDetailCard title="Your input">
<AgentInputsReadOnly <AgentInputsReadOnly
agent={agent} agent={agent}
inputs={(run as any)?.inputs} inputs={run?.inputs}
credentialInputs={(run as any)?.credential_inputs} credentialInputs={run?.credential_inputs}
/> />
</RunDetailCard> </RunDetailCard>
</div> </div>

View File

@@ -20,13 +20,18 @@ import { useSelectedRunActions } from "./useSelectedRunActions";
type Props = { type Props = {
agent: LibraryAgent; agent: LibraryAgent;
run: GraphExecution | undefined; run: GraphExecution | undefined;
scheduleRecurrence?: string;
onSelectRun?: (id: string) => void; onSelectRun?: (id: string) => void;
onClearSelectedRun?: () => void; onClearSelectedRun?: () => void;
}; };
export function SelectedRunActions(props: Props) { export function SelectedRunActions({
agent,
run,
onSelectRun,
onClearSelectedRun,
}: Props) {
const { const {
canRunManually,
handleRunAgain, handleRunAgain,
handleStopRun, handleStopRun,
isRunningAgain, isRunningAgain,
@@ -37,21 +42,20 @@ export function SelectedRunActions(props: Props) {
isCreateTemplateModalOpen, isCreateTemplateModalOpen,
setIsCreateTemplateModalOpen, setIsCreateTemplateModalOpen,
} = useSelectedRunActions({ } = useSelectedRunActions({
agentGraphId: props.agent.graph_id, agentGraphId: agent.graph_id,
run: props.run, run: run,
agent: props.agent, agent: agent,
onSelectRun: props.onSelectRun, onSelectRun: onSelectRun,
onClearSelectedRun: props.onClearSelectedRun,
}); });
const shareExecutionResultsEnabled = useGetFlag(Flag.SHARE_EXECUTION_RESULTS); const shareExecutionResultsEnabled = useGetFlag(Flag.SHARE_EXECUTION_RESULTS);
const isRunning = props.run?.status === "RUNNING"; const isRunning = run?.status === "RUNNING";
if (!props.run || !props.agent) return null; if (!run || !agent) return null;
return ( return (
<SelectedActionsWrap> <SelectedActionsWrap>
{!isRunning ? ( {canRunManually && !isRunning ? (
<Button <Button
variant="icon" variant="icon"
size="icon" size="icon"
@@ -103,37 +107,37 @@ export function SelectedRunActions(props: Props) {
) : null} ) : null}
{shareExecutionResultsEnabled && ( {shareExecutionResultsEnabled && (
<ShareRunButton <ShareRunButton
graphId={props.agent.graph_id} graphId={agent.graph_id}
executionId={props.run.id} executionId={run.id}
isShared={props.run.is_shared} isShared={run.is_shared}
shareToken={props.run.share_token} shareToken={run.share_token}
/> />
)} )}
<FloatingSafeModeToggle <FloatingSafeModeToggle graph={agent} variant="white" fullWidth={false} />
graph={props.agent} {canRunManually && (
variant="white" <>
fullWidth={false} <Button
/> variant="icon"
<Button size="icon"
variant="icon" aria-label="Save task as template"
size="icon" onClick={() => setIsCreateTemplateModalOpen(true)}
aria-label="Save task as template" title="Create template"
onClick={() => setIsCreateTemplateModalOpen(true)} >
title="Create template" <CardsThreeIcon weight="bold" size={18} className="text-zinc-700" />
> </Button>
<CardsThreeIcon weight="bold" size={18} className="text-zinc-700" /> <CreateTemplateModal
</Button> isOpen={isCreateTemplateModalOpen}
onClose={() => setIsCreateTemplateModalOpen(false)}
onCreate={handleCreateTemplate}
run={run}
/>
</>
)}
<AgentActionsDropdown <AgentActionsDropdown
agent={props.agent} agent={agent}
run={props.run} run={run}
agentGraphId={props.agent.graph_id} agentGraphId={agent.graph_id}
onClearSelectedRun={props.onClearSelectedRun} onClearSelectedRun={onClearSelectedRun}
/>
<CreateTemplateModal
isOpen={isCreateTemplateModalOpen}
onClose={() => setIsCreateTemplateModalOpen(false)}
onCreate={handleCreateTemplate}
run={props.run}
/> />
</SelectedActionsWrap> </SelectedActionsWrap>
); );

View File

@@ -15,15 +15,19 @@ import { useToast } from "@/components/molecules/Toast/use-toast";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import { useState } from "react"; import { useState } from "react";
interface Args { interface Params {
agentGraphId: string; agentGraphId: string;
run?: GraphExecution; run?: GraphExecution;
agent?: LibraryAgent; agent?: LibraryAgent;
onSelectRun?: (id: string) => void; onSelectRun?: (id: string) => void;
onClearSelectedRun?: () => void;
} }
export function useSelectedRunActions(args: Args) { export function useSelectedRunActions({
agentGraphId,
run,
agent,
onSelectRun,
}: Params) {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { toast } = useToast(); const { toast } = useToast();
@@ -31,8 +35,9 @@ export function useSelectedRunActions(args: Args) {
const [isCreateTemplateModalOpen, setIsCreateTemplateModalOpen] = const [isCreateTemplateModalOpen, setIsCreateTemplateModalOpen] =
useState(false); useState(false);
const canStop = const canStop = run?.status === "RUNNING" || run?.status === "QUEUED";
args.run?.status === "RUNNING" || args.run?.status === "QUEUED";
const canRunManually = !agent?.trigger_setup_info;
const { mutateAsync: stopRun, isPending: isStopping } = const { mutateAsync: stopRun, isPending: isStopping } =
usePostV1StopGraphExecution(); usePostV1StopGraphExecution();
@@ -46,16 +51,16 @@ export function useSelectedRunActions(args: Args) {
async function handleStopRun() { async function handleStopRun() {
try { try {
await stopRun({ await stopRun({
graphId: args.run?.graph_id ?? "", graphId: run?.graph_id ?? "",
graphExecId: args.run?.id ?? "", graphExecId: run?.id ?? "",
}); });
toast({ title: "Run stopped" }); toast({ title: "Run stopped" });
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
queryKey: getGetV1ListGraphExecutionsInfiniteQueryOptions( queryKey:
args.agentGraphId, getGetV1ListGraphExecutionsInfiniteQueryOptions(agentGraphId)
).queryKey, .queryKey,
}); });
} catch (error: unknown) { } catch (error: unknown) {
toast({ toast({
@@ -70,7 +75,7 @@ export function useSelectedRunActions(args: Args) {
} }
async function handleRunAgain() { async function handleRunAgain() {
if (!args.run) { if (!run) {
toast({ toast({
title: "Run not found", title: "Run not found",
description: "Run not found", description: "Run not found",
@@ -83,11 +88,11 @@ export function useSelectedRunActions(args: Args) {
toast({ title: "Run started" }); toast({ title: "Run started" });
const res = await executeRun({ const res = await executeRun({
graphId: args.run.graph_id, graphId: run.graph_id,
graphVersion: args.run.graph_version, graphVersion: run.graph_version,
data: { data: {
inputs: args.run.inputs || {}, inputs: run.inputs || {},
credentials_inputs: args.run.credential_inputs || {}, credentials_inputs: run.credential_inputs || {},
source: "library", source: "library",
}, },
}); });
@@ -95,12 +100,12 @@ export function useSelectedRunActions(args: Args) {
const newRunId = res?.status === 200 ? (res?.data?.id ?? "") : ""; const newRunId = res?.status === 200 ? (res?.data?.id ?? "") : "";
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
queryKey: getGetV1ListGraphExecutionsInfiniteQueryOptions( queryKey:
args.agentGraphId, getGetV1ListGraphExecutionsInfiniteQueryOptions(agentGraphId)
).queryKey, .queryKey,
}); });
if (newRunId && args.onSelectRun) args.onSelectRun(newRunId); if (newRunId && onSelectRun) onSelectRun(newRunId);
} catch (error: unknown) { } catch (error: unknown) {
toast({ toast({
title: "Failed to start run", title: "Failed to start run",
@@ -118,7 +123,7 @@ export function useSelectedRunActions(args: Args) {
} }
async function handleCreateTemplate(name: string, description: string) { async function handleCreateTemplate(name: string, description: string) {
if (!args.run) { if (!run) {
toast({ toast({
title: "Run not found", title: "Run not found",
description: "Cannot create template from missing run", description: "Cannot create template from missing run",
@@ -132,7 +137,7 @@ export function useSelectedRunActions(args: Args) {
data: { data: {
name, name,
description, description,
graph_execution_id: args.run.id, graph_execution_id: run.id,
}, },
}); });
@@ -141,10 +146,10 @@ export function useSelectedRunActions(args: Args) {
title: "Template created", title: "Template created",
}); });
if (args.agent) { if (agent) {
queryClient.invalidateQueries({ queryClient.invalidateQueries({
queryKey: getGetV2ListPresetsQueryKey({ queryKey: getGetV2ListPresetsQueryKey({
graph_id: args.agent.graph_id, graph_id: agent.graph_id,
}), }),
}); });
} }
@@ -164,8 +169,8 @@ export function useSelectedRunActions(args: Args) {
} }
// Open in builder URL helper // Open in builder URL helper
const openInBuilderHref = args.run const openInBuilderHref = run
? `/build?flowID=${args.run.graph_id}&flowVersion=${args.run.graph_version}&flowExecutionID=${args.run.id}` ? `/build?flowID=${run.graph_id}&flowVersion=${run.graph_version}&flowExecutionID=${run.id}`
: undefined; : undefined;
return { return {
@@ -173,6 +178,7 @@ export function useSelectedRunActions(args: Args) {
showDeleteDialog, showDeleteDialog,
canStop, canStop,
isStopping, isStopping,
canRunManually,
isRunningAgain, isRunningAgain,
handleShowDeleteDialog, handleShowDeleteDialog,
handleStopRun, handleStopRun,

View File

@@ -95,6 +95,7 @@ export function SelectedTemplateView({
return null; return null;
} }
const templateOrTrigger = agent.trigger_setup_info ? "Trigger" : "Template";
const hasWebhook = !!template.webhook_id && template.webhook; const hasWebhook = !!template.webhook_id && template.webhook;
return ( return (
@@ -111,14 +112,14 @@ export function SelectedTemplateView({
/> />
)} )}
<RunDetailCard title="Template Details"> <RunDetailCard title={`${templateOrTrigger} Details`}>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<Input <Input
id="template-name" id="template-name"
label="Name" label="Name"
value={name} value={name}
onChange={(e) => setName(e.target.value)} onChange={(e) => setName(e.target.value)}
placeholder="Enter template name" placeholder={`Enter ${templateOrTrigger.toLowerCase()} name`}
/> />
<Input <Input
@@ -128,7 +129,7 @@ export function SelectedTemplateView({
rows={3} rows={3}
value={description} value={description}
onChange={(e) => setDescription(e.target.value)} onChange={(e) => setDescription(e.target.value)}
placeholder="Enter template description" placeholder={`Enter ${templateOrTrigger.toLowerCase()} description`}
/> />
</div> </div>
</RunDetailCard> </RunDetailCard>

View File

@@ -138,11 +138,21 @@ export function useSelectedTemplateView({
} }
function handleStartTask() { function handleStartTask() {
if (!query.data) return;
const inputsChanged =
JSON.stringify(inputs) !== JSON.stringify(query.data.inputs || {});
const credentialsChanged =
JSON.stringify(credentials) !==
JSON.stringify(query.data.credentials || {});
// Use changed unpersisted inputs if applicable
executeMutation.mutate({ executeMutation.mutate({
presetId: templateId, presetId: templateId,
data: { data: {
inputs: {}, inputs: inputsChanged ? inputs : undefined,
credential_inputs: {}, credential_inputs: credentialsChanged ? credentials : undefined,
}, },
}); });
} }

View File

@@ -1,5 +1,7 @@
import { useGetV2GetLibraryAgent } from "@/app/api/__generated__/endpoints/library/library"; import { useGetV2GetLibraryAgent } from "@/app/api/__generated__/endpoints/library/library";
import { useGetV2GetASpecificPreset } from "@/app/api/__generated__/endpoints/presets/presets";
import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent"; import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
import { LibraryAgentPreset } from "@/app/api/__generated__/models/libraryAgentPreset";
import { okData } from "@/app/api/helpers"; import { okData } from "@/app/api/helpers";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
import { parseAsString, useQueryStates } from "nuqs"; import { parseAsString, useQueryStates } from "nuqs";
@@ -24,7 +26,7 @@ export function useNewAgentLibraryView() {
const agentId = id as string; const agentId = id as string;
const { const {
data: response, data: agent,
isSuccess, isSuccess,
error, error,
} = useGetV2GetLibraryAgent(agentId, { } = useGetV2GetLibraryAgent(agentId, {
@@ -41,6 +43,24 @@ export function useNewAgentLibraryView() {
const activeTab = useMemo(() => parseTab(activeTabRaw), [activeTabRaw]); const activeTab = useMemo(() => parseTab(activeTabRaw), [activeTabRaw]);
const {
data: _template,
isSuccess: isTemplateLoaded,
isLoading: isTemplateLoading,
error: templateError,
} = useGetV2GetASpecificPreset(activeItem ?? "", {
query: {
enabled: Boolean(activeTab === "templates" && activeItem),
select: okData<LibraryAgentPreset>,
},
});
const activeTemplate =
isTemplateLoaded &&
activeTab === "templates" &&
_template?.id === activeItem
? _template
: null;
useEffect(() => { useEffect(() => {
if (!activeTabRaw && !activeItem) { if (!activeTabRaw && !activeItem) {
setQueryStates({ setQueryStates({
@@ -71,10 +91,10 @@ export function useNewAgentLibraryView() {
const showSidebarLayout = sidebarLoading || hasAnyItems; const showSidebarLayout = sidebarLoading || hasAnyItems;
useEffect(() => { useEffect(() => {
if (response) { if (agent) {
document.title = `${response.name} - Library - AutoGPT Platform`; document.title = `${agent.name} - Library - AutoGPT Platform`;
} }
}, [response]); }, [agent]);
useEffect(() => { useEffect(() => {
if ( if (
@@ -135,9 +155,11 @@ export function useNewAgentLibraryView() {
return { return {
agentId: id, agentId: id,
agent,
ready: isSuccess, ready: isSuccess,
error, activeTemplate,
agent: response, isTemplateLoading,
error: error || templateError,
hasAnyItems, hasAnyItems,
showSidebarLayout, showSidebarLayout,
activeItem, activeItem,