From 6c43b34dee81b8bba8ae4e766b6989afd5213d6c Mon Sep 17 00:00:00 2001 From: Ubbe Date: Tue, 9 Dec 2025 18:30:13 +0700 Subject: [PATCH] feat(frontend): add templates/triggers to new Library page view (#11580) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Changes 🏗️ Add Templates to the new Agent Library page: Screenshot 2025-12-09 at 14 10 01 - You can create a template from a run ( new action button ) - Templates are listed and can be selected on the sidebar - When viewing a template, you can edit it, create a task or delete it Add Triggers to the new Agent Library page: Screenshot 2025-12-09 at 14 10 43 - When an agent contains a trigger block, on the modal it will create a trigger - When there are triggers, they are listed on the sidebar - A trigger can be viewed and edited ## 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] Run the new page locally --- .../NewAgentLibraryView.tsx | 22 ++ .../CredentialsSelect/CredentialsSelect.tsx | 12 +- .../modals/CredentialsInputs/helpers.ts | 10 +- .../modals/RunAgentModal/RunAgentModal.tsx | 54 ++- .../ModalRunSection/ModalRunSection.tsx | 4 +- .../WebhookTriggerBanner.tsx | 2 +- .../modals/RunAgentModal/useAgentRunModal.tsx | 182 ++-------- .../components/other/EmptyTriggers.tsx | 323 ++++++++++++++++++ .../RunDetailHeader/RunDetailHeader.tsx | 16 +- .../SelectedRunView/SelectedRunView.tsx | 19 +- .../CreateTemplateModal.tsx | 98 ++++++ .../SelectedRunActions/SelectedRunActions.tsx | 21 ++ .../useSelectedRunActions.ts | 61 ++++ .../components/WebhookTriggerSection.tsx | 92 +++++ .../SelectedRunView/useSelectedRunView.ts | 22 +- .../SelectedScheduleView.tsx | 24 +- .../SelectedTemplateView.tsx | 204 +++++++++++ .../components/SelectedTemplateActions.tsx | 172 ++++++++++ .../components/WebhookTriggerCard.tsx | 92 +++++ .../useSelectedTemplateView.ts | 189 ++++++++++ .../SelectedTriggerView.tsx | 196 +++++++++++ .../components/SelectedTriggerActions.tsx | 149 ++++++++ .../useSelectedTriggerView.ts | 141 ++++++++ .../SidebarRunsList/SidebarRunsList.tsx | 77 ++++- .../components/ScheduleListItem.tsx | 11 +- .../components/TemplateListItem.tsx | 33 ++ .../components/TriggerListItem.tsx | 33 ++ .../SidebarRunsList/useSidebarRunsList.ts | 81 ++++- .../useNewAgentLibraryView.ts | 42 ++- 29 files changed, 2156 insertions(+), 226 deletions(-) create mode 100644 autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptyTriggers.tsx create mode 100644 autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedRunView/components/CreateTemplateModal/CreateTemplateModal.tsx create mode 100644 autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedRunView/components/WebhookTriggerSection.tsx create mode 100644 autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTemplateView/SelectedTemplateView.tsx create mode 100644 autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTemplateView/components/SelectedTemplateActions.tsx create mode 100644 autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTemplateView/components/WebhookTriggerCard.tsx create mode 100644 autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTemplateView/useSelectedTemplateView.ts create mode 100644 autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTriggerView/SelectedTriggerView.tsx create mode 100644 autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTriggerView/components/SelectedTriggerActions.tsx create mode 100644 autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTriggerView/useSelectedTriggerView.ts create mode 100644 autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/components/TemplateListItem.tsx create mode 100644 autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/components/TriggerListItem.tsx diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/NewAgentLibraryView.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/NewAgentLibraryView.tsx index 7a7470a391..b06901f860 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/NewAgentLibraryView.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/NewAgentLibraryView.tsx @@ -10,10 +10,13 @@ import { AgentRunsLoading } from "./components/other/AgentRunsLoading"; import { EmptySchedules } from "./components/other/EmptySchedules"; import { EmptyTasks } from "./components/other/EmptyTasks"; import { EmptyTemplates } from "./components/other/EmptyTemplates"; +import { EmptyTriggers } from "./components/other/EmptyTriggers"; import { SectionWrap } from "./components/other/SectionWrap"; import { LoadingSelectedContent } from "./components/selected-views/LoadingSelectedContent"; import { SelectedRunView } from "./components/selected-views/SelectedRunView/SelectedRunView"; import { SelectedScheduleView } from "./components/selected-views/SelectedScheduleView/SelectedScheduleView"; +import { SelectedTemplateView } from "./components/selected-views/SelectedTemplateView/SelectedTemplateView"; +import { SelectedTriggerView } from "./components/selected-views/SelectedTriggerView/SelectedTriggerView"; import { SelectedViewLayout } from "./components/selected-views/SelectedViewLayout"; import { SidebarRunsList } from "./components/sidebar/SidebarRunsList/SidebarRunsList"; import { AGENT_LIBRARY_SECTION_PADDING_X } from "./helpers"; @@ -109,6 +112,21 @@ export function NewAgentLibraryView() { scheduleId={activeItem} onClearSelectedRun={handleClearSelectedRun} /> + ) : activeTab === "templates" ? ( + handleSelectRun(execution.id, "runs")} + onSwitchToRunsTab={() => setActiveTab("runs")} + /> + ) : activeTab === "triggers" ? ( + setActiveTab("runs")} + /> ) : ( + ) : activeTab === "triggers" ? ( + + + ) : ( diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/components/CredentialsSelect/CredentialsSelect.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/components/CredentialsSelect/CredentialsSelect.tsx index 29f9b09a22..7adfa5772b 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/components/CredentialsSelect/CredentialsSelect.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/components/CredentialsSelect/CredentialsSelect.tsx @@ -48,8 +48,8 @@ export function CredentialsSelect({ onValueChange={(value) => onSelectCredential(value)} > - - {selectedCredentials ? ( + {selectedCredentials ? ( + - ) : ( - Select credential - )} - + + ) : ( + + )} {credentials.map((credential) => ( diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/helpers.ts b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/helpers.ts index 9e6f374437..4cca825747 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/helpers.ts +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/helpers.ts @@ -75,11 +75,11 @@ export function getActionButtonText( hasExistingCredentials: boolean, ): string { if (hasExistingCredentials) { - if (supportsOAuth2) return "Connect a different account"; - if (supportsApiKey) return "Use a different API key"; - if (supportsUserPassword) return "Use a different username and password"; - if (supportsHostScoped) return "Use different headers"; - return "Add credentials"; + if (supportsOAuth2) return "Connect another account"; + if (supportsApiKey) return "Use a new API key"; + if (supportsUserPassword) return "Add a new username and password"; + if (supportsHostScoped) return "Add new headers"; + return "Add new credentials"; } else { if (supportsOAuth2) return "Add account"; if (supportsApiKey) return "Add API key"; diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/RunAgentModal.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/RunAgentModal.tsx index eff83cf824..3818c05c45 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/RunAgentModal.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/RunAgentModal.tsx @@ -4,6 +4,12 @@ import { GraphExecutionJobInfo } from "@/app/api/__generated__/models/graphExecu import { GraphExecutionMeta } from "@/app/api/__generated__/models/graphExecutionMeta"; import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent"; import { Button } from "@/components/atoms/Button/Button"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/atoms/Tooltip/BaseTooltip"; import { Dialog } from "@/components/molecules/Dialog/Dialog"; import { useState } from "react"; import { ScheduleAgentModal } from "../ScheduleAgentModal/ScheduleAgentModal"; @@ -147,15 +153,45 @@ export function RunAgentModal({
- + {!allRequiredInputsAreSet ? ( + + + + + + + + +

+ Please set up all required inputs and credentials before + scheduling +

+
+
+
+ ) : ( + + )} 0 ? ( {/* Regular inputs */} {inputFields.map(([key, inputSubSchema]) => ( diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/components/WebhookTriggerBanner/WebhookTriggerBanner.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/components/WebhookTriggerBanner/WebhookTriggerBanner.tsx index bc69a5f633..23834cbd9d 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/components/WebhookTriggerBanner/WebhookTriggerBanner.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/components/WebhookTriggerBanner/WebhookTriggerBanner.tsx @@ -1,6 +1,6 @@ export function WebhookTriggerBanner() { return ( -
+
>({}); const [inputCredentials, setInputCredentials] = useState>( {}, ); const [presetName, setPresetName] = useState(""); const [presetDescription, setPresetDescription] = useState(""); - const defaultScheduleName = useMemo(() => `Run ${agent.name}`, [agent.name]); - const [scheduleName, setScheduleName] = useState(defaultScheduleName); - const [cronExpression, setCronExpression] = useState( - agent.recommended_schedule_cron || "0 9 * * 1", - ); - - // Get user timezone for scheduling - const { data: userTimezone } = useGetV1GetUserTimezone({ - query: { - select: (res) => (res.status === 200 ? res.data.timezone : undefined), - }, - }); // Determine the default run type based on agent capabilities const defaultRunType: RunVariant = agent.has_external_trigger @@ -94,38 +79,6 @@ export function useAgentRunModal( }, }); - const createScheduleMutation = useCreateSchedule({ - mutation: { - onSuccess: (response) => { - if (response.status === 200) { - toast({ - title: "Schedule created", - }); - callbacks?.onCreateSchedule?.(response.data); - // Invalidate schedules list for this graph - queryClient.invalidateQueries({ - queryKey: getGetV1ListExecutionSchedulesForAGraphQueryKey( - agent.graph_id, - ), - }); - analytics.sendDatafastEvent("schedule_agent", { - name: agent.name, - id: agent.graph_id, - cronExpression: cronExpression, - }); - setIsOpen(false); - } - }, - onError: (error: any) => { - toast({ - title: "❌ Failed to create schedule", - description: error.message || "An unexpected error occurred.", - variant: "destructive", - }); - }, - }, - }); - const setupTriggerMutation = usePostV2SetupTrigger({ mutation: { onSuccess: (response: any) => { @@ -134,6 +87,11 @@ export function useAgentRunModal( title: "Trigger setup complete", }); callbacks?.onSetupTrigger?.(response.data); + queryClient.invalidateQueries({ + queryKey: getGetV2ListPresetsQueryKey({ + graph_id: agent.graph_id, + }), + }); setIsOpen(false); } }, @@ -220,33 +178,25 @@ export function useAgentRunModal( [allRequiredInputsAreSetRaw, credentialsRequired, allCredentialsAreSet], ); - const notifyMissingRequirements = useCallback( - (needScheduleName: boolean = false) => { - const allMissingFields = ( - needScheduleName && !scheduleName ? ["schedule_name"] : [] - ) - .concat(missingInputs) - .concat( - credentialsRequired && !allCredentialsAreSet - ? missingCredentials.map((k) => `credentials:${k}`) - : [], - ); + const notifyMissingRequirements = useCallback(() => { + const allMissingFields = missingInputs.concat( + credentialsRequired && !allCredentialsAreSet + ? missingCredentials.map((k) => `credentials:${k}`) + : [], + ); - toast({ - title: "⚠️ Missing required inputs", - description: `Please provide: ${allMissingFields.map((k) => `"${k}"`).join(", ")}`, - variant: "destructive", - }); - }, - [ - missingInputs, - scheduleName, - toast, - credentialsRequired, - allCredentialsAreSet, - missingCredentials, - ], - ); + toast({ + title: "⚠️ Missing required inputs", + description: `Please provide: ${allMissingFields.map((k) => `"${k}"`).join(", ")}`, + variant: "destructive", + }); + }, [ + missingInputs, + toast, + credentialsRequired, + allCredentialsAreSet, + missingCredentials, + ]); // Action handlers const handleRun = useCallback(() => { @@ -257,7 +207,7 @@ export function useAgentRunModal( if (defaultRunType === "automatic-trigger") { // Setup trigger - if (!scheduleName.trim()) { + if (!presetName.trim()) { toast({ title: "⚠️ Trigger name required", description: "Please provide a name for your trigger.", @@ -268,7 +218,7 @@ export function useAgentRunModal( setupTriggerMutation.mutate({ data: { - name: presetName || scheduleName, + name: presetName, description: presetDescription || `Trigger for ${agent.name}`, graph_id: agent.graph_id, graph_version: agent.graph_version, @@ -291,7 +241,6 @@ export function useAgentRunModal( }, [ allRequiredInputsAreSet, defaultRunType, - scheduleName, inputValues, inputCredentials, agent, @@ -303,70 +252,6 @@ export function useAgentRunModal( toast, ]); - const handleSchedule = useCallback(() => { - if (!allRequiredInputsAreSet) { - notifyMissingRequirements(true); - return; - } - - if (!scheduleName.trim()) { - toast({ - title: "⚠️ Schedule name required", - description: "Please provide a name for your schedule.", - variant: "destructive", - }); - return; - } - - createScheduleMutation.mutate({ - graphId: agent.graph_id, - data: { - name: presetName || scheduleName, - cron: cronExpression, - inputs: inputValues, - graph_version: agent.graph_version, - credentials: inputCredentials, - timezone: - userTimezone && userTimezone !== "not-set" ? userTimezone : undefined, - }, - }); - }, [ - allRequiredInputsAreSet, - scheduleName, - cronExpression, - inputValues, - inputCredentials, - agent, - notifyMissingRequirements, - createScheduleMutation, - toast, - userTimezone, - ]); - - function handleShowSchedule() { - // Initialize with sensible defaults when entering schedule view - setScheduleName((prev) => prev || defaultScheduleName); - setCronExpression( - (prev) => prev || agent.recommended_schedule_cron || "0 9 * * 1", - ); - setShowScheduleView(true); - } - - function handleGoBack() { - setShowScheduleView(false); - // Reset schedule fields on exit - setScheduleName(defaultScheduleName); - setCronExpression(agent.recommended_schedule_cron || "0 9 * * 1"); - } - - function handleSetScheduleName(name: string) { - setScheduleName(name); - } - - function handleSetCronExpression(expression: string) { - setCronExpression(expression); - } - const hasInputFields = useMemo(() => { return Object.keys(agentInputFields).length > 0; }, [agentInputFields]); @@ -375,7 +260,6 @@ export function useAgentRunModal( // UI state isOpen, setIsOpen, - showScheduleView, // Run mode defaultRunType, @@ -394,10 +278,6 @@ export function useAgentRunModal( setPresetName, setPresetDescription, - // Scheduling - scheduleName, - cronExpression, - // Validation/readiness allRequiredInputsAreSet, missingInputs, @@ -409,15 +289,9 @@ export function useAgentRunModal( // Async states isExecuting: executeGraphMutation.isPending, - isCreatingSchedule: createScheduleMutation.isPending, isSettingUpTrigger: setupTriggerMutation.isPending, // Actions handleRun, - handleSchedule, - handleShowSchedule, - handleGoBack, - handleSetScheduleName, - handleSetCronExpression, }; } diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptyTriggers.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptyTriggers.tsx new file mode 100644 index 0000000000..0d9dc47fff --- /dev/null +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptyTriggers.tsx @@ -0,0 +1,323 @@ +import { Text } from "@/components/atoms/Text/Text"; + +export function EmptyTriggers() { + return ( +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + No triggers yet + + + Set up automatic triggers for your agent to run tasks automatically — + they'll show up here. + +
+
+ ); +} diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/RunDetailHeader/RunDetailHeader.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/RunDetailHeader/RunDetailHeader.tsx index 7c70b5e6aa..3d04234bb3 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/RunDetailHeader/RunDetailHeader.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/RunDetailHeader/RunDetailHeader.tsx @@ -1,6 +1,7 @@ import { GraphExecution } from "@/app/api/__generated__/models/graphExecution"; import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent"; import { Text } from "@/components/atoms/Text/Text"; +import { ClockClockwiseIcon } from "@phosphor-icons/react"; import moment from "moment"; import { AGENT_LIBRARY_SECTION_PADDING_X } from "../../../helpers"; import { RunStatusBadge } from "../SelectedRunView/components/RunStatusBadge"; @@ -20,7 +21,20 @@ export function RunDetailHeader({ agent, run, scheduleRecurrence }: Props) {
- {run?.status ? : null} + {run?.status ? ( + + ) : scheduleRecurrence ? ( +
+ + + Scheduled + +
+ ) : null} {agent.name} diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedRunView/SelectedRunView.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedRunView/SelectedRunView.tsx index 7cce125e7c..1c7df0f680 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedRunView/SelectedRunView.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedRunView/SelectedRunView.tsx @@ -24,6 +24,7 @@ import { SelectedViewLayout } from "../SelectedViewLayout"; import { RunOutputs } from "./components/RunOutputs"; import { RunSummary } from "./components/RunSummary"; import { SelectedRunActions } from "./components/SelectedRunActions/SelectedRunActions"; +import { WebhookTriggerSection } from "./components/WebhookTriggerSection"; import { useSelectedRunView } from "./useSelectedRunView"; const anchorStyles = @@ -42,10 +43,8 @@ export function SelectedRunView({ onSelectRun, onClearSelectedRun, }: Props) { - const { run, isLoading, responseError, httpError } = useSelectedRunView( - agent.graph_id, - runId, - ); + const { run, preset, isLoading, responseError, httpError } = + useSelectedRunView(agent.graph_id, runId); const { pendingReviews, @@ -90,6 +89,16 @@ export function SelectedRunView({
+ {preset && + agent.trigger_setup_info && + preset.webhook_id && + preset.webhook && ( + + )} + {/* Navigation Links */}
); } diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedRunView/components/SelectedRunActions/useSelectedRunActions.ts b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedRunView/components/SelectedRunActions/useSelectedRunActions.ts index e88a4d6ea7..462490b6da 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedRunView/components/SelectedRunActions/useSelectedRunActions.ts +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedRunView/components/SelectedRunActions/useSelectedRunActions.ts @@ -5,7 +5,12 @@ import { usePostV1ExecuteGraphAgent, usePostV1StopGraphExecution, } from "@/app/api/__generated__/endpoints/graphs/graphs"; +import { + getGetV2ListPresetsQueryKey, + usePostV2CreateANewPreset, +} from "@/app/api/__generated__/endpoints/presets/presets"; import type { GraphExecution } from "@/app/api/__generated__/models/graphExecution"; +import type { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent"; import { useToast } from "@/components/molecules/Toast/use-toast"; import { useQueryClient } from "@tanstack/react-query"; import { useState } from "react"; @@ -13,6 +18,7 @@ import { useState } from "react"; interface Args { agentGraphId: string; run?: GraphExecution; + agent?: LibraryAgent; onSelectRun?: (id: string) => void; onClearSelectedRun?: () => void; } @@ -22,6 +28,8 @@ export function useSelectedRunActions(args: Args) { const { toast } = useToast(); const [showDeleteDialog, setShowDeleteDialog] = useState(false); + const [isCreateTemplateModalOpen, setIsCreateTemplateModalOpen] = + useState(false); const canStop = args.run?.status === "RUNNING" || args.run?.status === "QUEUED"; @@ -32,6 +40,9 @@ export function useSelectedRunActions(args: Args) { const { mutateAsync: executeRun, isPending: isRunningAgain } = usePostV1ExecuteGraphAgent(); + const { mutateAsync: createPreset, isPending: isCreatingTemplate } = + usePostV2CreateANewPreset(); + async function handleStopRun() { try { await stopRun({ @@ -106,6 +117,52 @@ export function useSelectedRunActions(args: Args) { setShowDeleteDialog(open); } + async function handleCreateTemplate(name: string, description: string) { + if (!args.run) { + toast({ + title: "Run not found", + description: "Cannot create template from missing run", + variant: "destructive", + }); + return; + } + + try { + const res = await createPreset({ + data: { + name, + description, + graph_execution_id: args.run.id, + }, + }); + + if (res.status === 200) { + toast({ + title: "Template created", + }); + + if (args.agent) { + queryClient.invalidateQueries({ + queryKey: getGetV2ListPresetsQueryKey({ + graph_id: args.agent.graph_id, + }), + }); + } + + setIsCreateTemplateModalOpen(false); + } + } catch (error: unknown) { + toast({ + title: "Failed to create template", + description: + error instanceof Error + ? error.message + : "An unexpected error occurred.", + variant: "destructive", + }); + } + } + // Open in builder URL helper const openInBuilderHref = args.run ? `/build?flowID=${args.run.graph_id}&flowVersion=${args.run.graph_version}&flowExecutionID=${args.run.id}` @@ -120,5 +177,9 @@ export function useSelectedRunActions(args: Args) { handleShowDeleteDialog, handleStopRun, handleRunAgain, + handleCreateTemplate, + isCreatingTemplate, + isCreateTemplateModalOpen, + setIsCreateTemplateModalOpen, } as const; } diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedRunView/components/WebhookTriggerSection.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedRunView/components/WebhookTriggerSection.tsx new file mode 100644 index 0000000000..0b24f38731 --- /dev/null +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedRunView/components/WebhookTriggerSection.tsx @@ -0,0 +1,92 @@ +"use client"; + +import { GraphTriggerInfo } from "@/app/api/__generated__/models/graphTriggerInfo"; +import { LibraryAgentPreset } from "@/app/api/__generated__/models/libraryAgentPreset"; +import { Button } from "@/components/atoms/Button/Button"; +import { Text } from "@/components/atoms/Text/Text"; +import { CopyIcon } from "@phosphor-icons/react"; +import { RunDetailCard } from "../../RunDetailCard/RunDetailCard"; + +interface Props { + preset: LibraryAgentPreset; + triggerSetupInfo: GraphTriggerInfo; +} + +function getTriggerStatus( + preset: LibraryAgentPreset, +): "active" | "inactive" | "broken" { + if (!preset.webhook_id || !preset.webhook) return "broken"; + return preset.is_active ? "active" : "inactive"; +} + +export function WebhookTriggerSection({ preset, triggerSetupInfo }: Props) { + const status = getTriggerStatus(preset); + const webhook = preset.webhook; + + function handleCopyWebhookUrl() { + if (webhook?.url) { + navigator.clipboard.writeText(webhook.url); + } + } + + return ( + +
+
+ Status + + {status === "active" + ? "Active" + : status === "inactive" + ? "Inactive" + : "Broken"} + +
+ + {!preset.webhook_id ? ( + + This trigger is not attached to a webhook. Use "Set up + trigger" to fix this. + + ) : !triggerSetupInfo.credentials_input_name && webhook ? ( +
+ + This trigger is ready to be used. Use the Webhook URL below to set + up the trigger connection with the service of your choosing. + +
+ Webhook URL: +
+ {webhook.url} + +
+
+
+ ) : ( + + This agent trigger is{" "} + {preset.is_active + ? "ready. When a trigger is received, it will run with the provided settings." + : "disabled. It will not respond to triggers until you enable it."} + + )} +
+
+ ); +} diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedRunView/useSelectedRunView.ts b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedRunView/useSelectedRunView.ts index 276673d389..342241ef89 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedRunView/useSelectedRunView.ts +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedRunView/useSelectedRunView.ts @@ -1,8 +1,11 @@ "use client"; import { useGetV1GetExecutionDetails } from "@/app/api/__generated__/endpoints/graphs/graphs"; -import type { GetV1GetExecutionDetails200 } from "@/app/api/__generated__/models/getV1GetExecutionDetails200"; +import { useGetV2GetASpecificPreset } from "@/app/api/__generated__/endpoints/presets/presets"; import { AgentExecutionStatus } from "@/app/api/__generated__/models/agentExecutionStatus"; +import type { GetV1GetExecutionDetails200 } from "@/app/api/__generated__/models/getV1GetExecutionDetails200"; +import type { LibraryAgentPreset } from "@/app/api/__generated__/models/libraryAgentPreset"; +import { okData } from "@/app/api/helpers"; export function useSelectedRunView(graphId: string, runId: string) { const query = useGetV1GetExecutionDetails(graphId, runId, { @@ -37,6 +40,18 @@ export function useSelectedRunView(graphId: string, runId: string) { ? (query.data?.data as GetV1GetExecutionDetails200) : undefined; + const presetId = + run && "preset_id" in run && run.preset_id + ? (run.preset_id as string) + : undefined; + + const presetQuery = useGetV2GetASpecificPreset(presetId || "", { + query: { + enabled: !!presetId, + select: (res) => okData(res), + }, + }); + const httpError = status && status !== 200 ? { status, statusText: `Request failed: ${status}` } @@ -44,8 +59,9 @@ export function useSelectedRunView(graphId: string, runId: string) { return { run, - isLoading: query.isLoading, - responseError: query.error, + preset: presetQuery.data, + isLoading: query.isLoading || presetQuery.isLoading, + responseError: query.error || presetQuery.error, httpError, } as const; } diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedScheduleView/SelectedScheduleView.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedScheduleView/SelectedScheduleView.tsx index 6eda578f87..841ff04df9 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedScheduleView/SelectedScheduleView.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedScheduleView/SelectedScheduleView.tsx @@ -83,19 +83,17 @@ export function SelectedScheduleView({
-
-
-
- -
+
+
+
diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTemplateView/SelectedTemplateView.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTemplateView/SelectedTemplateView.tsx new file mode 100644 index 0000000000..b1c89c1945 --- /dev/null +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTemplateView/SelectedTemplateView.tsx @@ -0,0 +1,204 @@ +"use client"; + +import type { GraphExecutionMeta } from "@/app/api/__generated__/models/graphExecutionMeta"; +import type { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent"; +import { Input } from "@/components/atoms/Input/Input"; +import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard"; +import { InformationTooltip } from "@/components/molecules/InformationTooltip/InformationTooltip"; +import { + getAgentCredentialsFields, + getAgentInputFields, +} from "../../modals/AgentInputsReadOnly/helpers"; +import { CredentialsInput } from "../../modals/CredentialsInputs/CredentialsInputs"; +import { RunAgentInputs } from "../../modals/RunAgentInputs/RunAgentInputs"; +import { LoadingSelectedContent } from "../LoadingSelectedContent"; +import { RunDetailCard } from "../RunDetailCard/RunDetailCard"; +import { RunDetailHeader } from "../RunDetailHeader/RunDetailHeader"; +import { SelectedViewLayout } from "../SelectedViewLayout"; +import { SelectedTemplateActions } from "./components/SelectedTemplateActions"; +import { WebhookTriggerCard } from "./components/WebhookTriggerCard"; +import { useSelectedTemplateView } from "./useSelectedTemplateView"; + +interface Props { + agent: LibraryAgent; + templateId: string; + onClearSelectedRun?: () => void; + onRunCreated?: (execution: GraphExecutionMeta) => void; + onSwitchToRunsTab?: () => void; +} + +export function SelectedTemplateView({ + agent, + templateId, + onClearSelectedRun, + onRunCreated, + onSwitchToRunsTab, +}: Props) { + const { + template, + isLoading, + error, + name, + setName, + description, + setDescription, + inputs, + setInputValue, + credentials, + setCredentialValue, + handleSaveChanges, + handleStartTask, + isSaving, + isStarting, + } = useSelectedTemplateView({ + templateId, + graphId: agent.graph_id, + onRunCreated, + }); + + const agentInputFields = getAgentInputFields(agent); + const agentCredentialsFields = getAgentCredentialsFields(agent); + const inputFields = Object.entries(agentInputFields); + const credentialFields = Object.entries(agentCredentialsFields); + + if (error) { + return ( + + ); + } + + if (isLoading && !template) { + return ; + } + + if (!template) { + return null; + } + + const hasWebhook = !!template.webhook_id && template.webhook; + + return ( +
+
+ +
+ + + {hasWebhook && agent.trigger_setup_info && ( + + )} + + +
+ setName(e.target.value)} + placeholder="Enter template name" + /> + + setDescription(e.target.value)} + placeholder="Enter template description" + /> +
+
+ + {inputFields.length > 0 && ( + +
+ {inputFields.map(([key, inputSubSchema]) => ( +
+ + setInputValue(key, value)} + /> +
+ ))} +
+
+ )} + + {credentialFields.length > 0 && ( + +
+ {credentialFields.map(([key, inputSubSchema]) => ( + + setCredentialValue(key, value!) + } + siblingInputs={inputs} + /> + ))} +
+
+ )} +
+
+
+ {template ? ( +
+ +
+ ) : null} +
+ ); +} diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTemplateView/components/SelectedTemplateActions.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTemplateView/components/SelectedTemplateActions.tsx new file mode 100644 index 0000000000..a3a2a9ac62 --- /dev/null +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTemplateView/components/SelectedTemplateActions.tsx @@ -0,0 +1,172 @@ +"use client"; + +import { + getGetV2ListPresetsQueryKey, + useDeleteV2DeleteAPreset, +} from "@/app/api/__generated__/endpoints/presets/presets"; +import type { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent"; +import type { LibraryAgentPresetResponse } from "@/app/api/__generated__/models/libraryAgentPresetResponse"; +import { okData } from "@/app/api/helpers"; +import { Button } from "@/components/atoms/Button/Button"; +import { LoadingSpinner } from "@/components/atoms/LoadingSpinner/LoadingSpinner"; +import { Text } from "@/components/atoms/Text/Text"; +import { Dialog } from "@/components/molecules/Dialog/Dialog"; +import { useToast } from "@/components/molecules/Toast/use-toast"; +import { FloppyDiskIcon, PlayIcon, TrashIcon } from "@phosphor-icons/react"; +import { useQueryClient } from "@tanstack/react-query"; +import { useState } from "react"; + +interface Props { + agent: LibraryAgent; + templateId: string; + onDeleted?: () => void; + onSaveChanges?: () => void; + onStartTask?: () => void; + isSaving?: boolean; + isStarting?: boolean; + onSwitchToRunsTab?: () => void; +} + +export function SelectedTemplateActions({ + agent, + templateId, + onDeleted, + onSaveChanges, + onStartTask, + isSaving, + isStarting, + onSwitchToRunsTab, +}: Props) { + const { toast } = useToast(); + const queryClient = useQueryClient(); + const [showDeleteDialog, setShowDeleteDialog] = useState(false); + + const deleteMutation = useDeleteV2DeleteAPreset({ + mutation: { + onSuccess: async () => { + toast({ + title: "Template deleted", + }); + const queryKey = getGetV2ListPresetsQueryKey({ + graph_id: agent.graph_id, + }); + + queryClient.invalidateQueries({ + queryKey, + }); + + const queryData = queryClient.getQueryData<{ + data: LibraryAgentPresetResponse; + }>(queryKey); + + const presets = + okData(queryData)?.presets ?? []; + const templates = presets.filter( + (preset) => !preset.webhook_id || !preset.webhook, + ); + + setShowDeleteDialog(false); + onDeleted?.(); + + if (templates.length === 0 && onSwitchToRunsTab) { + onSwitchToRunsTab(); + } + }, + onError: (error: any) => { + toast({ + title: "Failed to delete template", + description: error.message || "An unexpected error occurred.", + variant: "destructive", + }); + }, + }, + }); + + function handleDelete() { + deleteMutation.mutate({ presetId: templateId }); + } + + return ( + <> +
+ + {onStartTask && ( + + )} + +
+ + + + + Are you sure you want to delete this template? This action cannot be + undone. + + + + + + + + + ); +} diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTemplateView/components/WebhookTriggerCard.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTemplateView/components/WebhookTriggerCard.tsx new file mode 100644 index 0000000000..d8a54f0474 --- /dev/null +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTemplateView/components/WebhookTriggerCard.tsx @@ -0,0 +1,92 @@ +"use client"; + +import { GraphTriggerInfo } from "@/app/api/__generated__/models/graphTriggerInfo"; +import { LibraryAgentPreset } from "@/app/api/__generated__/models/libraryAgentPreset"; +import { Button } from "@/components/atoms/Button/Button"; +import { Text } from "@/components/atoms/Text/Text"; +import { CopyIcon } from "@phosphor-icons/react"; +import { RunDetailCard } from "../../RunDetailCard/RunDetailCard"; + +interface Props { + template: LibraryAgentPreset; + triggerSetupInfo: GraphTriggerInfo; +} + +function getTriggerStatus( + template: LibraryAgentPreset, +): "active" | "inactive" | "broken" { + if (!template.webhook_id || !template.webhook) return "broken"; + return template.is_active ? "active" : "inactive"; +} + +export function WebhookTriggerCard({ template, triggerSetupInfo }: Props) { + const status = getTriggerStatus(template); + const webhook = template.webhook; + + function handleCopyWebhookUrl() { + if (webhook?.url) { + navigator.clipboard.writeText(webhook.url); + } + } + + return ( + +
+
+ Status + + {status === "active" + ? "Active" + : status === "inactive" + ? "Inactive" + : "Broken"} + +
+ + {!template.webhook_id ? ( + + This trigger is not attached to a webhook. Use "Set up + trigger" to fix this. + + ) : !triggerSetupInfo.credentials_input_name && webhook ? ( +
+ + This trigger is ready to be used. Use the Webhook URL below to set + up the trigger connection with the service of your choosing. + +
+ Webhook URL: +
+ {webhook.url} + +
+
+
+ ) : ( + + This agent trigger is{" "} + {template.is_active + ? "ready. When a trigger is received, it will run with the provided settings." + : "disabled. It will not respond to triggers until you enable it."} + + )} +
+
+ ); +} diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTemplateView/useSelectedTemplateView.ts b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTemplateView/useSelectedTemplateView.ts new file mode 100644 index 0000000000..58483fdc74 --- /dev/null +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTemplateView/useSelectedTemplateView.ts @@ -0,0 +1,189 @@ +"use client"; + +import { getGetV1ListGraphExecutionsInfiniteQueryOptions } from "@/app/api/__generated__/endpoints/graphs/graphs"; +import { + getGetV2GetASpecificPresetQueryKey, + getGetV2ListPresetsQueryKey, + useGetV2GetASpecificPreset, + usePatchV2UpdateAnExistingPreset, + usePostV2ExecuteAPreset, +} from "@/app/api/__generated__/endpoints/presets/presets"; +import type { GraphExecutionMeta } from "@/app/api/__generated__/models/graphExecutionMeta"; +import type { LibraryAgentPreset } from "@/app/api/__generated__/models/libraryAgentPreset"; +import type { LibraryAgentPresetUpdatable } from "@/app/api/__generated__/models/libraryAgentPresetUpdatable"; +import { okData } from "@/app/api/helpers"; +import { useToast } from "@/components/molecules/Toast/use-toast"; +import type { CredentialsMetaInput } from "@/lib/autogpt-server-api/types"; +import { useQueryClient } from "@tanstack/react-query"; +import { useEffect, useState } from "react"; + +type Args = { + templateId: string; + graphId: string; + onRunCreated?: (execution: GraphExecutionMeta) => void; +}; + +export function useSelectedTemplateView({ + templateId, + graphId, + onRunCreated, +}: Args) { + const { toast } = useToast(); + const queryClient = useQueryClient(); + + const query = useGetV2GetASpecificPreset(templateId, { + query: { + enabled: !!templateId, + select: (res) => okData(res), + }, + }); + + const [name, setName] = useState(""); + const [description, setDescription] = useState(""); + const [inputs, setInputs] = useState>({}); + const [credentials, setCredentials] = useState< + Record + >({}); + + useEffect(() => { + if (query.data) { + setName(query.data.name || ""); + setDescription(query.data.description || ""); + setInputs(query.data.inputs || {}); + setCredentials(query.data.credentials || {}); + } + }, [query.data]); + + const updateMutation = usePatchV2UpdateAnExistingPreset({ + mutation: { + onSuccess: (response) => { + if (response.status === 200) { + toast({ + title: "Template updated", + }); + queryClient.invalidateQueries({ + queryKey: getGetV2GetASpecificPresetQueryKey(templateId), + }); + queryClient.invalidateQueries({ + queryKey: getGetV2ListPresetsQueryKey({ graph_id: graphId }), + }); + } + }, + onError: (error: any) => { + toast({ + title: "Failed to update template", + description: error.message || "An unexpected error occurred.", + variant: "destructive", + }); + }, + }, + }); + + const executeMutation = usePostV2ExecuteAPreset({ + mutation: { + onSuccess: (response) => { + if (response.status === 200) { + const execution = okData(response); + if (execution) { + toast({ + title: "Task started", + }); + queryClient.invalidateQueries({ + queryKey: + getGetV1ListGraphExecutionsInfiniteQueryOptions(graphId) + .queryKey, + }); + onRunCreated?.(execution); + } + } + }, + onError: (error: any) => { + toast({ + title: "Failed to start task", + description: error.message || "An unexpected error occurred.", + variant: "destructive", + }); + }, + }, + }); + + function handleSaveChanges() { + if (!query.data) return; + + const updateData: LibraryAgentPresetUpdatable = {}; + if (name !== (query.data.name || "")) { + updateData.name = name; + } + + if (description !== (query.data.description || "")) { + updateData.description = description; + } + + const inputsChanged = + JSON.stringify(inputs) !== JSON.stringify(query.data.inputs || {}); + + const credentialsChanged = + JSON.stringify(credentials) !== + JSON.stringify(query.data.credentials || {}); + + if (inputsChanged || credentialsChanged) { + updateData.inputs = inputs; + updateData.credentials = credentials; + } + + updateMutation.mutate({ + presetId: templateId, + data: updateData, + }); + } + + function handleStartTask() { + executeMutation.mutate({ + presetId: templateId, + data: { + inputs: {}, + credential_inputs: {}, + }, + }); + } + + function setInputValue(key: string, value: any) { + setInputs((prev) => ({ ...prev, [key]: value })); + } + + function setCredentialValue(key: string, value: CredentialsMetaInput) { + setCredentials((prev) => ({ ...prev, [key]: value })); + } + + const httpError = + query.isSuccess && !query.data + ? { status: 404, statusText: "Not found" } + : undefined; + + useEffect(() => { + if (updateMutation.isSuccess && query.data) { + setName(query.data.name || ""); + setDescription(query.data.description || ""); + setInputs(query.data.inputs || {}); + setCredentials(query.data.credentials || {}); + } + }, [updateMutation.isSuccess, query.data]); + + return { + template: query.data, + isLoading: query.isLoading, + error: query.error || httpError, + name, + setName, + description, + setDescription, + inputs, + setInputValue, + credentials, + setCredentialValue, + handleSaveChanges, + handleStartTask, + isSaving: updateMutation.isPending, + isStarting: executeMutation.isPending, + } as const; +} diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTriggerView/SelectedTriggerView.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTriggerView/SelectedTriggerView.tsx new file mode 100644 index 0000000000..2021251ad2 --- /dev/null +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTriggerView/SelectedTriggerView.tsx @@ -0,0 +1,196 @@ +"use client"; + +import type { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent"; +import { Input } from "@/components/atoms/Input/Input"; +import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard"; +import { InformationTooltip } from "@/components/molecules/InformationTooltip/InformationTooltip"; +import { + getAgentCredentialsFields, + getAgentInputFields, +} from "../../modals/AgentInputsReadOnly/helpers"; +import { CredentialsInput } from "../../modals/CredentialsInputs/CredentialsInputs"; +import { RunAgentInputs } from "../../modals/RunAgentInputs/RunAgentInputs"; +import { LoadingSelectedContent } from "../LoadingSelectedContent"; +import { RunDetailCard } from "../RunDetailCard/RunDetailCard"; +import { RunDetailHeader } from "../RunDetailHeader/RunDetailHeader"; +import { WebhookTriggerCard } from "../SelectedTemplateView/components/WebhookTriggerCard"; +import { SelectedViewLayout } from "../SelectedViewLayout"; +import { SelectedTriggerActions } from "./components/SelectedTriggerActions"; +import { useSelectedTriggerView } from "./useSelectedTriggerView"; + +interface Props { + agent: LibraryAgent; + triggerId: string; + onClearSelectedRun?: () => void; + onSwitchToRunsTab?: () => void; +} + +export function SelectedTriggerView({ + agent, + triggerId, + onClearSelectedRun, + onSwitchToRunsTab, +}: Props) { + const { + trigger, + isLoading, + error, + name, + setName, + description, + setDescription, + inputs, + setInputValue, + credentials, + setCredentialValue, + handleSaveChanges, + isSaving, + } = useSelectedTriggerView({ + triggerId, + graphId: agent.graph_id, + }); + + const agentInputFields = getAgentInputFields(agent); + const agentCredentialsFields = getAgentCredentialsFields(agent); + const inputFields = Object.entries(agentInputFields); + const credentialFields = Object.entries(agentCredentialsFields); + + if (error) { + return ( + + ); + } + + if (isLoading && !trigger) { + return ; + } + + if (!trigger) { + return null; + } + + const hasWebhook = !!trigger.webhook_id && trigger.webhook; + + return ( +
+
+ +
+ + + +
+ setName(e.target.value)} + placeholder="Enter trigger name" + /> + + setDescription(e.target.value)} + placeholder="Enter trigger description" + /> +
+
+ + {hasWebhook && agent.trigger_setup_info && ( + + )} + + {inputFields.length > 0 && ( + +
+ {inputFields.map(([key, inputSubSchema]) => ( +
+ + setInputValue(key, value)} + /> +
+ ))} +
+
+ )} + + {credentialFields.length > 0 && ( + +
+ {credentialFields.map(([key, inputSubSchema]) => ( + + setCredentialValue(key, value!) + } + siblingInputs={inputs} + /> + ))} +
+
+ )} +
+
+
+ {trigger ? ( +
+ +
+ ) : null} +
+ ); +} diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTriggerView/components/SelectedTriggerActions.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTriggerView/components/SelectedTriggerActions.tsx new file mode 100644 index 0000000000..ed37c864a2 --- /dev/null +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTriggerView/components/SelectedTriggerActions.tsx @@ -0,0 +1,149 @@ +"use client"; + +import { + getGetV2ListPresetsQueryKey, + useDeleteV2DeleteAPreset, +} from "@/app/api/__generated__/endpoints/presets/presets"; +import type { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent"; +import type { LibraryAgentPresetResponse } from "@/app/api/__generated__/models/libraryAgentPresetResponse"; +import { okData } from "@/app/api/helpers"; +import { Button } from "@/components/atoms/Button/Button"; +import { LoadingSpinner } from "@/components/atoms/LoadingSpinner/LoadingSpinner"; +import { Text } from "@/components/atoms/Text/Text"; +import { Dialog } from "@/components/molecules/Dialog/Dialog"; +import { useToast } from "@/components/molecules/Toast/use-toast"; +import { FloppyDiskIcon, TrashIcon } from "@phosphor-icons/react"; +import { useQueryClient } from "@tanstack/react-query"; +import { useState } from "react"; + +interface Props { + agent: LibraryAgent; + triggerId: string; + onDeleted?: () => void; + onSaveChanges?: () => void; + isSaving?: boolean; + onSwitchToRunsTab?: () => void; +} + +export function SelectedTriggerActions({ + agent, + triggerId, + onDeleted, + onSaveChanges, + isSaving, + onSwitchToRunsTab, +}: Props) { + const { toast } = useToast(); + const queryClient = useQueryClient(); + const [showDeleteDialog, setShowDeleteDialog] = useState(false); + + const deleteMutation = useDeleteV2DeleteAPreset({ + mutation: { + onSuccess: async () => { + toast({ + title: "Trigger deleted", + }); + const queryKey = getGetV2ListPresetsQueryKey({ + graph_id: agent.graph_id, + }); + + queryClient.invalidateQueries({ + queryKey, + }); + + const queryData = queryClient.getQueryData<{ + data: LibraryAgentPresetResponse; + }>(queryKey); + + const presets = + okData(queryData)?.presets ?? []; + const triggers = presets.filter( + (preset) => preset.webhook_id && preset.webhook, + ); + + setShowDeleteDialog(false); + onDeleted?.(); + + if (triggers.length === 0 && onSwitchToRunsTab) { + onSwitchToRunsTab(); + } + }, + onError: (error: any) => { + toast({ + title: "Failed to delete trigger", + description: error.message || "An unexpected error occurred.", + variant: "destructive", + }); + }, + }, + }); + + function handleDelete() { + deleteMutation.mutate({ presetId: triggerId }); + } + + return ( + <> +
+ + +
+ + + + + Are you sure you want to delete this trigger? This action cannot be + undone. + + + + + + + + + ); +} diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTriggerView/useSelectedTriggerView.ts b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTriggerView/useSelectedTriggerView.ts new file mode 100644 index 0000000000..4669d850b2 --- /dev/null +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/SelectedTriggerView/useSelectedTriggerView.ts @@ -0,0 +1,141 @@ +"use client"; + +import { + getGetV2GetASpecificPresetQueryKey, + getGetV2ListPresetsQueryKey, + useGetV2GetASpecificPreset, + usePatchV2UpdateAnExistingPreset, +} from "@/app/api/__generated__/endpoints/presets/presets"; +import type { LibraryAgentPreset } from "@/app/api/__generated__/models/libraryAgentPreset"; +import type { LibraryAgentPresetUpdatable } from "@/app/api/__generated__/models/libraryAgentPresetUpdatable"; +import { okData } from "@/app/api/helpers"; +import { useToast } from "@/components/molecules/Toast/use-toast"; +import type { CredentialsMetaInput } from "@/lib/autogpt-server-api/types"; +import { useQueryClient } from "@tanstack/react-query"; +import { useEffect, useState } from "react"; + +type Args = { + triggerId: string; + graphId: string; +}; + +export function useSelectedTriggerView({ triggerId, graphId }: Args) { + const { toast } = useToast(); + const queryClient = useQueryClient(); + + const query = useGetV2GetASpecificPreset(triggerId, { + query: { + enabled: !!triggerId, + select: (res) => okData(res), + }, + }); + + const [name, setName] = useState(""); + const [description, setDescription] = useState(""); + const [inputs, setInputs] = useState>({}); + const [credentials, setCredentials] = useState< + Record + >({}); + + useEffect(() => { + if (query.data) { + setName(query.data.name || ""); + setDescription(query.data.description || ""); + setInputs(query.data.inputs || {}); + setCredentials(query.data.credentials || {}); + } + }, [query.data]); + + const updateMutation = usePatchV2UpdateAnExistingPreset({ + mutation: { + onSuccess: (response) => { + if (response.status === 200) { + toast({ + title: "Trigger updated", + }); + queryClient.invalidateQueries({ + queryKey: getGetV2GetASpecificPresetQueryKey(triggerId), + }); + queryClient.invalidateQueries({ + queryKey: getGetV2ListPresetsQueryKey({ graph_id: graphId }), + }); + } + }, + onError: (error: any) => { + toast({ + title: "Failed to update trigger", + description: error.message || "An unexpected error occurred.", + variant: "destructive", + }); + }, + }, + }); + + function handleSaveChanges() { + if (!query.data) return; + + const updateData: LibraryAgentPresetUpdatable = {}; + if (name !== (query.data.name || "")) { + updateData.name = name; + } + + if (description !== (query.data.description || "")) { + updateData.description = description; + } + + const inputsChanged = + JSON.stringify(inputs) !== JSON.stringify(query.data.inputs || {}); + + const credentialsChanged = + JSON.stringify(credentials) !== + JSON.stringify(query.data.credentials || {}); + + if (inputsChanged || credentialsChanged) { + updateData.inputs = inputs; + updateData.credentials = credentials; + } + + updateMutation.mutate({ + presetId: triggerId, + data: updateData, + }); + } + + function setInputValue(key: string, value: any) { + setInputs((prev) => ({ ...prev, [key]: value })); + } + + function setCredentialValue(key: string, value: CredentialsMetaInput) { + setCredentials((prev) => ({ ...prev, [key]: value })); + } + + const httpError = + query.isSuccess && !query.data + ? { status: 404, statusText: "Not found" } + : undefined; + + useEffect(() => { + if (updateMutation.isSuccess && query.data) { + setName(query.data.name || ""); + setDescription(query.data.description || ""); + setInputs(query.data.inputs || {}); + setCredentials(query.data.credentials || {}); + } + }, [updateMutation.isSuccess, query.data]); + + return { + trigger: query.data, + isLoading: query.isLoading, + error: query.error || httpError, + name, + setName, + description, + setDescription, + inputs, + setInputValue, + credentials, + setCredentialValue, + handleSaveChanges, + isSaving: updateMutation.isPending, + } as const; +} diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/SidebarRunsList.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/SidebarRunsList.tsx index a8d0eeb8e9..e893f2101b 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/SidebarRunsList.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/SidebarRunsList.tsx @@ -16,17 +16,24 @@ import { cn } from "@/lib/utils"; import { AGENT_LIBRARY_SECTION_PADDING_X } from "../../../helpers"; import { RunListItem } from "./components/RunListItem"; import { ScheduleListItem } from "./components/ScheduleListItem"; +import { TemplateListItem } from "./components/TemplateListItem"; +import { TriggerListItem } from "./components/TriggerListItem"; import { useSidebarRunsList } from "./useSidebarRunsList"; interface Props { agent: LibraryAgent; selectedRunId?: string; - onSelectRun: (id: string, tab?: "runs" | "scheduled") => void; + onSelectRun: ( + id: string, + tab?: "runs" | "scheduled" | "templates" | "triggers", + ) => void; onClearSelectedRun?: () => void; - onTabChange?: (tab: "runs" | "scheduled" | "templates") => void; + onTabChange?: (tab: "runs" | "scheduled" | "templates" | "triggers") => void; onCountsChange?: (info: { runsCount: number; schedulesCount: number; + templatesCount: number; + triggersCount: number; loading?: boolean; }) => void; } @@ -42,8 +49,12 @@ export function SidebarRunsList({ const { runs, schedules, + templates, + triggers, runsCount, schedulesCount, + templatesCount, + triggersCount, error, loading, fetchMoreRuns, @@ -79,7 +90,7 @@ export function SidebarRunsList({ { - const value = v as "runs" | "scheduled" | "templates"; + const value = v as "runs" | "scheduled" | "templates" | "triggers"; onTabChange?.(value); if (value === "runs") { if (runs && runs.length) { @@ -95,6 +106,8 @@ export function SidebarRunsList({ } } else if (value === "templates") { onClearSelectedRun?.(); + } else if (value === "triggers") { + onClearSelectedRun?.(); } }} className="flex min-h-0 flex-col overflow-hidden" @@ -106,8 +119,13 @@ export function SidebarRunsList({ Scheduled {schedulesCount} + {triggersCount > 0 && ( + + Triggers {triggersCount} + + )} - Templates 0 + Templates {templatesCount} @@ -165,6 +183,35 @@ export function SidebarRunsList({ )}
+ {triggersCount > 0 && ( + +
+ {triggers.length > 0 ? ( + triggers.map((trigger) => ( +
+ onSelectRun(trigger.id, "triggers")} + /> +
+ )) + ) : ( +
+ + No triggers set up + +
+ )} +
+
+ )}
-
- - No templates saved - -
+ {templates.length > 0 ? ( + templates.map((template) => ( +
+ onSelectRun(template.id, "templates")} + /> +
+ )) + ) : ( +
+ + No templates saved + +
+ )}
diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/components/ScheduleListItem.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/components/ScheduleListItem.tsx index b06b67647d..2265a92f62 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/components/ScheduleListItem.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/components/ScheduleListItem.tsx @@ -1,11 +1,10 @@ "use client"; -import React from "react"; import { GraphExecutionJobInfo } from "@/app/api/__generated__/models/graphExecutionJobInfo"; -import moment from "moment"; -import { RunSidebarCard } from "./RunSidebarCard"; -import { IconWrapper } from "./RunIconWrapper"; import { ClockClockwiseIcon } from "@phosphor-icons/react"; +import moment from "moment"; +import { IconWrapper } from "./RunIconWrapper"; +import { RunSidebarCard } from "./RunSidebarCard"; interface ScheduleListItemProps { schedule: GraphExecutionJobInfo; @@ -25,10 +24,10 @@ export function ScheduleListItem({ onClick={onClick} selected={selected} icon={ - + diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/components/TemplateListItem.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/components/TemplateListItem.tsx new file mode 100644 index 0000000000..ffdd746416 --- /dev/null +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/components/TemplateListItem.tsx @@ -0,0 +1,33 @@ +"use client"; + +import { LibraryAgentPreset } from "@/app/api/__generated__/models/libraryAgentPreset"; +import { FileTextIcon } from "@phosphor-icons/react"; +import moment from "moment"; +import { IconWrapper } from "./RunIconWrapper"; +import { RunSidebarCard } from "./RunSidebarCard"; + +interface TemplateListItemProps { + template: LibraryAgentPreset; + selected?: boolean; + onClick?: () => void; +} + +export function TemplateListItem({ + template, + selected, + onClick, +}: TemplateListItemProps) { + return ( + + + + } + title={template.name} + description={moment(template.updated_at).fromNow()} + onClick={onClick} + selected={selected} + /> + ); +} diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/components/TriggerListItem.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/components/TriggerListItem.tsx new file mode 100644 index 0000000000..a5d339ad36 --- /dev/null +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/components/TriggerListItem.tsx @@ -0,0 +1,33 @@ +"use client"; + +import { LibraryAgentPreset } from "@/app/api/__generated__/models/libraryAgentPreset"; +import { LightningIcon } from "@phosphor-icons/react"; +import moment from "moment"; +import { IconWrapper } from "./RunIconWrapper"; +import { RunSidebarCard } from "./RunSidebarCard"; + +interface TriggerListItemProps { + trigger: LibraryAgentPreset; + selected?: boolean; + onClick?: () => void; +} + +export function TriggerListItem({ + trigger, + selected, + onClick, +}: TriggerListItemProps) { + return ( + + + + } + title={trigger.name} + description={moment(trigger.updated_at).fromNow()} + onClick={onClick} + selected={selected} + /> + ); +} diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/useSidebarRunsList.ts b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/useSidebarRunsList.ts index eecada463a..38ac1d79c2 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/useSidebarRunsList.ts +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/useSidebarRunsList.ts @@ -3,8 +3,10 @@ import { useEffect, useMemo } from "react"; import { useGetV1ListGraphExecutionsInfinite } from "@/app/api/__generated__/endpoints/graphs/graphs"; +import { useGetV2ListPresets } from "@/app/api/__generated__/endpoints/presets/presets"; import { useGetV1ListExecutionSchedulesForAGraph } from "@/app/api/__generated__/endpoints/schedules/schedules"; import type { GraphExecutionJobInfo } from "@/app/api/__generated__/models/graphExecutionJobInfo"; +import type { LibraryAgentPresetResponse } from "@/app/api/__generated__/models/libraryAgentPresetResponse"; import { okData } from "@/app/api/helpers"; import { useExecutionEvents } from "@/hooks/useExecutionEvents"; import { useQueryClient } from "@tanstack/react-query"; @@ -15,8 +17,15 @@ import { getNextRunsPageParam, } from "./helpers"; -function parseTab(value: string | null): "runs" | "scheduled" | "templates" { - if (value === "runs" || value === "scheduled" || value === "templates") { +function parseTab( + value: string | null, +): "runs" | "scheduled" | "templates" | "triggers" { + if ( + value === "runs" || + value === "scheduled" || + value === "templates" || + value === "triggers" + ) { return value; } return "runs"; @@ -24,10 +33,15 @@ function parseTab(value: string | null): "runs" | "scheduled" | "templates" { type Args = { graphId?: string; - onSelectRun: (runId: string, tab?: "runs" | "scheduled") => void; + onSelectRun: ( + runId: string, + tab?: "runs" | "scheduled" | "templates" | "triggers", + ) => void; onCountsChange?: (info: { runsCount: number; schedulesCount: number; + templatesCount: number; + triggersCount: number; loading?: boolean; }) => void; }; @@ -67,16 +81,40 @@ export function useSidebarRunsList({ }, ); + const presetsQuery = useGetV2ListPresets( + { graph_id: graphId || null, page: 1, page_size: 100 }, + { + query: { + enabled: !!graphId, + select: (r) => okData(r)?.presets ?? [], + }, + }, + ); + const runs = useMemo( () => extractRunsFromPages(runsQuery.data), [runsQuery.data], ); const schedules = schedulesQuery.data || []; + const allPresets = presetsQuery.data || []; + const triggers = useMemo( + () => allPresets.filter((preset) => preset.webhook_id && preset.webhook), + [allPresets], + ); + const templates = useMemo( + () => allPresets.filter((preset) => !preset.webhook_id || !preset.webhook), + [allPresets], + ); const runsCount = computeRunsCount(runsQuery.data, runs.length); const schedulesCount = schedules.length; - const loading = !schedulesQuery.isSuccess || !runsQuery.isSuccess; + const templatesCount = templates.length; + const triggersCount = triggers.length; + const loading = + !schedulesQuery.isSuccess || + !runsQuery.isSuccess || + !presetsQuery.isSuccess; // Update query cache when execution events arrive via websocket useExecutionEvents({ @@ -94,9 +132,22 @@ export function useSidebarRunsList({ // Notify parent about counts and loading state useEffect(() => { if (onCountsChange) { - onCountsChange({ runsCount, schedulesCount, loading }); + onCountsChange({ + runsCount, + schedulesCount, + templatesCount, + triggersCount, + loading, + }); } - }, [runsCount, schedulesCount, loading, onCountsChange]); + }, [ + runsCount, + schedulesCount, + templatesCount, + triggersCount, + loading, + onCountsChange, + ]); useEffect(() => { if (runs.length > 0 && tabValue === "runs" && !activeItem) { @@ -111,15 +162,31 @@ export function useSidebarRunsList({ } }, [activeItem, runs.length, schedules, onSelectRun]); + useEffect(() => { + if (templates.length > 0 && tabValue === "templates" && !activeItem) { + onSelectRun(templates[0].id, "templates"); + } + }, [templates, activeItem, tabValue, onSelectRun]); + + useEffect(() => { + if (triggers.length > 0 && tabValue === "triggers" && !activeItem) { + onSelectRun(triggers[0].id, "triggers"); + } + }, [triggers, activeItem, tabValue, onSelectRun]); + return { runs, schedules, - error: schedulesQuery.error || runsQuery.error, + templates, + triggers, + error: schedulesQuery.error || runsQuery.error || presetsQuery.error, loading, runsQuery, tabValue, runsCount, schedulesCount, + templatesCount, + triggersCount, fetchMoreRuns: runsQuery.fetchNextPage, hasMoreRuns: runsQuery.hasNextPage, isFetchingMoreRuns: runsQuery.isFetchingNextPage, diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/useNewAgentLibraryView.ts b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/useNewAgentLibraryView.ts index 46b9c9abc7..b7b6301ad6 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/useNewAgentLibraryView.ts +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/useNewAgentLibraryView.ts @@ -5,8 +5,15 @@ import { useParams } from "next/navigation"; import { parseAsString, useQueryStates } from "nuqs"; import { useCallback, useEffect, useMemo, useState } from "react"; -function parseTab(value: string | null): "runs" | "scheduled" | "templates" { - if (value === "runs" || value === "scheduled" || value === "templates") { +function parseTab( + value: string | null, +): "runs" | "scheduled" | "templates" | "triggers" { + if ( + value === "runs" || + value === "scheduled" || + value === "templates" || + value === "triggers" + ) { return value; } return "runs"; @@ -45,6 +52,8 @@ export function useNewAgentLibraryView() { const [sidebarCounts, setSidebarCounts] = useState({ runsCount: 0, schedulesCount: 0, + templatesCount: 0, + triggersCount: 0, }); const [sidebarLoading, setSidebarLoading] = useState(true); @@ -52,7 +61,9 @@ export function useNewAgentLibraryView() { const hasAnyItems = useMemo( () => (sidebarCounts.runsCount ?? 0) > 0 || - (sidebarCounts.schedulesCount ?? 0) > 0, + (sidebarCounts.schedulesCount ?? 0) > 0 || + (sidebarCounts.templatesCount ?? 0) > 0 || + (sidebarCounts.triggersCount ?? 0) > 0, [sidebarCounts], ); @@ -65,7 +76,22 @@ export function useNewAgentLibraryView() { } }, [response]); - function handleSelectRun(id: string, tab?: "runs" | "scheduled") { + useEffect(() => { + if ( + activeTab === "triggers" && + sidebarCounts.triggersCount === 0 && + !sidebarLoading + ) { + setQueryStates({ + activeTab: "runs", + }); + } + }, [activeTab, sidebarCounts.triggersCount, sidebarLoading, setQueryStates]); + + function handleSelectRun( + id: string, + tab?: "runs" | "scheduled" | "templates" | "triggers", + ) { setQueryStates({ activeItem: id, activeTab: tab ?? "runs", @@ -78,7 +104,9 @@ export function useNewAgentLibraryView() { }); } - function handleSetActiveTab(tab: "runs" | "scheduled" | "templates") { + function handleSetActiveTab( + tab: "runs" | "scheduled" | "templates" | "triggers", + ) { setQueryStates({ activeTab: tab, }); @@ -88,11 +116,15 @@ export function useNewAgentLibraryView() { (counts: { runsCount: number; schedulesCount: number; + templatesCount: number; + triggersCount: number; loading?: boolean; }) => { setSidebarCounts({ runsCount: counts.runsCount, schedulesCount: counts.schedulesCount, + templatesCount: counts.templatesCount, + triggersCount: counts.triggersCount, }); if (counts.loading !== undefined) { setSidebarLoading(counts.loading);