+ return (
+
+
+
+
+ New task
+
+ }
+ agent={agent}
+ agentId={agent.id.toString()}
+ onRunCreated={(execution) => handleSelectRun(execution.id, "runs")}
+ onScheduleCreated={(schedule) =>
+ handleSelectRun(schedule.id, "scheduled")
+ }
+ />
+
+
+
+
+
+
+
- {selectedRun ? (
- selectedRun.startsWith("schedule:") ? (
+ {activeItem ? (
+ activeTab === "scheduled" ? (
) : (
)
) : sidebarLoading ? (
- // Show loading state while sidebar is loading to prevent flash of empty state
-
Loading runs...
- ) : hasAnyItems ? (
-
- Select a run to view its details
+
+
+
+
+
+ ) : activeTab === "scheduled" ? (
+
+ ) : activeTab === "templates" ? (
+
) : (
-
+
)}
-
+
);
}
diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/useAgentRunModal.ts b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/useAgentRunModal.ts
index 2e8fc02d97..92f9c2703c 100644
--- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/useAgentRunModal.ts
+++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/RunAgentModal/useAgentRunModal.ts
@@ -87,9 +87,13 @@ export function useAgentRunModal(
}
},
onError: (error: any) => {
+ const errorMessage = error.isGraphValidationError()
+ ? error.response.detail.message
+ : error.message;
+
toast({
title: "❌ Failed to execute agent",
- description: error.message || "An unexpected error occurred.",
+ description: errorMessage || "An unexpected error occurred.",
variant: "destructive",
});
},
diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptyAgentRuns.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptyAgentRuns.tsx
deleted file mode 100644
index 578a95b7f7..0000000000
--- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptyAgentRuns.tsx
+++ /dev/null
@@ -1,106 +0,0 @@
-import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
-import { Button } from "@/components/atoms/Button/Button";
-import { Link } from "@/components/atoms/Link/Link";
-import { Text } from "@/components/atoms/Text/Text";
-import { ShowMoreText } from "@/components/molecules/ShowMoreText/ShowMoreText";
-import { formatDate } from "@/lib/utils/time";
-import { RunAgentModal } from "../modals/RunAgentModal/RunAgentModal";
-import { RunDetailCard } from "../selected-views/RunDetailCard/RunDetailCard";
-import { EmptyRunsIllustration } from "./EmptyRunsIllustration";
-
-type Props = {
- agent: LibraryAgent;
-};
-
-export function EmptyAgentRuns({ agent }: Props) {
- const isPublished = Boolean(agent.marketplace_listing);
- const createdAt = formatDate(agent.created_at);
- const updatedAt = formatDate(agent.updated_at);
- const isUpdated = updatedAt !== createdAt;
-
- return (
-
-
-
-
-
-
-
-
- Ready to get started?
-
-
- Run your agent and this space will fill with your agent's
- activity
-
-
-
-
- Setup your task
-
- }
- agent={agent}
- agentId={agent.id.toString()}
- />
-
-
-
- {isPublished ? (
-
-
- About this agent
-
-
- {agent.name}
-
- by{" "}
-
- {agent.marketplace_listing?.creator.name}
-
-
-
-
- {agent.description ||
- `Note: If you're using Docker Compose watch mode (docker compose watch), it will automatically rebuild on file changes. Since you're using docker compose up -d, manual rebuilds are needed.
-You can test the endpoint from your frontend; it should return the marketplace_listing field when an agent has been published, or null if it hasn't.`}
-
-
-
-
-
- Agent created on
-
- {createdAt}
-
- {isUpdated ? (
-
-
- Agent updated on
-
- {updatedAt}
-
- ) : null}
-
-
-
- Edit agent
-
-
- Export agent to file
-
-
-
-
- ) : null}
-
- );
-}
diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptySchedules.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptySchedules.tsx
new file mode 100644
index 0000000000..97492d8a59
--- /dev/null
+++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptySchedules.tsx
@@ -0,0 +1,323 @@
+import { Text } from "@/components/atoms/Text/Text";
+
+export function EmptySchedules() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Nothing scheduled yet
+
+
+ Create a new run, and you'll have the option to schedule your
+ agent to run automatically.
+
+
+
+ );
+}
diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptySchedulesIllustration.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptySchedulesIllustration.tsx
new file mode 100644
index 0000000000..51d33ba098
--- /dev/null
+++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptySchedulesIllustration.tsx
@@ -0,0 +1,314 @@
+type Props = {
+ className?: string;
+};
+
+export function EmptySchedulesIllustration({ className }: Props) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptyTasks.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptyTasks.tsx
new file mode 100644
index 0000000000..c0c2c900a1
--- /dev/null
+++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptyTasks.tsx
@@ -0,0 +1,107 @@
+import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
+import { Button } from "@/components/atoms/Button/Button";
+import { Text } from "@/components/atoms/Text/Text";
+import { ShowMoreText } from "@/components/molecules/ShowMoreText/ShowMoreText";
+import { formatDate } from "@/lib/utils/time";
+import { RunAgentModal } from "../modals/RunAgentModal/RunAgentModal";
+import { RunDetailCard } from "../selected-views/RunDetailCard/RunDetailCard";
+import { EmptyTasksIllustration } from "./EmptyTasksIllustration";
+
+type Props = {
+ agent: LibraryAgent;
+};
+
+export function EmptyTasks({ agent }: Props) {
+ const isPublished = Boolean(agent.marketplace_listing);
+ const createdAt = formatDate(agent.created_at);
+ const updatedAt = formatDate(agent.updated_at);
+ const isUpdated = updatedAt !== createdAt;
+
+ return (
+
+
+
+
+
+
+
+
+ Ready to get started?
+
+
+ Run your agent and this space will fill with your agent's
+ activity
+
+
+
+
+ Setup your task
+
+ }
+ agent={agent}
+ agentId={agent.id.toString()}
+ />
+
+
+
+
+
+
+ About this agent
+
+
+ {agent.name}
+ {isPublished ? (
+
+ by {agent.marketplace_listing?.creator.name}
+
+ ) : null}
+
+
+ {agent.description ||
+ `This agent is not yet published. Once it is published, You can publish your agent by clicking the "Publish" button in the agent editor.`}
+
+
+
+
+
+ Agent created on
+
+
+ {createdAt}
+
+
+ {isUpdated ? (
+
+
+ Agent updated on
+
+
+ {updatedAt}
+
+
+ ) : null}
+
+
+
+ Edit agent
+
+
+ Export agent to file
+
+
+
+
+
+ );
+}
diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptyRunsIllustration.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptyTasksIllustration.tsx
similarity index 99%
rename from autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptyRunsIllustration.tsx
rename to autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptyTasksIllustration.tsx
index 13847884bc..978e6735df 100644
--- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptyRunsIllustration.tsx
+++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/EmptyTasksIllustration.tsx
@@ -2,7 +2,7 @@ type Props = {
className?: string;
};
-export function EmptyRunsIllustration({ className }: Props) {
+export function EmptyTasksIllustration({ className }: Props) {
return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ No templates yet
+
+
+ Create a task first, then save it as a template to reuse later —
+ it'll show up here.
+
+
+
+ );
+}
diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/SectionWrap.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/SectionWrap.tsx
new file mode 100644
index 0000000000..75571dd856
--- /dev/null
+++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/other/SectionWrap.tsx
@@ -0,0 +1,19 @@
+import { cn } from "@/lib/utils";
+
+type Props = {
+ children: React.ReactNode;
+ className?: string;
+};
+
+export function SectionWrap({ children, className }: Props) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/RunDetailCard/RunDetailCard.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/RunDetailCard/RunDetailCard.tsx
index d90a72e429..83df2d026a 100644
--- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/RunDetailCard/RunDetailCard.tsx
+++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/selected-views/RunDetailCard/RunDetailCard.tsx
@@ -9,7 +9,7 @@ export function RunDetailCard({ children, className }: Props) {
return (
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 7f9c5065d1..811c9d4f55 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
@@ -3,6 +3,7 @@ import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
import { Button } from "@/components/atoms/Button/Button";
import { Text } from "@/components/atoms/Text/Text";
import { Dialog } from "@/components/molecules/Dialog/Dialog";
+import { FloatingSafeModeToggle } from "@/components/molecules/FloatingSafeModeToggle/FloatingSafeModeToggle";
import { Flag, useGetFlag } from "@/services/feature-flags/use-get-flag";
import {
ArrowSquareOutIcon,
@@ -11,10 +12,10 @@ import {
TrashIcon,
} from "@phosphor-icons/react";
import moment from "moment";
+import { AGENT_LIBRARY_SECTION_PADDING_X } from "../../../helpers";
import { AgentActionsDropdown } from "../AgentActionsDropdown";
import { RunStatusBadge } from "../SelectedRunView/components/RunStatusBadge";
import { ShareRunButton } from "../ShareRunButton/ShareRunButton";
-import { FloatingSafeModeToggle } from "@/components/molecules/FloatingSafeModeToggle/FloatingSafeModeToggle";
import { useRunDetailHeader } from "./useRunDetailHeader";
type Props = {
@@ -49,16 +50,13 @@ export function RunDetailHeader({
} = useRunDetailHeader(agent.graph_id, run, onSelectRun, onClearSelectedRun);
return (
-
+
-
-
+
+
{run?.status ? : null}
-
+
{agent.name}
@@ -120,18 +118,18 @@ export function RunDetailHeader({
) : null}
{run ? (
-
-
+
+
Started {moment(run.started_at).fromNow()}
|
-
+
Version: {run.graph_version}
{run.stats?.node_exec_count !== undefined && (
<>
|
-
+
Steps: {run.stats.node_exec_count}
>
@@ -139,7 +137,7 @@ export function RunDetailHeader({
{run.stats?.duration !== undefined && (
<>
|
-
+
Duration:{" "}
{moment.duration(run.stats.duration, "seconds").humanize()}
@@ -148,7 +146,7 @@ export function RunDetailHeader({
{run.stats?.cost !== undefined && (
<>
|
-
+
Cost: ${(run.stats.cost / 100).toFixed(2)}
>
@@ -156,7 +154,7 @@ export function RunDetailHeader({
{run.stats?.activity_status && (
<>
|
-
+
{String(run.stats.activity_status)}
>
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 52122edf8d..857127164d 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
@@ -14,6 +14,7 @@ import { PendingReviewsList } from "@/components/organisms/PendingReviewsList/Pe
import { usePendingReviewsForExecution } from "@/hooks/usePendingReviews";
import { parseAsString, useQueryState } from "nuqs";
import { useEffect } from "react";
+import { AGENT_LIBRARY_SECTION_PADDING_X } from "../../../helpers";
import { AgentInputsReadOnly } from "../../modals/AgentInputsReadOnly/AgentInputsReadOnly";
import { RunDetailCard } from "../RunDetailCard/RunDetailCard";
import { RunDetailHeader } from "../RunDetailHeader/RunDetailHeader";
@@ -68,7 +69,7 @@ export function SelectedRunView({
if (isLoading && !run) {
return (
-
+
@@ -78,7 +79,7 @@ export function SelectedRunView({
}
return (
-
+
-
+
Output
Your input
{run?.status === AgentExecutionStatus.REVIEW && (
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 fb5a84a3b1..48d93ec64d 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
@@ -13,6 +13,7 @@ import {
} from "@/components/molecules/TabsLine/TabsLine";
import { humanizeCronExpression } from "@/lib/cron-expression-utils";
import { formatInTimezone, getTimezoneDisplayName } from "@/lib/timezone-utils";
+import { AGENT_LIBRARY_SECTION_PADDING_X } from "../../../helpers";
import { AgentInputsReadOnly } from "../../modals/AgentInputsReadOnly/AgentInputsReadOnly";
import { RunDetailCard } from "../RunDetailCard/RunDetailCard";
import { RunDetailHeader } from "../RunDetailHeader/RunDetailHeader";
@@ -68,7 +69,7 @@ export function SelectedScheduleView({
if (isLoading && !schedule) {
return (
-
+
@@ -78,7 +79,7 @@ export function SelectedScheduleView({
}
return (
-
+
@@ -103,7 +104,7 @@ export function SelectedScheduleView({
-
+
Your input
Schedule
diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/AgentRunsLists/AgentRunsLists.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/AgentRunsLists/AgentRunsLists.tsx
deleted file mode 100644
index bfd10d98b8..0000000000
--- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/AgentRunsLists/AgentRunsLists.tsx
+++ /dev/null
@@ -1,128 +0,0 @@
-"use client";
-
-import type { GraphExecutionJobInfo } from "@/app/api/__generated__/models/graphExecutionJobInfo";
-import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
-import { Skeleton } from "@/components/__legacy__/ui/skeleton";
-import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
-import { InfiniteList } from "@/components/molecules/InfiniteList/InfiniteList";
-import {
- TabsLine,
- TabsLineContent,
- TabsLineList,
- TabsLineTrigger,
-} from "@/components/molecules/TabsLine/TabsLine";
-import { RunListItem } from "./components/RunListItem";
-import { ScheduleListItem } from "./components/ScheduleListItem";
-import { useAgentRunsLists } from "./useAgentRunsLists";
-
-interface Props {
- agent: LibraryAgent;
- selectedRunId?: string;
- onSelectRun: (id: string) => void;
- onCountsChange?: (info: {
- runsCount: number;
- schedulesCount: number;
- loading?: boolean;
- }) => void;
-}
-
-export function AgentRunsLists({
- agent,
- selectedRunId,
- onSelectRun,
- onCountsChange,
-}: Props) {
- const {
- runs,
- schedules,
- runsCount,
- schedulesCount,
- error,
- loading,
- fetchMoreRuns,
- hasMoreRuns,
- isFetchingMoreRuns,
- tabValue,
- setTabValue,
- } = useAgentRunsLists({
- graphId: agent.graph_id,
- onSelectRun,
- onCountsChange,
- });
-
- if (error) {
- return ;
- }
-
- if (loading) {
- return (
-
-
-
-
-
- );
- }
-
- return (
- {
- const value = v as "runs" | "scheduled";
- setTabValue(value);
- if (value === "runs") {
- if (runs && runs.length) onSelectRun(runs[0].id);
- } else {
- if (schedules && schedules.length)
- onSelectRun(`schedule:${schedules[0].id}`);
- }
- }}
- className="min-w-0 overflow-hidden"
- >
-
-
- Runs {runsCount}
-
-
- Scheduled {schedulesCount}
-
-
-
- <>
-
- (
-
- onSelectRun && onSelectRun(run.id)}
- />
-
- )}
- />
-
-
-
- {schedules.map((s: GraphExecutionJobInfo) => (
-
- onSelectRun(`schedule:${s.id}`)}
- />
-
- ))}
-
-
- >
-
- );
-}
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
new file mode 100644
index 0000000000..6d5f2f98a6
--- /dev/null
+++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/SidebarRunsList.tsx
@@ -0,0 +1,181 @@
+"use client";
+
+import type { GraphExecutionJobInfo } from "@/app/api/__generated__/models/graphExecutionJobInfo";
+import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
+import { Skeleton } from "@/components/__legacy__/ui/skeleton";
+import { Text } from "@/components/atoms/Text/Text";
+import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
+import { InfiniteList } from "@/components/molecules/InfiniteList/InfiniteList";
+import {
+ TabsLine,
+ TabsLineContent,
+ TabsLineList,
+ TabsLineTrigger,
+} from "@/components/molecules/TabsLine/TabsLine";
+import { cn } from "@/lib/utils";
+import { AGENT_LIBRARY_SECTION_PADDING_X } from "../../../helpers";
+import { RunListItem } from "./components/RunListItem";
+import { ScheduleListItem } from "./components/ScheduleListItem";
+import { useSidebarRunsList } from "./useSidebarRunsList";
+
+interface Props {
+ agent: LibraryAgent;
+ selectedRunId?: string;
+ onSelectRun: (id: string, tab?: "runs" | "scheduled") => void;
+ onClearSelectedRun?: () => void;
+ onTabChange?: (tab: "runs" | "scheduled" | "templates") => void;
+ onCountsChange?: (info: {
+ runsCount: number;
+ schedulesCount: number;
+ loading?: boolean;
+ }) => void;
+}
+
+export function SidebarRunsList({
+ agent,
+ selectedRunId,
+ onSelectRun,
+ onClearSelectedRun,
+ onTabChange,
+ onCountsChange,
+}: Props) {
+ const {
+ runs,
+ schedules,
+ runsCount,
+ schedulesCount,
+ error,
+ loading,
+ fetchMoreRuns,
+ hasMoreRuns,
+ isFetchingMoreRuns,
+ tabValue,
+ } = useSidebarRunsList({
+ graphId: agent.graph_id,
+ onSelectRun,
+ onCountsChange,
+ });
+
+ if (error) {
+ return ;
+ }
+
+ if (loading) {
+ return (
+
+
+
+
+
+ );
+ }
+
+ return (
+ {
+ const value = v as "runs" | "scheduled" | "templates";
+ onTabChange?.(value);
+ if (value === "runs") {
+ if (runs && runs.length) {
+ onSelectRun(runs[0].id, "runs");
+ } else {
+ onClearSelectedRun?.();
+ }
+ } else if (value === "scheduled") {
+ if (schedules && schedules.length) {
+ onSelectRun(schedules[0].id, "scheduled");
+ } else {
+ onClearSelectedRun?.();
+ }
+ } else if (value === "templates") {
+ onClearSelectedRun?.();
+ }
+ }}
+ className="flex min-h-0 flex-col overflow-hidden"
+ >
+
+
+ Tasks {runsCount}
+
+
+ Scheduled {schedulesCount}
+
+
+ Templates 0
+
+
+
+ <>
+
+ (
+
+ onSelectRun && onSelectRun(run.id, "runs")}
+ />
+
+ )}
+ />
+
+
+
+ {schedules.length > 0 ? (
+ schedules.map((s: GraphExecutionJobInfo) => (
+
+ onSelectRun(s.id, "scheduled")}
+ />
+
+ ))
+ ) : (
+
+
+ No scheduled agents
+
+
+ )}
+
+
+
+
+
+
+ No templates saved
+
+
+
+
+ >
+
+ );
+}
diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/AgentRunsLists/components/RunIconWrapper.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/components/RunIconWrapper.tsx
similarity index 100%
rename from autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/AgentRunsLists/components/RunIconWrapper.tsx
rename to autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/components/RunIconWrapper.tsx
diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/AgentRunsLists/components/RunListItem.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/components/RunListItem.tsx
similarity index 100%
rename from autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/AgentRunsLists/components/RunListItem.tsx
rename to autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/components/RunListItem.tsx
index 89137cbaf7..c038217f72 100644
--- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/AgentRunsLists/components/RunListItem.tsx
+++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/components/RunListItem.tsx
@@ -1,10 +1,7 @@
"use client";
-import React from "react";
-import moment from "moment";
-import { GraphExecutionMeta } from "@/app/api/__generated__/models/graphExecutionMeta";
-import { RunSidebarCard } from "./RunSidebarCard";
import { AgentExecutionStatus } from "@/app/api/__generated__/models/agentExecutionStatus";
+import { GraphExecutionMeta } from "@/app/api/__generated__/models/graphExecutionMeta";
import {
CheckCircleIcon,
ClockIcon,
@@ -13,7 +10,10 @@ import {
WarningCircleIcon,
XCircleIcon,
} from "@phosphor-icons/react";
+import moment from "moment";
+import React from "react";
import { IconWrapper } from "./RunIconWrapper";
+import { RunSidebarCard } from "./RunSidebarCard";
const statusIconMap: Record = {
INCOMPLETE: (
diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/AgentRunsLists/components/RunSidebarCard.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/components/RunSidebarCard.tsx
similarity index 72%
rename from autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/AgentRunsLists/components/RunSidebarCard.tsx
rename to autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/components/RunSidebarCard.tsx
index d5f0e09b65..eb163f7337 100644
--- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/AgentRunsLists/components/RunSidebarCard.tsx
+++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/components/RunSidebarCard.tsx
@@ -1,8 +1,8 @@
"use client";
-import React from "react";
-import { cn } from "@/lib/utils";
import { Text } from "@/components/atoms/Text/Text";
+import { cn } from "@/lib/utils";
+import React from "react";
interface RunListItemProps {
title: string;
@@ -22,21 +22,21 @@ export function RunSidebarCard({
return (
{icon}
-
+
{title}
-
+
{description}
diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/AgentRunsLists/components/ScheduleListItem.tsx b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/components/ScheduleListItem.tsx
similarity index 100%
rename from autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/AgentRunsLists/components/ScheduleListItem.tsx
rename to autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/components/ScheduleListItem.tsx
diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/AgentRunsLists/helpers.ts b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/helpers.ts
similarity index 71%
rename from autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/AgentRunsLists/helpers.ts
rename to autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/helpers.ts
index 4cbe6787d9..096e40239b 100644
--- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/AgentRunsLists/helpers.ts
+++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/helpers.ts
@@ -1,8 +1,6 @@
import type { GraphExecutionsPaginated } from "@/app/api/__generated__/models/graphExecutionsPaginated";
import type { InfiniteData } from "@tanstack/react-query";
-const AGENT_RUNNING_POLL_INTERVAL = 1500;
-
function hasValidExecutionsData(
page: unknown,
): page is { data: GraphExecutionsPaginated } {
@@ -16,26 +14,6 @@ function hasValidExecutionsData(
);
}
-export function getRunsPollingInterval(
- pages: Array
| undefined,
- isRunsTab: boolean,
-): number | false {
- if (!isRunsTab || !pages?.length) return false;
-
- try {
- const executions = pages.flatMap((page) => {
- if (!hasValidExecutionsData(page)) return [];
- return page.data.executions || [];
- });
- const hasActive = executions.some(
- (e) => e.status === "RUNNING" || e.status === "QUEUED",
- );
- return hasActive ? AGENT_RUNNING_POLL_INTERVAL : false;
- } catch {
- return false;
- }
-}
-
export function computeRunsCount(
infiniteData: InfiniteData | undefined,
runsLength: number,
diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/AgentRunsLists/useAgentRunsLists.ts b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/useSidebarRunsList.ts
similarity index 56%
rename from autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/AgentRunsLists/useAgentRunsLists.ts
rename to autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/useSidebarRunsList.ts
index e6bd124006..eecada463a 100644
--- a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/AgentRunsLists/useAgentRunsLists.ts
+++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/sidebar/SidebarRunsList/useSidebarRunsList.ts
@@ -1,22 +1,30 @@
"use client";
-import { useEffect, useMemo, useState } from "react";
+import { useEffect, useMemo } from "react";
import { useGetV1ListGraphExecutionsInfinite } from "@/app/api/__generated__/endpoints/graphs/graphs";
import { useGetV1ListExecutionSchedulesForAGraph } from "@/app/api/__generated__/endpoints/schedules/schedules";
import type { GraphExecutionJobInfo } from "@/app/api/__generated__/models/graphExecutionJobInfo";
import { okData } from "@/app/api/helpers";
-import { useSearchParams } from "next/navigation";
+import { useExecutionEvents } from "@/hooks/useExecutionEvents";
+import { useQueryClient } from "@tanstack/react-query";
+import { parseAsString, useQueryStates } from "nuqs";
import {
computeRunsCount,
extractRunsFromPages,
getNextRunsPageParam,
- getRunsPollingInterval,
} from "./helpers";
+function parseTab(value: string | null): "runs" | "scheduled" | "templates" {
+ if (value === "runs" || value === "scheduled" || value === "templates") {
+ return value;
+ }
+ return "runs";
+}
+
type Args = {
graphId?: string;
- onSelectRun: (runId: string) => void;
+ onSelectRun: (runId: string, tab?: "runs" | "scheduled") => void;
onCountsChange?: (info: {
runsCount: number;
schedulesCount: number;
@@ -24,14 +32,18 @@ type Args = {
}) => void;
};
-export function useAgentRunsLists({
+export function useSidebarRunsList({
graphId,
onSelectRun,
onCountsChange,
}: Args) {
- const params = useSearchParams();
- const existingRunId = params.get("executionId") as string | undefined;
- const [tabValue, setTabValue] = useState<"runs" | "scheduled">("runs");
+ const [{ activeItem, activeTab: activeTabRaw }] = useQueryStates({
+ activeItem: parseAsString,
+ activeTab: parseAsString,
+ });
+
+ const tabValue = useMemo(() => parseTab(activeTabRaw), [activeTabRaw]);
+ const queryClient = useQueryClient();
const runsQuery = useGetV1ListGraphExecutionsInfinite(
graphId || "",
@@ -39,9 +51,6 @@ export function useAgentRunsLists({
{
query: {
enabled: !!graphId,
- refetchInterval: (q) =>
- getRunsPollingInterval(q.state.data?.pages, tabValue === "runs"),
- refetchIntervalInBackground: true,
refetchOnWindowFocus: false,
getNextPageParam: getNextRunsPageParam,
},
@@ -69,6 +78,19 @@ export function useAgentRunsLists({
const schedulesCount = schedules.length;
const loading = !schedulesQuery.isSuccess || !runsQuery.isSuccess;
+ // Update query cache when execution events arrive via websocket
+ useExecutionEvents({
+ graphId: graphId || undefined,
+ enabled: !!graphId && tabValue === "runs",
+ onExecutionUpdate: (_execution) => {
+ // Invalidate and refetch the query to ensure we have the latest data
+ // This is simpler and more reliable than manually updating the cache
+ // The queryKey is stable and includes the graphId, so this only invalidates
+ // queries for this specific graph's executions
+ queryClient.invalidateQueries({ queryKey: runsQuery.queryKey });
+ },
+ });
+
// Notify parent about counts and loading state
useEffect(() => {
if (onCountsChange) {
@@ -77,26 +99,17 @@ export function useAgentRunsLists({
}, [runsCount, schedulesCount, loading, onCountsChange]);
useEffect(() => {
- if (runs.length > 0) {
- if (existingRunId) {
- onSelectRun(existingRunId);
- return;
- }
- onSelectRun(runs[0].id);
+ if (runs.length > 0 && tabValue === "runs" && !activeItem) {
+ onSelectRun(runs[0].id, "runs");
}
- }, [runs, existingRunId]);
-
- useEffect(() => {
- if (existingRunId && existingRunId.startsWith("schedule:"))
- setTabValue("scheduled");
- else setTabValue("runs");
- }, [existingRunId]);
+ }, [runs, activeItem, tabValue, onSelectRun]);
// 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]);
+ if (!activeItem && runs.length === 0 && schedules.length > 0) {
+ onSelectRun(schedules[0].id, "scheduled");
+ }
+ }, [activeItem, runs.length, schedules, onSelectRun]);
return {
runs,
@@ -105,7 +118,6 @@ export function useAgentRunsLists({
loading,
runsQuery,
tabValue,
- setTabValue,
runsCount,
schedulesCount,
fetchMoreRuns: runsQuery.fetchNextPage,
diff --git a/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/helpers.ts b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/helpers.ts
new file mode 100644
index 0000000000..302468d5e8
--- /dev/null
+++ b/autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/helpers.ts
@@ -0,0 +1 @@
+export const AGENT_LIBRARY_SECTION_PADDING_X = "px-4";
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 427ca81706..011956cb40 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
@@ -2,8 +2,15 @@ import { useGetV2GetLibraryAgent } from "@/app/api/__generated__/endpoints/libra
import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
import { okData } from "@/app/api/helpers";
import { useParams } from "next/navigation";
-import { parseAsString, useQueryState } from "nuqs";
-import { useCallback, useMemo, useState } from "react";
+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") {
+ return value;
+ }
+ return "runs";
+}
export function useNewAgentLibraryView() {
const { id } = useParams();
@@ -18,8 +25,21 @@ export function useNewAgentLibraryView() {
},
});
- const [runParam, setRunParam] = useQueryState("executionId", parseAsString);
- const selectedRun = runParam ?? undefined;
+ const [{ activeItem, activeTab: activeTabRaw }, setQueryStates] =
+ useQueryStates({
+ activeItem: parseAsString,
+ activeTab: parseAsString,
+ });
+
+ const activeTab = useMemo(() => parseTab(activeTabRaw), [activeTabRaw]);
+
+ useEffect(() => {
+ if (!activeTabRaw) {
+ setQueryStates({
+ activeTab: "runs",
+ });
+ }
+ }, [activeTabRaw, setQueryStates]);
const [sidebarCounts, setSidebarCounts] = useState({
runsCount: 0,
@@ -38,12 +58,29 @@ export function useNewAgentLibraryView() {
// Show sidebar layout while loading or when there are items
const showSidebarLayout = sidebarLoading || hasAnyItems;
- function handleSelectRun(id: string) {
- setRunParam(id, { shallow: true });
+ useEffect(() => {
+ if (response) {
+ document.title = `${response.name} - Library - AutoGPT Platform`;
+ }
+ }, [response]);
+
+ function handleSelectRun(id: string, tab?: "runs" | "scheduled") {
+ setQueryStates({
+ activeItem: id,
+ activeTab: tab ?? "runs",
+ });
}
function handleClearSelectedRun() {
- setRunParam(null, { shallow: true });
+ setQueryStates({
+ activeItem: null,
+ });
+ }
+
+ function handleSetActiveTab(tab: "runs" | "scheduled" | "templates") {
+ setQueryStates({
+ activeTab: tab,
+ });
}
const handleCountsChange = useCallback(
@@ -70,8 +107,10 @@ export function useNewAgentLibraryView() {
agent: response,
hasAnyItems,
showSidebarLayout,
- selectedRun,
+ activeItem,
sidebarLoading,
+ activeTab,
+ setActiveTab: handleSetActiveTab,
handleClearSelectedRun,
handleCountsChange,
handleSelectRun,
diff --git a/autogpt_platform/frontend/src/components/atoms/Text/helpers.ts b/autogpt_platform/frontend/src/components/atoms/Text/helpers.ts
index 0b2eabe38d..cf5d9c1da0 100644
--- a/autogpt_platform/frontend/src/components/atoms/Text/helpers.ts
+++ b/autogpt_platform/frontend/src/components/atoms/Text/helpers.ts
@@ -14,33 +14,33 @@ export type As =
export const variants = {
// Headings
- h1: "font-poppins text-[2.75rem] font-[600] leading-[3.5rem] tracking-[-0.033rem] text-zinc-800",
- h2: "font-poppins text-[2rem] font-[500] leading-[2.5rem] text-zinc-800 tracking-[-0.02rem]",
- h3: "font-poppins text-[1.75rem] font-[500] leading-[2.5rem] text-zinc-800 tracking-[-0.01313rem]",
- h4: "font-poppins text-[1.375rem] font-[500] leading-[1.5rem] text-zinc-800",
- h5: "font-poppins text-[1rem] font-[500] leading-[1.5rem] text-zinc-800",
+ h1: "font-poppins text-[2.75rem] font-[600] leading-[3.5rem] tracking-[-0.033rem] text-black",
+ h2: "font-poppins text-[2rem] font-[500] leading-[2.5rem] text-black tracking-[-0.02rem]",
+ h3: "font-poppins text-[1.75rem] font-[500] leading-[2.5rem] text-black tracking-[-0.01313rem]",
+ h4: "font-poppins text-[1.375rem] font-[500] leading-[1.5rem] text-black",
+ h5: "font-poppins text-[1rem] font-[500] leading-[1.5rem] text-black",
// Body Text
- lead: "font-sans text-[1.25rem] font-[400] leading-[1.75rem] text-zinc-800",
+ lead: "font-sans text-[1.25rem] font-[400] leading-[1.75rem] text-black",
"lead-medium":
- "font-sans text-[1.25rem] font-[500] leading-[1.75rem] text-zinc-800",
+ "font-sans text-[1.25rem] font-[500] leading-[1.75rem] text-black",
"lead-semibold":
- "font-sans text-[1.25rem] font-[600] leading-[1.75rem] text-zinc-800",
- large: "font-sans text-[1rem] font-[400] leading-[1.625rem] text-zinc-800",
+ "font-sans text-[1.25rem] font-[600] leading-[1.75rem] text-black",
+ large: "font-sans text-[1rem] font-[400] leading-[1.625rem] text-black",
"large-medium":
- "font-sans text-[1rem] font-[500] leading-[1.625rem] text-zinc-800",
+ "font-sans text-[1rem] font-[500] leading-[1.625rem] text-black",
"large-semibold":
- "font-sans text-[1rem] font-[600] leading-[1.625rem] text-zinc-800",
- body: "font-sans text-[0.875rem] font-[400] leading-[1.375rem] text-zinc-800",
+ "font-sans text-[1rem] font-[600] leading-[1.625rem] text-black",
+ body: "font-sans text-[0.875rem] font-[400] leading-[1.375rem] text-black",
"body-medium":
- "font-sans text-[0.875rem] font-[500] leading-[1.375rem] text-zinc-800",
- small: "font-sans text-[0.75rem] font-[400] leading-[1.125rem] text-zinc-800",
+ "font-sans text-[0.875rem] font-[500] leading-[1.375rem] text-black",
+ small: "font-sans text-[0.75rem] font-[400] leading-[1.125rem] text-black",
"small-medium":
- "font-sans text-[0.75rem] font-[500] leading-[1.125rem] text-zinc-800",
+ "font-sans text-[0.75rem] font-[500] leading-[1.125rem] text-black",
// Label Text
label:
- "font-sans text-[0.6785rem] font-medium uppercase leading-[1.25rem] tracking-[0.06785rem] text-zinc-800",
+ "font-sans text-[0.6785rem] font-medium uppercase leading-[1.25rem] tracking-[0.06785rem] text-black",
} as const;
export type Variant = keyof typeof variants;
diff --git a/autogpt_platform/frontend/src/components/layout/Navbar/components/AgentActivityDropdown/useAgentActivityDropdown.ts b/autogpt_platform/frontend/src/components/layout/Navbar/components/AgentActivityDropdown/useAgentActivityDropdown.ts
index 6df18738ca..df8402906b 100644
--- a/autogpt_platform/frontend/src/components/layout/Navbar/components/AgentActivityDropdown/useAgentActivityDropdown.ts
+++ b/autogpt_platform/frontend/src/components/layout/Navbar/components/AgentActivityDropdown/useAgentActivityDropdown.ts
@@ -1,19 +1,17 @@
import { useGetV1ListAllExecutions } from "@/app/api/__generated__/endpoints/graphs/graphs";
-import BackendAPI from "@/lib/autogpt-server-api/client";
-import type { GraphExecution, GraphID } from "@/lib/autogpt-server-api/types";
-import { useCallback, useEffect, useState } from "react";
-import * as Sentry from "@sentry/nextjs";
+import { useExecutionEvents } from "@/hooks/useExecutionEvents";
+import { useLibraryAgents } from "@/hooks/useLibraryAgents/useLibraryAgents";
+import type { GraphExecution } from "@/lib/autogpt-server-api/types";
+import { useCallback, useEffect, useMemo, useState } from "react";
import {
NotificationState,
categorizeExecutions,
handleExecutionUpdate,
} from "./helpers";
-import { useLibraryAgents } from "@/hooks/useLibraryAgents/useLibraryAgents";
export function useAgentActivityDropdown() {
const [isOpen, setIsOpen] = useState(false);
- const [api] = useState(() => new BackendAPI());
const { agentInfoMap } = useLibraryAgents();
const [notifications, setNotifications] = useState({
@@ -23,8 +21,6 @@ export function useAgentActivityDropdown() {
totalCount: 0,
});
- const [isConnected, setIsConnected] = useState(false);
-
const {
data: executions,
isSuccess: executionsSuccess,
@@ -33,6 +29,12 @@ export function useAgentActivityDropdown() {
query: { select: (res) => (res.status === 200 ? res.data : null) },
});
+ // Get all graph IDs from agentInfoMap
+ const graphIds = useMemo(
+ () => Array.from(agentInfoMap.keys()),
+ [agentInfoMap],
+ );
+
// Handle real-time execution updates
const handleExecutionEvent = useCallback(
(execution: GraphExecution) => {
@@ -51,45 +53,15 @@ export function useAgentActivityDropdown() {
}
}, [executions, executionsSuccess, agentInfoMap]);
- // Initialize WebSocket connection for real-time updates
- useEffect(() => {
- if (!agentInfoMap.size) return;
-
- const connectHandler = api.onWebSocketConnect(() => {
- setIsConnected(true);
- agentInfoMap.forEach((_, graphId) => {
- api.subscribeToGraphExecutions(graphId as GraphID).catch((error) => {
- Sentry.captureException(error, {
- tags: {
- graphId,
- },
- });
- });
- });
- });
-
- const disconnectHandler = api.onWebSocketDisconnect(() => {
- setIsConnected(false);
- });
-
- const messageHandler = api.onWebSocketMessage(
- "graph_execution_event",
- handleExecutionEvent,
- );
-
- api.connectWebSocket();
-
- return () => {
- connectHandler();
- disconnectHandler();
- messageHandler();
- api.disconnectWebSocket();
- };
- }, [api, handleExecutionEvent, agentInfoMap]);
+ // Subscribe to execution events for all graphs
+ useExecutionEvents({
+ graphIds: graphIds.length > 0 ? graphIds : undefined,
+ enabled: graphIds.length > 0,
+ onExecutionUpdate: handleExecutionEvent,
+ });
return {
...notifications,
- isConnected,
isReady: executionsSuccess,
error: executionsError,
isOpen,
diff --git a/autogpt_platform/frontend/src/components/molecules/Breadcrumbs/Breadcrumbs.tsx b/autogpt_platform/frontend/src/components/molecules/Breadcrumbs/Breadcrumbs.tsx
index 89ac45282c..1fb69763fe 100644
--- a/autogpt_platform/frontend/src/components/molecules/Breadcrumbs/Breadcrumbs.tsx
+++ b/autogpt_platform/frontend/src/components/molecules/Breadcrumbs/Breadcrumbs.tsx
@@ -13,17 +13,17 @@ interface Props {
export function Breadcrumbs({ items }: Props) {
return (
-
+
{items.map((item, index) => (
{item.name}
{index < items.length - 1 && (
-
+
/
)}
diff --git a/autogpt_platform/frontend/src/components/molecules/TabsLine/TabsLine.tsx b/autogpt_platform/frontend/src/components/molecules/TabsLine/TabsLine.tsx
index 2c0f6ac4a5..0eeda6c110 100644
--- a/autogpt_platform/frontend/src/components/molecules/TabsLine/TabsLine.tsx
+++ b/autogpt_platform/frontend/src/components/molecules/TabsLine/TabsLine.tsx
@@ -52,7 +52,7 @@ const TabsLineList = React.forwardRef<
listRef.current = node;
}}
className={cn(
- "inline-flex w-full items-center justify-start border-b border-zinc-200",
+ "inline-flex w-full items-center justify-start border-b border-zinc-100",
className,
)}
{...props}
@@ -109,7 +109,7 @@ const TabsLineTrigger = React.forwardRef<
elementRef.current = node;
}}
className={cn(
- "relative inline-flex items-center justify-center whitespace-nowrap px-3 py-3 font-sans text-[1rem] font-medium leading-[1.5rem] text-zinc-700 transition-all data-[state=active]:text-purple-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-400 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
+ "relative inline-flex items-center justify-center whitespace-nowrap px-3 py-3 font-sans text-[0.875rem] font-medium leading-[1.5rem] text-zinc-700 transition-all data-[state=active]:text-purple-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-400 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
className,
)}
{...props}
diff --git a/autogpt_platform/frontend/src/components/styles/colors.ts b/autogpt_platform/frontend/src/components/styles/colors.ts
index 50129e2093..4e37b9bdd5 100644
--- a/autogpt_platform/frontend/src/components/styles/colors.ts
+++ b/autogpt_platform/frontend/src/components/styles/colors.ts
@@ -19,7 +19,7 @@ export const colors = {
400: "#ADADB3",
500: "#83838C",
600: "#68686F",
- 700: "#8E98A8",
+ 700: "#505057",
800: "#3E3E43",
900: "#2C2C30",
},
diff --git a/autogpt_platform/frontend/src/hooks/useExecutionEvents.ts b/autogpt_platform/frontend/src/hooks/useExecutionEvents.ts
new file mode 100644
index 0000000000..9af2b8aead
--- /dev/null
+++ b/autogpt_platform/frontend/src/hooks/useExecutionEvents.ts
@@ -0,0 +1,99 @@
+"use client";
+
+import { useBackendAPI } from "@/lib/autogpt-server-api/context";
+import type { GraphExecution, GraphID } from "@/lib/autogpt-server-api/types";
+import * as Sentry from "@sentry/nextjs";
+import { useEffect, useRef } from "react";
+
+type ExecutionEventHandler = (execution: GraphExecution) => void;
+
+interface UseExecutionEventsOptions {
+ graphId?: GraphID | string | null;
+ graphIds?: (GraphID | string)[];
+ enabled?: boolean;
+ onExecutionUpdate?: ExecutionEventHandler;
+}
+
+/**
+ * Generic hook to subscribe to graph execution events via WebSocket.
+ * Automatically handles subscription/unsubscription and reconnection.
+ *
+ * @param options - Configuration options
+ * @param options.graphId - The graph ID to subscribe to (single graph)
+ * @param options.graphIds - Array of graph IDs to subscribe to (multiple graphs)
+ * @param options.enabled - Whether the subscription is enabled (default: true)
+ * @param options.onExecutionUpdate - Callback invoked when an execution is updated
+ */
+export function useExecutionEvents({
+ graphId,
+ graphIds,
+ enabled = true,
+ onExecutionUpdate,
+}: UseExecutionEventsOptions) {
+ const api = useBackendAPI();
+ const onExecutionUpdateRef = useRef(onExecutionUpdate);
+
+ useEffect(() => {
+ onExecutionUpdateRef.current = onExecutionUpdate;
+ }, [onExecutionUpdate]);
+
+ useEffect(() => {
+ if (!enabled) return;
+
+ const idsToSubscribe = graphIds || (graphId ? [graphId] : []);
+ if (idsToSubscribe.length === 0) return;
+
+ // Normalize IDs to strings for consistent comparison
+ const normalizedIds = idsToSubscribe.map((id) => String(id));
+ const subscribedIds = new Set();
+
+ const handleExecutionEvent = (execution: GraphExecution) => {
+ // Filter by graphIds if provided, using normalized string comparison
+ if (normalizedIds.length > 0) {
+ const executionGraphId = String(execution.graph_id);
+ if (!normalizedIds.includes(executionGraphId)) return;
+ }
+
+ onExecutionUpdateRef.current?.(execution);
+ };
+
+ const connectHandler = api.onWebSocketConnect(() => {
+ normalizedIds.forEach((id) => {
+ // Track subscriptions to avoid duplicate subscriptions
+ if (subscribedIds.has(id)) return;
+ subscribedIds.add(id);
+
+ api
+ .subscribeToGraphExecutions(id as GraphID)
+ .then(() => {
+ console.debug(`Subscribed to execution updates for graph ${id}`);
+ })
+ .catch((error) => {
+ console.error(
+ `Failed to subscribe to execution updates for graph ${id}:`,
+ error,
+ );
+ Sentry.captureException(error, {
+ tags: { graphId: id },
+ });
+ subscribedIds.delete(id);
+ });
+ });
+ });
+
+ const messageHandler = api.onWebSocketMessage(
+ "graph_execution_event",
+ handleExecutionEvent,
+ );
+
+ api.connectWebSocket();
+
+ return () => {
+ connectHandler();
+ messageHandler();
+ // Note: Backend automatically cleans up subscriptions on websocket disconnect
+ // If IDs change while connected, old subscriptions remain but are filtered client-side
+ subscribedIds.clear();
+ };
+ }, [api, graphId, graphIds, enabled]);
+}
diff --git a/autogpt_platform/frontend/src/lib/autogpt-server-api/helpers.ts b/autogpt_platform/frontend/src/lib/autogpt-server-api/helpers.ts
index 8607b45da0..7e20783042 100644
--- a/autogpt_platform/frontend/src/lib/autogpt-server-api/helpers.ts
+++ b/autogpt_platform/frontend/src/lib/autogpt-server-api/helpers.ts
@@ -1,4 +1,7 @@
-import { IMPERSONATION_HEADER_NAME } from "@/lib/constants";
+import {
+ API_KEY_HEADER_NAME,
+ IMPERSONATION_HEADER_NAME,
+} from "@/lib/constants";
import { getServerSupabase } from "@/lib/supabase/server/getServerSupabase";
import { environment } from "@/services/environment";
import { Key, storage } from "@/services/storage/local-storage";
@@ -154,6 +157,12 @@ export function createRequestHeaders(
if (impersonationHeader) {
headers[IMPERSONATION_HEADER_NAME] = impersonationHeader;
}
+
+ // Forward X-API-Key header if present
+ const apiKeyHeader = originalRequest.headers.get(API_KEY_HEADER_NAME);
+ if (apiKeyHeader) {
+ headers[API_KEY_HEADER_NAME] = apiKeyHeader;
+ }
}
return headers;
diff --git a/autogpt_platform/frontend/src/lib/constants.ts b/autogpt_platform/frontend/src/lib/constants.ts
index f275dbf919..433a37b00e 100644
--- a/autogpt_platform/frontend/src/lib/constants.ts
+++ b/autogpt_platform/frontend/src/lib/constants.ts
@@ -5,3 +5,6 @@
// Admin impersonation
export const IMPERSONATION_HEADER_NAME = "X-Act-As-User-Id";
export const IMPERSONATION_STORAGE_KEY = "admin-impersonate-user-id";
+
+// API key authentication
+export const API_KEY_HEADER_NAME = "X-API-Key";