diff --git a/frontend/src/components/agent-monitor.tsx b/frontend/src/components/agent-monitor.tsx new file mode 100644 index 0000000..1d8b787 --- /dev/null +++ b/frontend/src/components/agent-monitor.tsx @@ -0,0 +1,215 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { useCoAgent } from "@copilotkit/react-core"; +import { AvailableAgents } from "@/lib/available-agents"; +import { Clock, Activity, CheckCircle, XCircle, Server } from "lucide-react"; + +type AgentHistoryItem = { + agentName: string; + action: string; + timestamp: number; + success: boolean; + details?: string; +}; + +type AgentState = { + running: boolean; + name: string; + nodeName: string; + lastActivity?: number; +}; + +export default function AgentMonitor() { + const [agentHistory, setAgentHistory] = useState([]); + const [agentStates, setAgentStates] = useState>({ + [AvailableAgents.TRAVEL_AGENT]: { + running: false, + name: "Travel Agent", + nodeName: "", + }, + [AvailableAgents.RESEARCH_AGENT]: { + running: false, + name: "Research Agent", + nodeName: "", + }, + [AvailableAgents.MCP_AGENT]: { + running: false, + name: "MCP Agent", + nodeName: "", + }, + }); + + // Monitor travel agent state + const { running: travelAgentRunning, name: travelAgentName, nodeName: travelAgentNodeName } = useCoAgent({ + name: AvailableAgents.TRAVEL_AGENT, + }); + + // Monitor AI research agent state + const { running: aiResearchAgentRunning, name: aiResearchAgentName, nodeName: aiResearchAgentNodeName } = useCoAgent({ + name: AvailableAgents.RESEARCH_AGENT, + }); + + // Monitor MCP agent state + const { running: mcpAgentRunning, name: mcpAgentName, nodeName: mcpAgentNodeName } = useCoAgent({ + name: AvailableAgents.MCP_AGENT, + }); + + // Update agent states when they change + useEffect(() => { + const newStates = { + [AvailableAgents.TRAVEL_AGENT]: { + running: travelAgentRunning, + name: travelAgentName, + nodeName: travelAgentNodeName || "", + lastActivity: travelAgentRunning ? Date.now() : agentStates[AvailableAgents.TRAVEL_AGENT].lastActivity, + }, + [AvailableAgents.RESEARCH_AGENT]: { + running: aiResearchAgentRunning, + name: aiResearchAgentName, + nodeName: aiResearchAgentNodeName || "", + lastActivity: aiResearchAgentRunning ? Date.now() : agentStates[AvailableAgents.RESEARCH_AGENT].lastActivity, + }, + [AvailableAgents.MCP_AGENT]: { + running: mcpAgentRunning, + name: mcpAgentName, + nodeName: mcpAgentNodeName || "", + lastActivity: mcpAgentRunning ? Date.now() : agentStates[AvailableAgents.MCP_AGENT].lastActivity, + }, + }; + + // Check for state changes and add to history + Object.entries(newStates).forEach(([agentKey, newState]) => { + const oldState = agentStates[agentKey]; + + if (newState.running !== oldState.running) { + const action = newState.running ? "Started" : "Stopped"; + setAgentHistory(prev => [ + { + agentName: newState.name, + action, + timestamp: Date.now(), + success: true, + details: newState.nodeName ? `Node: ${newState.nodeName}` : undefined, + }, + ...prev, + ]); + } else if (newState.running && newState.nodeName !== oldState.nodeName && newState.nodeName) { + setAgentHistory(prev => [ + { + agentName: newState.name, + action: "Changed Node", + timestamp: Date.now(), + success: true, + details: `Node: ${newState.nodeName}`, + }, + ...prev, + ]); + } + }); + + setAgentStates(newStates); + }, [travelAgentRunning, travelAgentName, travelAgentNodeName, aiResearchAgentRunning, aiResearchAgentName, aiResearchAgentNodeName, mcpAgentRunning, mcpAgentName, mcpAgentNodeName, agentStates]); + + // Format timestamp to readable time + const formatTime = (timestamp: number) => { + return new Date(timestamp).toLocaleTimeString(); + }; + + // Get agent status color + const getStatusColor = (running: boolean) => { + return running ? "text-green-500" : "text-gray-500"; + }; + + // Get agent status icon + const getStatusIcon = (running: boolean) => { + return running ? ( + + ) : ( + + ); + }; + + // Get history item icon + const getHistoryIcon = (success: boolean) => { + return success ? ( + + ) : ( + + ); + }; + + return ( +
+
+

+ + Agent Monitor +

+
+ + {/* Agent States */} +
+ {Object.entries(agentStates).map(([agentKey, state]) => ( +
+
+
+ {getStatusIcon(state.running)} + {state.name} + + {state.running ? "Running" : "Idle"} + +
+ {state.running && state.nodeName && ( + + Node: {state.nodeName} + + )} +
+ {state.lastActivity && ( +
+ + Last activity: {formatTime(state.lastActivity)} +
+ )} +
+ ))} +
+ + {/* Agent History */} +
+

+ + Activity History +

+
+ {agentHistory.length === 0 ? ( +
+ No activity yet +
+ ) : ( +
+ {agentHistory.slice(0, 20).map((item, index) => ( +
+
+ {getHistoryIcon(item.success)} +
+
+ {item.agentName} + {formatTime(item.timestamp)} +
+

{item.action}

+ {item.details && ( +

{item.details}

+ )} +
+
+
+ ))} +
+ )} +
+
+
+ ); +} diff --git a/frontend/src/components/canvas.tsx b/frontend/src/components/canvas.tsx index dc3eea2..7935108 100644 --- a/frontend/src/components/canvas.tsx +++ b/frontend/src/components/canvas.tsx @@ -8,6 +8,7 @@ import { CircleOff, Loader2, Settings } from "lucide-react"; import { Suspense, useState } from "react"; import { ChatWindow } from "./chat-window"; import { MCPConfigModal } from "./mcp-config-modal"; +import AgentMonitor from "./agent-monitor"; const getCurrentlyRunningAgent = ( state: Array<{ @@ -104,6 +105,7 @@ export default function Canvas() { )}
+