diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/OutputRenderers/renderers/ImageRenderer.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/OutputRenderers/renderers/ImageRenderer.tsx index 7e2256f246..f2a7636c6e 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/OutputRenderers/renderers/ImageRenderer.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/OutputRenderers/renderers/ImageRenderer.tsx @@ -112,6 +112,7 @@ function renderImage( return (
+ {/* eslint-disable-next-line @next/next/no-img-element */} {altText} {/* Schedule Section - always visible */} -
+
{showScheduleView ? ( <> -
+
+ + +
+ onChange(sv.map((s) => parseInt(s)))} + aria-label="Select days of week" + /> +
+ ); +} diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunAgentModal/components/CronScheduler/YearlyPicker.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunAgentModal/components/CronScheduler/YearlyPicker.tsx new file mode 100644 index 0000000000..5d69073935 --- /dev/null +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunAgentModal/components/CronScheduler/YearlyPicker.tsx @@ -0,0 +1,58 @@ +"use client"; + +import React from "react"; +import { Text } from "@/components/atoms/Text/Text"; +import { MultiToggle } from "@/components/molecules/MultiToggle/MultiToggle"; + +const months = [ + { label: "Jan", value: 1 }, + { label: "Feb", value: 2 }, + { label: "Mar", value: 3 }, + { label: "Apr", value: 4 }, + { label: "May", value: 5 }, + { label: "Jun", value: 6 }, + { label: "Jul", value: 7 }, + { label: "Aug", value: 8 }, + { label: "Sep", value: 9 }, + { label: "Oct", value: 10 }, + { label: "Nov", value: 11 }, + { label: "Dec", value: 12 }, +]; + +export function YearlyPicker({ + values, + onChange, +}: { + values: number[]; + onChange: (v: number[]) => void; +}) { + function toggleAll() { + if (values.length === months.length) onChange([]); + else onChange(months.map((m) => m.value)); + } + const items = months.map((m) => ({ value: String(m.value), label: m.label })); + const selected = values.map((v) => String(v)); + + return ( +
+ + Months + +
+ +
+ onChange(sv.map((s) => parseInt(s)))} + aria-label="Select months" + /> +
+ ); +} diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunAgentModal/components/DefaultRunView/DefaultRunView.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunAgentModal/components/DefaultRunView/DefaultRunView.tsx index 9bd27dda48..c6c1a6f407 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunAgentModal/components/DefaultRunView/DefaultRunView.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunAgentModal/components/DefaultRunView/DefaultRunView.tsx @@ -22,7 +22,7 @@ export function DefaultRunView() { } = useRunAgentModalContext(); return ( -
+
{defaultRunType === "automatic-trigger" && } {/* Preset/Trigger fields */} diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunAgentModal/components/ScheduleView/ScheduleView.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunAgentModal/components/ScheduleView/ScheduleView.tsx index 7de4c7ea46..2d6b97eb08 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunAgentModal/components/ScheduleView/ScheduleView.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunAgentModal/components/ScheduleView/ScheduleView.tsx @@ -1,11 +1,9 @@ import { Input } from "@/components/atoms/Input/Input"; -import { MultiToggle } from "@/components/molecules/MultiToggle/MultiToggle"; import { Text } from "@/components/atoms/Text/Text"; -import { Select } from "@/components/atoms/Select/Select"; -import { useScheduleView } from "./useScheduleView"; import { useCallback, useState } from "react"; import { validateSchedule } from "./helpers"; import { TimezoneNotice } from "../TimezoneNotice/TimezoneNotice"; +import { CronScheduler } from "../CronScheduler/CronScheduler"; interface Props { scheduleName: string; @@ -24,41 +22,22 @@ export function ScheduleView({ onCronExpressionChange, onValidityChange, }: Props) { - const { - repeat, - selectedDays, - time, - repeatOptions, - dayItems, - setSelectedDays, - handleRepeatChange, - handleTimeChange, - handleSelectAll, - handleWeekdays, - handleWeekends, - } = useScheduleView({ onCronExpressionChange }); - function handleScheduleNameChange(e: React.ChangeEvent) { onScheduleNameChange(e.target.value); } const [errors, setErrors] = useState<{ scheduleName?: string; - time?: string; }>({}); const validateNow = useCallback( - (partial: { scheduleName?: string; time?: string }) => { - const fieldErrors = validateSchedule({ - scheduleName, - time, - ...partial, - }); + (partial: { scheduleName?: string }) => { + const fieldErrors = validateSchedule({ scheduleName, ...partial }); setErrors(fieldErrors); if (onValidityChange) onValidityChange(Object.keys(fieldErrors).length === 0); }, - [scheduleName, time, onValidityChange], + [scheduleName, onValidityChange], ); return ( @@ -67,12 +46,14 @@ export function ScheduleView({ id="schedule-name" label="Schedule Name" value={scheduleName} + size="small" onChange={(e) => { handleScheduleNameChange(e); validateNow({ scheduleName: e.target.value }); }} placeholder="Enter a name for this schedule" error={errors.scheduleName} + className="max-w-80" /> {recommendedScheduleCron && ( @@ -84,65 +65,15 @@ export function ScheduleView({
)} - { - const value = e.target.value.trim(); - handleTimeChange({ ...e, target: { ...e.target, value } } as any); - validateNow({ time: value }); - }} - placeholder="00:00" - error={errors.time} - /> -
+
+ +
+
diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunDetailHeader/RunDetailHeader.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunDetailHeader/RunDetailHeader.tsx index 7bebac4020..ccfd51d497 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunDetailHeader/RunDetailHeader.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunDetailHeader/RunDetailHeader.tsx @@ -2,23 +2,16 @@ import { RunStatusBadge } from "../RunDetails/components/RunStatusBadge"; import { Text } from "@/components/atoms/Text/Text"; import { Button } from "@/components/atoms/Button/Button"; import { - PencilSimpleIcon, TrashIcon, StopIcon, PlayIcon, - ArrowSquareOut, + ArrowSquareOutIcon, } from "@phosphor-icons/react"; import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent"; import moment from "moment"; import { GraphExecution } from "@/app/api/__generated__/models/graphExecution"; import { useRunDetailHeader } from "./useRunDetailHeader"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/molecules/DropdownMenu/DropdownMenu"; -import Link from "next/link"; +import { AgentActions } from "./components/AgentActions"; type Props = { agent: LibraryAgent; @@ -77,40 +70,28 @@ export function RunDetailHeader({ > Delete run - - - - - - {canStop ? ( - - Stop run - - ) : null} - {openInBuilderHref ? ( - - - Open in builder - - - ) : null} - - - Edit agent - - - - + {openInBuilderHref ? ( + + ) : null} + {canStop ? ( + + ) : null} +
) : null}
diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunDetailHeader/components/AgentActions.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunDetailHeader/components/AgentActions.tsx new file mode 100644 index 0000000000..44e8698bb1 --- /dev/null +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunDetailHeader/components/AgentActions.tsx @@ -0,0 +1,111 @@ +"use client"; + +import React, { useCallback } from "react"; +import { Button } from "@/components/atoms/Button/Button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/molecules/DropdownMenu/DropdownMenu"; +import Link from "next/link"; +import { + FileArrowDownIcon, + PencilSimpleIcon, + TrashIcon, +} from "@phosphor-icons/react"; +import type { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent"; +import { useRouter } from "next/navigation"; +import { useDeleteV2DeleteLibraryAgent } from "@/app/api/__generated__/endpoints/library/library"; +import { getV1GetGraphVersion } from "@/app/api/__generated__/endpoints/graphs/graphs"; +import { exportAsJSONFile } from "@/lib/utils"; +import { useToast } from "@/components/molecules/Toast/use-toast"; + +interface AgentActionsProps { + agent: LibraryAgent; +} + +export function AgentActions({ agent }: AgentActionsProps) { + const router = useRouter(); + const { toast } = useToast(); + const deleteMutation = useDeleteV2DeleteLibraryAgent(); + + const handleExport = useCallback(async () => { + try { + const res = await getV1GetGraphVersion( + agent.graph_id, + agent.graph_version, + { for_export: true }, + ); + if (res.status === 200) { + const filename = `${agent.name}_v${agent.graph_version}.json`; + exportAsJSONFile(res.data as any, filename); + toast({ title: "Agent exported" }); + } else { + toast({ title: "Failed to export agent", variant: "destructive" }); + } + } catch (e: any) { + toast({ + title: "Failed to export agent", + description: e?.message, + variant: "destructive", + }); + } + }, [agent.graph_id, agent.graph_version, agent.name, toast]); + + const handleDelete = useCallback(() => { + if (!agent?.id) return; + const confirmed = window.confirm( + "Are you sure you want to delete this agent? This action cannot be undone.", + ); + if (!confirmed) return; + deleteMutation.mutate( + { libraryAgentId: agent.id }, + { + onSuccess: () => { + toast({ title: "Agent deleted" }); + router.push("/library"); + }, + onError: (error: any) => + toast({ + title: "Failed to delete agent", + description: error?.message, + variant: "destructive", + }), + }, + ); + }, [agent.id, deleteMutation, router, toast]); + + return ( + + + + + + + + Edit agent + + + + Export agent to file + + + Delete agent + + + + ); +} diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunDetails/components/RunOutputs.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunDetails/components/RunOutputs.tsx index 5676240306..38a5da17cf 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunDetails/components/RunOutputs.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunDetails/components/RunOutputs.tsx @@ -83,8 +83,8 @@ export function RunOutputs({ outputs }: RunOutputsProps) { } return ( -
-
+
+
({ value: item.value, diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunsSidebar/useRunsSidebar.ts b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunsSidebar/useRunsSidebar.ts index 75dfaf6c3b..99f2a1175c 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunsSidebar/useRunsSidebar.ts +++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunsSidebar/useRunsSidebar.ts @@ -17,7 +17,7 @@ type Args = { export function useRunsSidebar({ graphId, onSelectRun }: Args) { const params = useSearchParams(); - const existingRunId = params.get("run") as string | undefined; + const existingRunId = params.get("executionId") as string | undefined; const [tabValue, setTabValue] = useState<"runs" | "scheduled">("runs"); const runsQuery = useGetV1ListGraphExecutionsInfinite( @@ -97,6 +97,12 @@ export function useRunsSidebar({ graphId, onSelectRun }: Args) { const schedules: GraphExecutionJobInfo[] = schedulesQuery.data?.status === 200 ? schedulesQuery.data.data : []; + // If there are no runs but there are schedules, and nothing is selected, auto-select the first schedule + useEffect(() => { + if (!existingRunId && runs.length === 0 && schedules.length > 0) + onSelectRun(`schedule:${schedules[0].id}`); + }, [existingRunId, runs.length, schedules, onSelectRun]); + return { runs, schedules, diff --git a/autogpt_platform/frontend/src/components/layout/Navbar/components/AgentActivityDropdown/components/ActivityDropdown/ActivityDropdown.tsx b/autogpt_platform/frontend/src/components/layout/Navbar/components/AgentActivityDropdown/components/ActivityDropdown/ActivityDropdown.tsx index f4dc003cd8..9bb30b2e94 100644 --- a/autogpt_platform/frontend/src/components/layout/Navbar/components/AgentActivityDropdown/components/ActivityDropdown/ActivityDropdown.tsx +++ b/autogpt_platform/frontend/src/components/layout/Navbar/components/AgentActivityDropdown/components/ActivityDropdown/ActivityDropdown.tsx @@ -146,7 +146,7 @@ export function ActivityDropdown({ {searchQuery ? "No matching agents found" - : "Nothing to show yet"} + : "No recent runs to show yet"} {searchQuery