feat(frontend/library): wire Agent Briefing to real execution data with live WebSocket updates

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lluis Agusti
2026-04-08 20:28:53 +07:00
parent 7b18949abb
commit cf7ad5abbd
3 changed files with 103 additions and 22 deletions

View File

@@ -292,4 +292,4 @@ function filterAgentsByStatus<T extends { id: string }>(
if (statusFilter === "healthy") return info.health === "good";
return info.status === statusFilter;
});
}
}

View File

@@ -1,25 +1,104 @@
"use client";
import { useState } from "react";
import {
getGetV1ListAllExecutionsQueryKey,
useGetV1ListAllExecutions,
} from "@/app/api/__generated__/endpoints/graphs/graphs";
import { AgentExecutionStatus } from "@/app/api/__generated__/models/agentExecutionStatus";
import type { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
import { okData } from "@/app/api/helpers";
import { useExecutionEvents } from "@/hooks/useExecutionEvents";
import { useQueryClient } from "@tanstack/react-query";
import { useCallback, useMemo } from "react";
import type { FleetSummary } from "../types";
/**
* Returns fleet-wide summary counts for the Agent Briefing Panel.
*
* TODO: Replace with a real `GET /agents/summary` API call once available.
* For now, returns deterministic mock data so the UI renders correctly.
*/
export function useLibraryFleetSummary(): FleetSummary {
// NOTE: useState initializer runs once on mount; the hardcoded mock values
// will not update if the component re-renders. Replace with a real API call
// once the backend endpoint is available.
const [summary] = useState<FleetSummary>(() => ({
running: 3,
error: 2,
listening: 4,
scheduled: 5,
idle: 8,
monthlySpend: 127.45,
}));
return summary;
const SEVENTY_TWO_HOURS_MS = 72 * 60 * 60 * 1000;
function isActiveExecution(status: string): boolean {
return (
status === AgentExecutionStatus.RUNNING ||
status === AgentExecutionStatus.QUEUED ||
status === AgentExecutionStatus.REVIEW
);
}
function isRecentFailure(
status: string,
endedAt?: string | Date | null,
): boolean {
if (
status !== AgentExecutionStatus.FAILED &&
status !== AgentExecutionStatus.TERMINATED
) {
return false;
}
if (!endedAt) return false;
const ts =
endedAt instanceof Date ? endedAt.getTime() : new Date(endedAt).getTime();
return Date.now() - ts < SEVENTY_TWO_HOURS_MS;
}
export function useLibraryFleetSummary(
agents: LibraryAgent[],
): FleetSummary | undefined {
const queryClient = useQueryClient();
const { data: executions, isSuccess } = useGetV1ListAllExecutions({
query: { select: okData },
});
const graphIDs = useMemo(() => agents.map((a) => a.graph_id), [agents]);
const handleExecutionUpdate = useCallback(() => {
queryClient.invalidateQueries({
queryKey: getGetV1ListAllExecutionsQueryKey(),
});
}, [queryClient]);
useExecutionEvents({
graphIds: graphIDs.length > 0 ? graphIDs : undefined,
enabled: graphIDs.length > 0,
onExecutionUpdate: handleExecutionUpdate,
});
return useMemo(() => {
if (!isSuccess || !executions) return undefined;
const agentsWithActiveExecution = new Set<string>();
const agentsWithRecentFailure = new Set<string>();
for (const exec of executions) {
if (isActiveExecution(exec.status)) {
agentsWithActiveExecution.add(exec.graph_id);
}
if (isRecentFailure(exec.status, exec.ended_at)) {
agentsWithRecentFailure.add(exec.graph_id);
}
}
const summary: FleetSummary = {
running: 0,
error: 0,
listening: 0,
scheduled: 0,
idle: 0,
monthlySpend: 0,
};
for (const agent of agents) {
if (agentsWithActiveExecution.has(agent.graph_id)) {
summary.running += 1;
} else if (agentsWithRecentFailure.has(agent.graph_id)) {
summary.error += 1;
} else if (agent.has_external_trigger) {
summary.listening += 1;
} else if (agent.recommended_schedule_cron) {
summary.scheduled += 1;
} else {
summary.idle += 1;
}
}
return summary;
}, [agents, executions, isSuccess]);
}

View File

@@ -9,6 +9,7 @@ import { FavoriteAnimationProvider } from "./context/FavoriteAnimationContext";
import type { LibraryTab, AgentStatusFilter } from "./types";
import { useLibraryFleetSummary } from "./hooks/useLibraryFleetSummary";
import { Flag, useGetFlag } from "@/services/feature-flags/use-get-flag";
import { useLibraryAgents } from "@/hooks/useLibraryAgents/useLibraryAgents";
const LIBRARY_TABS: LibraryTab[] = [
{ id: "all", title: "All", icon: ListIcon },
@@ -22,7 +23,8 @@ export default function LibraryPage() {
const [activeTab, setActiveTab] = useState(LIBRARY_TABS[0].id);
const [statusFilter, setStatusFilter] = useState<AgentStatusFilter>("all");
const isAgentBriefingEnabled = useGetFlag(Flag.AGENT_BRIEFING);
const fleetSummary = useLibraryFleetSummary();
const { agents } = useLibraryAgents();
const fleetSummary = useLibraryFleetSummary(agents);
useEffect(() => {
document.title = "Library AutoGPT Platform";