import { useState, useEffect, useCallback } from "react"; import type { JSX } from "react"; import { X, Plus, Play, Pause, Square, Activity, Heart, Trash2, RefreshCw, Bot } from "lucide-react"; import type { Agent, AgentCapability, AgentState } from "../api"; import { fetchAgents, createAgent, updateAgentState, deleteAgent } from "../api"; interface AgentListModalProps { isOpen: boolean; onClose: () => void; addToast: (message: string, type?: "success" | "error") => void; } const AGENT_ROLES: { value: AgentCapability; label: string; icon: string }[] = [ { value: "triage", label: "Triage", icon: "🔍" }, { value: "executor", label: "Executor", icon: "⚡" }, { value: "reviewer", label: "Reviewer", icon: "👁" }, { value: "merger", label: "Merger", icon: "🔀" }, { value: "scheduler", label: "Scheduler", icon: "⏰" }, { value: "custom", label: "Custom", icon: "🔧" }, ]; const STATE_COLORS: Record = { idle: { bg: "var(--state-idle-bg)", text: "var(--state-idle-text)", border: "var(--state-idle-border)" }, active: { bg: "var(--state-active-bg)", text: "var(--state-active-text)", border: "var(--state-active-border)" }, paused: { bg: "var(--state-paused-bg)", text: "var(--state-paused-text)", border: "var(--state-paused-border)" }, terminated: { bg: "var(--state-error-bg)", text: "var(--state-error-text)", border: "var(--state-error-border)" }, }; export function AgentListModal({ isOpen, onClose, addToast }: AgentListModalProps) { const [agents, setAgents] = useState([]); const [isLoading, setIsLoading] = useState(false); const [isCreating, setIsCreating] = useState(false); const [newAgentName, setNewAgentName] = useState(""); const [newAgentRole, setNewAgentRole] = useState("custom"); const [filterState, setFilterState] = useState("all"); const loadAgents = useCallback(async () => { setIsLoading(true); try { const filter = filterState !== "all" ? { state: filterState } : undefined; const data = await fetchAgents(filter); setAgents(data); } catch (err: any) { addToast(`Failed to load agents: ${err.message}`, "error"); } finally { setIsLoading(false); } }, [filterState, addToast]); useEffect(() => { if (isOpen) { void loadAgents(); } }, [isOpen, loadAgents]); const handleCreate = async () => { if (!newAgentName.trim()) return; try { await createAgent({ name: newAgentName.trim(), role: newAgentRole }); addToast(`Agent "${newAgentName}" created`, "success"); setNewAgentName(""); setIsCreating(false); void loadAgents(); } catch (err: any) { addToast(`Failed to create agent: ${err.message}`, "error"); } }; const handleStateChange = async (agentId: string, newState: AgentState) => { try { await updateAgentState(agentId, newState); addToast(`Agent state updated to ${newState}`, "success"); void loadAgents(); } catch (err: any) { addToast(`Failed to update state: ${err.message}`, "error"); } }; const handleDelete = async (agentId: string, agentName: string) => { if (!confirm(`Delete agent "${agentName}"? This cannot be undone.`)) return; try { await deleteAgent(agentId); addToast(`Agent "${agentName}" deleted`, "success"); void loadAgents(); } catch (err: any) { addToast(`Failed to delete agent: ${err.message}`, "error"); } }; const getRoleLabel = (role: AgentCapability) => AGENT_ROLES.find(r => r.value === role)?.label ?? role; const getRoleIcon = (role: AgentCapability) => AGENT_ROLES.find(r => r.value === role)?.icon ?? "🤖"; const getHealthStatus = (agent: Agent): { label: string; icon: JSX.Element; color: string } => { if (agent.state === "terminated") { return { label: "Terminated", icon: , color: "var(--state-error-text)" }; } if (agent.state === "paused") { return { label: "Paused", icon: , color: "var(--state-paused-text)" }; } if (!agent.lastHeartbeatAt) { return { label: agent.state === "active" ? "Starting..." : "Idle", icon: , color: "var(--text-secondary)" }; } const lastHeartbeat = new Date(agent.lastHeartbeatAt).getTime(); const elapsed = Date.now() - lastHeartbeat; const timeoutMs = 60000; // 60 second timeout if (elapsed > timeoutMs) { return { label: "Unresponsive", icon: , color: "var(--state-error-text)" }; } return { label: "Healthy", icon: , color: "var(--state-active-text)" }; }; if (!isOpen) return null; return (
e.target === e.currentTarget && onClose()}>

Agents

{/* Filter and Create Bar */}
{/* Create Form */} {isCreating && (
setNewAgentName(e.target.value)} onKeyDown={(e) => e.key === "Enter" && handleCreate()} className="input" autoFocus />
)} {/* Agent List */}
{agents.length === 0 ? (

No agents found

Create an agent to get started

) : ( agents.map(agent => { const health = getHealthStatus(agent); const stateStyle = STATE_COLORS[agent.state]; return (
{getRoleIcon(agent.role)}
{agent.name} {agent.id}
{agent.state} {health.icon} {health.label} {getRoleLabel(agent.role)}
{agent.taskId && (
Working on: {agent.taskId}
)} {agent.lastHeartbeatAt && (
Last heartbeat: {new Date(agent.lastHeartbeatAt).toLocaleString()}
)}
{agent.state === "idle" && ( )} {agent.state === "active" && ( <> )} {agent.state === "paused" && ( <> )} {agent.state === "terminated" && ( )}
); }) )}
); }