mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
chore: updates
This commit is contained in:
@@ -21,7 +21,7 @@ export function AgentNotifications() {
|
||||
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
className={`group relative rounded-full p-2 transition-colors hover:bg-white ${isOpen ? "bg-white" : ""}`}
|
||||
className={`group relative h-[2.5rem] w-[2.5rem] rounded-full p-2 transition-colors hover:bg-white ${isOpen ? "bg-white" : ""}`}
|
||||
title="Agent Activity"
|
||||
>
|
||||
<Bell size={22} className="text-black" />
|
||||
@@ -34,7 +34,7 @@ export function AgentNotifications() {
|
||||
<div className="absolute -inset-0.5 animate-spin rounded-full border-[3px] border-transparent border-r-purple-200 border-t-purple-200" />
|
||||
</div>
|
||||
{/* Running Agent Hover Hint */}
|
||||
<div className="rounded-small absolute bottom-[-2.5rem] left-1/2 z-50 hidden -translate-x-1/2 transform whitespace-nowrap bg-white px-4 py-2 shadow-md group-hover:block">
|
||||
<div className="absolute bottom-[-2.5rem] left-1/2 z-50 hidden -translate-x-1/2 transform whitespace-nowrap rounded-small bg-white px-4 py-2 shadow-md group-hover:block">
|
||||
<Text variant="body-medium">
|
||||
{activeExecutions.length} running agent
|
||||
{activeExecutions.length > 1 ? "s" : ""}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { AgentExecutionStatus } from "@/app/api/__generated__/models/agentExecut
|
||||
import { Text } from "@/components/atoms/Text/Text";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { Bell } from "@phosphor-icons/react";
|
||||
import { AgentExecutionWithInfo } from "../helpers";
|
||||
import { AgentExecutionWithInfo, EXECUTION_DISPLAY_LIMIT } from "../helpers";
|
||||
import { NotificationItem } from "./NotificationItem";
|
||||
|
||||
interface NotificationDropdownProps {
|
||||
@@ -26,25 +26,27 @@ export function NotificationDropdown({
|
||||
...recentFailures.map((e) => ({ ...e, type: "failed" as const })),
|
||||
];
|
||||
|
||||
return allExecutions.sort((a, b) => {
|
||||
// Running/queued always at top
|
||||
const aIsActive =
|
||||
a.status === AgentExecutionStatus.RUNNING ||
|
||||
a.status === AgentExecutionStatus.QUEUED;
|
||||
const bIsActive =
|
||||
b.status === AgentExecutionStatus.RUNNING ||
|
||||
b.status === AgentExecutionStatus.QUEUED;
|
||||
return allExecutions
|
||||
.sort((a, b) => {
|
||||
// Running/queued always at top
|
||||
const aIsActive =
|
||||
a.status === AgentExecutionStatus.RUNNING ||
|
||||
a.status === AgentExecutionStatus.QUEUED;
|
||||
const bIsActive =
|
||||
b.status === AgentExecutionStatus.RUNNING ||
|
||||
b.status === AgentExecutionStatus.QUEUED;
|
||||
|
||||
if (aIsActive && !bIsActive) return -1;
|
||||
if (!aIsActive && bIsActive) return 1;
|
||||
if (aIsActive && !bIsActive) return -1;
|
||||
if (!aIsActive && bIsActive) return 1;
|
||||
|
||||
// Within same category, sort by most recent
|
||||
const aTime = aIsActive ? a.started_at : a.ended_at;
|
||||
const bTime = bIsActive ? b.started_at : b.ended_at;
|
||||
// Within same category, sort by most recent
|
||||
const aTime = aIsActive ? a.started_at : a.ended_at;
|
||||
const bTime = bIsActive ? b.started_at : b.ended_at;
|
||||
|
||||
if (!aTime || !bTime) return 0;
|
||||
return new Date(bTime).getTime() - new Date(aTime).getTime();
|
||||
});
|
||||
if (!aTime || !bTime) return 0;
|
||||
return new Date(bTime).getTime() - new Date(aTime).getTime();
|
||||
})
|
||||
.slice(0, EXECUTION_DISPLAY_LIMIT);
|
||||
}
|
||||
|
||||
const sortedExecutions = getSortedExecutions();
|
||||
@@ -53,7 +55,7 @@ export function NotificationDropdown({
|
||||
<div>
|
||||
{/* Header */}
|
||||
<div className="sticky top-0 z-10 px-4 pb-1 pt-4">
|
||||
<Text variant="body-medium" className="font-semibold text-gray-900">
|
||||
<Text variant="large-medium" className="font-semibold text-black">
|
||||
Agent Activity
|
||||
</Text>
|
||||
</div>
|
||||
@@ -63,11 +65,7 @@ export function NotificationDropdown({
|
||||
{sortedExecutions.length > 0 ? (
|
||||
<div className="p-2">
|
||||
{sortedExecutions.map((execution) => (
|
||||
<NotificationItem
|
||||
key={execution.id}
|
||||
execution={execution}
|
||||
type={execution.type}
|
||||
/>
|
||||
<NotificationItem key={execution.id} execution={execution} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -7,57 +7,75 @@ import {
|
||||
CircleNotchIcon,
|
||||
Clock,
|
||||
WarningOctagonIcon,
|
||||
StopCircle,
|
||||
CircleDashed,
|
||||
} from "@phosphor-icons/react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import type { AgentExecutionWithInfo } from "../helpers";
|
||||
import {
|
||||
formatTimeAgo,
|
||||
getExecutionDuration,
|
||||
getStatusColorClass,
|
||||
} from "../helpers";
|
||||
import { formatTimeAgo, getExecutionDuration } from "../helpers";
|
||||
|
||||
interface NotificationItemProps {
|
||||
execution: AgentExecutionWithInfo;
|
||||
type: "running" | "completed" | "failed";
|
||||
}
|
||||
|
||||
export function NotificationItem({ execution, type }: NotificationItemProps) {
|
||||
export function NotificationItem({ execution }: NotificationItemProps) {
|
||||
const router = useRouter();
|
||||
|
||||
function getStatusIcon() {
|
||||
switch (type) {
|
||||
case "running":
|
||||
return execution.status === AgentExecutionStatus.QUEUED ? (
|
||||
<Clock size={16} className="text-purple-500" />
|
||||
) : (
|
||||
switch (execution.status) {
|
||||
case AgentExecutionStatus.QUEUED:
|
||||
return <Clock size={18} className="text-purple-500" />;
|
||||
case AgentExecutionStatus.RUNNING:
|
||||
return (
|
||||
<CircleNotchIcon
|
||||
size={16}
|
||||
size={18}
|
||||
className="animate-spin text-purple-500"
|
||||
weight="bold"
|
||||
/>
|
||||
);
|
||||
case "completed":
|
||||
case AgentExecutionStatus.COMPLETED:
|
||||
return (
|
||||
<CheckCircle size={16} weight="fill" className="text-purple-500" />
|
||||
<CheckCircle size={18} weight="fill" className="text-purple-500" />
|
||||
);
|
||||
case "failed":
|
||||
return <WarningOctagonIcon size={16} className="text-purple-500" />;
|
||||
case AgentExecutionStatus.FAILED:
|
||||
return <WarningOctagonIcon size={18} className="text-purple-500" />;
|
||||
case AgentExecutionStatus.TERMINATED:
|
||||
return (
|
||||
<StopCircle size={18} className="text-purple-500" weight="fill" />
|
||||
);
|
||||
case AgentExecutionStatus.INCOMPLETE:
|
||||
return <CircleDashed size={18} className="text-purple-500" />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getTimeDisplay() {
|
||||
if (type === "running") {
|
||||
const isActiveStatus =
|
||||
execution.status === AgentExecutionStatus.RUNNING ||
|
||||
execution.status === AgentExecutionStatus.QUEUED;
|
||||
|
||||
if (isActiveStatus) {
|
||||
const timeAgo = formatTimeAgo(execution.started_at.toString());
|
||||
return `Started ${timeAgo}, ${getExecutionDuration(execution)} running`;
|
||||
const statusText =
|
||||
execution.status === AgentExecutionStatus.QUEUED ? "queued" : "running";
|
||||
return `Started ${timeAgo}, ${getExecutionDuration(execution)} ${statusText}`;
|
||||
}
|
||||
|
||||
if (execution.ended_at) {
|
||||
const timeAgo = formatTimeAgo(execution.ended_at.toString());
|
||||
return type === "completed"
|
||||
? `Completed ${timeAgo}`
|
||||
: `Failed ${timeAgo}`;
|
||||
switch (execution.status) {
|
||||
case AgentExecutionStatus.COMPLETED:
|
||||
return `Completed ${timeAgo}`;
|
||||
case AgentExecutionStatus.FAILED:
|
||||
return `Failed ${timeAgo}`;
|
||||
case AgentExecutionStatus.TERMINATED:
|
||||
return `Stopped ${timeAgo}`;
|
||||
case AgentExecutionStatus.INCOMPLETE:
|
||||
return `Incomplete ${timeAgo}`;
|
||||
default:
|
||||
return `Ended ${timeAgo}`;
|
||||
}
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
@@ -81,15 +99,9 @@ export function NotificationItem({ execution, type }: NotificationItemProps) {
|
||||
</div>
|
||||
|
||||
{/* Agent Message - Indented */}
|
||||
<div className="ml-7">
|
||||
{execution.agent_description ? (
|
||||
<Text variant="body" className={`${getStatusColorClass(execution)}`}>
|
||||
{execution.agent_description}
|
||||
</Text>
|
||||
) : null}
|
||||
|
||||
<div className="ml-7 pt-1">
|
||||
{/* Time - Indented */}
|
||||
<Text variant="small" className="pt-2 !text-zinc-500">
|
||||
<Text variant="small" className="!text-zinc-500">
|
||||
{getTimeDisplay()}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { ReactNode } from "react";
|
||||
|
||||
interface NotificationSectionProps {
|
||||
title: string;
|
||||
count: number;
|
||||
colorClass: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function NotificationSection({
|
||||
title,
|
||||
count,
|
||||
colorClass,
|
||||
children,
|
||||
}: NotificationSectionProps) {
|
||||
return (
|
||||
<div className="border-b border-gray-100 p-4 dark:border-gray-700">
|
||||
<h4 className={`mb-2 text-sm font-medium ${colorClass}`}>
|
||||
{title} ({count})
|
||||
</h4>
|
||||
<div className="space-y-2">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -3,17 +3,30 @@ import { GraphExecutionMeta as GeneratedGraphExecutionMeta } from "@/app/api/__g
|
||||
import { MyAgent } from "@/app/api/__generated__/models/myAgent";
|
||||
import type { GraphExecution } from "@/lib/autogpt-server-api/types";
|
||||
|
||||
// Time constants
|
||||
const MILLISECONDS_PER_SECOND = 1000;
|
||||
const SECONDS_PER_MINUTE = 60;
|
||||
const MINUTES_PER_HOUR = 60;
|
||||
const HOURS_PER_DAY = 24;
|
||||
const MILLISECONDS_PER_MINUTE = SECONDS_PER_MINUTE * MILLISECONDS_PER_SECOND;
|
||||
const MILLISECONDS_PER_HOUR = MINUTES_PER_HOUR * MILLISECONDS_PER_MINUTE;
|
||||
const MILLISECONDS_PER_DAY = HOURS_PER_DAY * MILLISECONDS_PER_HOUR;
|
||||
|
||||
// Display constants
|
||||
export const EXECUTION_DISPLAY_LIMIT = 6;
|
||||
const SHORT_DURATION_THRESHOLD_SECONDS = 5;
|
||||
|
||||
export function formatTimeAgo(dateStr: string): string {
|
||||
const date = new Date(dateStr);
|
||||
const now = new Date();
|
||||
const diffMs = now.getTime() - date.getTime();
|
||||
const diffMins = Math.floor(diffMs / 60000);
|
||||
const diffMins = Math.floor(diffMs / MILLISECONDS_PER_MINUTE);
|
||||
|
||||
if (diffMins < 1) return "Just now";
|
||||
if (diffMins < 60) return `${diffMins}m ago`;
|
||||
const diffHours = Math.floor(diffMins / 60);
|
||||
if (diffHours < 24) return `${diffHours}h ago`;
|
||||
const diffDays = Math.floor(diffHours / 24);
|
||||
if (diffMins < SECONDS_PER_MINUTE) return `${diffMins}m ago`;
|
||||
const diffHours = Math.floor(diffMins / MINUTES_PER_HOUR);
|
||||
if (diffHours < HOURS_PER_DAY) return `${diffHours}h ago`;
|
||||
const diffDays = Math.floor(diffHours / HOURS_PER_DAY);
|
||||
return `${diffDays}d ago`;
|
||||
}
|
||||
|
||||
@@ -69,14 +82,30 @@ export function getExecutionDuration(
|
||||
|
||||
const start = new Date(execution.started_at);
|
||||
const end = execution.ended_at ? new Date(execution.ended_at) : new Date();
|
||||
const durationMs = end.getTime() - start.getTime();
|
||||
const durationSec = Math.floor(durationMs / 1000);
|
||||
|
||||
if (durationSec < 60) return `${durationSec}s`;
|
||||
const durationMin = Math.floor(durationSec / 60);
|
||||
if (durationMin < 60) return `${durationMin}m ${durationSec % 60}s`;
|
||||
const durationHr = Math.floor(durationMin / 60);
|
||||
return `${durationHr}h ${durationMin % 60}m`;
|
||||
// Check if dates are valid
|
||||
if (isNaN(start.getTime()) || isNaN(end.getTime())) {
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
const durationMs = end.getTime() - start.getTime();
|
||||
|
||||
// Handle negative durations (shouldn't happen but just in case)
|
||||
if (durationMs < 0) return "Unknown";
|
||||
|
||||
const durationSec = Math.floor(durationMs / MILLISECONDS_PER_SECOND);
|
||||
|
||||
// For short durations (< 5 seconds), show "a few seconds"
|
||||
if (durationSec < SHORT_DURATION_THRESHOLD_SECONDS) {
|
||||
return "a few seconds";
|
||||
}
|
||||
|
||||
if (durationSec < SECONDS_PER_MINUTE) return `${durationSec}s`;
|
||||
const durationMin = Math.floor(durationSec / SECONDS_PER_MINUTE);
|
||||
if (durationMin < MINUTES_PER_HOUR)
|
||||
return `${durationMin}m ${durationSec % SECONDS_PER_MINUTE}s`;
|
||||
const durationHr = Math.floor(durationMin / MINUTES_PER_HOUR);
|
||||
return `${durationHr}h ${durationMin % MINUTES_PER_HOUR}m`;
|
||||
}
|
||||
|
||||
export function shouldShowNotificationBadge(totalCount: number): boolean {
|
||||
@@ -84,7 +113,7 @@ export function shouldShowNotificationBadge(totalCount: number): boolean {
|
||||
}
|
||||
|
||||
export function formatNotificationCount(count: number): string {
|
||||
if (count > 99) return "99+";
|
||||
if (count > 99) return "+99";
|
||||
return count.toString();
|
||||
}
|
||||
|
||||
@@ -213,7 +242,7 @@ export function categorizeExecutions(
|
||||
{ name: string; description: string; library_agent_id?: string }
|
||||
>,
|
||||
): NotificationState {
|
||||
const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
||||
const twentyFourHoursAgo = new Date(Date.now() - MILLISECONDS_PER_DAY);
|
||||
|
||||
const enrichedExecutions = executions.map((execution) =>
|
||||
enrichExecutionWithAgentInfo(execution, agentInfoMap),
|
||||
@@ -221,15 +250,15 @@ export function categorizeExecutions(
|
||||
|
||||
const activeExecutions = enrichedExecutions
|
||||
.filter(isActiveExecution)
|
||||
.slice(0, 10);
|
||||
.slice(0, EXECUTION_DISPLAY_LIMIT);
|
||||
|
||||
const recentCompletions = enrichedExecutions
|
||||
.filter((execution) => isRecentCompletion(execution, twentyFourHoursAgo))
|
||||
.slice(0, 10);
|
||||
.slice(0, EXECUTION_DISPLAY_LIMIT);
|
||||
|
||||
const recentFailures = enrichedExecutions
|
||||
.filter((execution) => isRecentFailure(execution, twentyFourHoursAgo))
|
||||
.slice(0, 10);
|
||||
.slice(0, EXECUTION_DISPLAY_LIMIT);
|
||||
|
||||
return {
|
||||
activeExecutions,
|
||||
@@ -262,23 +291,23 @@ export function addExecutionToCategory(
|
||||
state: NotificationState,
|
||||
execution: AgentExecutionWithInfo,
|
||||
): NotificationState {
|
||||
const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
||||
const twentyFourHoursAgo = new Date(Date.now() - MILLISECONDS_PER_DAY);
|
||||
const newState = { ...state };
|
||||
|
||||
if (isActiveExecution(execution)) {
|
||||
newState.activeExecutions = [execution, ...newState.activeExecutions].slice(
|
||||
0,
|
||||
10,
|
||||
EXECUTION_DISPLAY_LIMIT,
|
||||
);
|
||||
} else if (isRecentCompletion(execution, twentyFourHoursAgo)) {
|
||||
newState.recentCompletions = [
|
||||
execution,
|
||||
...newState.recentCompletions,
|
||||
].slice(0, 10);
|
||||
].slice(0, EXECUTION_DISPLAY_LIMIT);
|
||||
} else if (isRecentFailure(execution, twentyFourHoursAgo)) {
|
||||
newState.recentFailures = [execution, ...newState.recentFailures].slice(
|
||||
0,
|
||||
10,
|
||||
EXECUTION_DISPLAY_LIMIT,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user