/** * Active Projects & Agent Chains Widget * Shows active projects and running agent sessions */ import { useState, useEffect } from "react"; import { FolderOpen, Bot, Activity, Clock, AlertCircle, CheckCircle2 } from "lucide-react"; import type { WidgetProps } from "@mosaic/shared"; import { apiPost } from "@/lib/api/client"; interface ActiveProject { id: string; name: string; status: string; lastActivity: string; taskCount: number; eventCount: number; color: string | null; } interface AgentSession { id: string; sessionKey: string; label: string | null; channel: string | null; agentName: string | null; agentStatus: string | null; status: "active" | "ended"; startedAt: string; lastMessageAt: string | null; runtimeMs: number; messageCount: number; contextSummary: string | null; } export function ActiveProjectsWidget({ id: _id, config: _config }: WidgetProps): React.JSX.Element { const [projects, setProjects] = useState([]); const [agentSessions, setAgentSessions] = useState([]); const [isLoadingProjects, setIsLoadingProjects] = useState(true); const [isLoadingAgents, setIsLoadingAgents] = useState(true); const [expandedSession, setExpandedSession] = useState(null); // Fetch active projects useEffect(() => { const fetchProjects = async (): Promise => { try { // Use API client to ensure CSRF token is included const data = await apiPost("/api/widgets/data/active-projects"); setProjects(data); } catch (error) { console.error("Failed to fetch active projects:", error); } finally { setIsLoadingProjects(false); } }; void fetchProjects(); const interval = setInterval(() => { void fetchProjects(); }, 30000); // Refresh every 30s return (): void => { clearInterval(interval); }; }, []); // Fetch agent chains useEffect(() => { const fetchAgentSessions = async (): Promise => { try { // Use API client to ensure CSRF token is included const data = await apiPost("/api/widgets/data/agent-chains"); setAgentSessions(data); } catch (error) { console.error("Failed to fetch agent sessions:", error); } finally { setIsLoadingAgents(false); } }; void fetchAgentSessions(); const interval = setInterval(() => { void fetchAgentSessions(); }, 10000); // Refresh every 10s return (): void => { clearInterval(interval); }; }, []); const getStatusIcon = (status: string): React.JSX.Element => { const statusUpper = status.toUpperCase(); switch (statusUpper) { case "WORKING": return ; case "IDLE": return ; case "WAITING": return ; case "ERROR": return ; case "TERMINATED": return ; default: return ; } }; const formatRuntime = (runtimeMs: number): string => { const seconds = Math.floor(runtimeMs / 1000); if (seconds < 60) return `${String(seconds)}s`; const minutes = Math.floor(seconds / 60); if (minutes < 60) return `${String(minutes)}m`; const hours = Math.floor(minutes / 60); return `${String(hours)}h ${String(minutes % 60)}m`; }; const formatLastActivity = (timestamp: string): string => { const now = new Date(); const last = new Date(timestamp); const diffMs = now.getTime() - last.getTime(); if (diffMs < 60000) return "Just now"; if (diffMs < 3600000) return `${String(Math.floor(diffMs / 60000))}m ago`; if (diffMs < 86400000) return `${String(Math.floor(diffMs / 3600000))}h ago`; return `${String(Math.floor(diffMs / 86400000))}d ago`; }; return (
{/* Active Projects Panel */}

Active Projects

{!isLoadingProjects && ({projects.length})}
{isLoadingProjects ? (
Loading projects...
) : projects.length === 0 ? (
No active projects
) : ( projects.map((project) => (
{project.color && (
)} {project.name}
{formatLastActivity(project.lastActivity)}
{project.taskCount} tasks {project.eventCount} events 🟢 {project.status}
)) )}
{/* Agent Chains Panel */}

Agent Chains

{!isLoadingAgents && ( ({agentSessions.length}) )}
{isLoadingAgents ? (
Loading agents...
) : agentSessions.length === 0 ? (
No running agents
) : ( agentSessions.map((session) => (
{ setExpandedSession(expandedSession === session.id ? null : session.id); }} >
{session.agentStatus && getStatusIcon(session.agentStatus)} {session.label ?? session.agentName ?? "Unnamed Agent"}
{formatRuntime(session.runtimeMs)}
{session.messageCount} msgs {session.channel && {session.channel}} {session.status === "active" ? "🔵 Running" : "⚪ Ended"}
{/* Expandable details */} {expandedSession === session.id && session.contextSummary && (
{session.contextSummary}
)}
)) )}
); }