diff --git a/.gitignore b/.gitignore index e04c4f8f..68bfa28f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ # Python venv/ .venv/ +.venv_local/ +.saropa/ __pycache__/ *.py[cod] *.pyo diff --git a/frontend/src/app/agents/page.tsx b/frontend/src/app/agents/page.tsx new file mode 100644 index 00000000..0603020d --- /dev/null +++ b/frontend/src/app/agents/page.tsx @@ -0,0 +1,889 @@ +"use client"; + +import { useState, useEffect, useRef } from "react"; +import { + Brain, + Search, + Code, + CheckSquare, + Shield, + Zap, + Pause, + Play, + RotateCcw, + MessageSquare, + X, + Activity, + Wifi, + WifiOff, +} from "lucide-react"; +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Badge } from "@/components/ui/badge"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Separator } from "@/components/ui/separator"; +import { Slider } from "@/components/ui/slider"; +import { Label } from "@/components/ui/label"; + +interface AgentNode { + id: string; + role: string; + name: string; + icon: React.ReactNode; + color: string; + status: "idle" | "active" | "completed" | "error"; + progress: number; + lastThought: string; + thoughts: string[]; +} + +interface AgentEvent { + type: string; + payload: any; + timestamp: string; +} + +interface WebSocketMessage { + event_type: string; + payload: any; + timestamp: string; + correlation_id?: string; +} + +const AGENT_ROLES: AgentNode[] = [ + { + id: "planner", + role: "planner", + name: "Planner", + icon: , + color: "bg-blue-500", + status: "idle", + progress: 0, + lastThought: "", + thoughts: [], + }, + { + id: "researcher", + role: "researcher", + name: "Researcher", + icon: , + color: "bg-green-500", + status: "idle", + progress: 0, + lastThought: "", + thoughts: [], + }, + { + id: "coder", + role: "coder", + name: "Coder", + icon: , + color: "bg-purple-500", + status: "idle", + progress: 0, + lastThought: "", + thoughts: [], + }, + { + id: "evaluator", + role: "evaluator", + name: "Evaluator", + icon: , + color: "bg-orange-500", + status: "idle", + progress: 0, + lastThought: "", + thoughts: [], + }, + { + id: "safety_gate", + role: "safety_gate", + name: "Safety Gate", + icon: , + color: "bg-red-500", + status: "idle", + progress: 0, + lastThought: "", + thoughts: [], + }, +]; + +const AGENT_CONNECTIONS = [ + { from: "planner", to: "researcher" }, + { from: "researcher", to: "coder" }, + { from: "coder", to: "evaluator" }, + { from: "evaluator", to: "safety_gate" }, +]; + +export default function AgentsPage() { + const [ws, setWs] = useState(null); + const canvasRef = useRef(null); + + const [connected, setConnected] = useState(false); + const [agents, setAgents] = useState(AGENT_ROLES); + const [objective, setObjective] = useState(""); + const [isRunning, setIsRunning] = useState(false); + const [events, setEvents] = useState([]); + const [fineTuneEvents, setFineTuneEvents] = useState([]); + const [selectedAgent, setSelectedAgent] = useState(null); + const [showFineTune, setShowFineTune] = useState(false); + const [modelName, setModelName] = useState("Qwen2.5-0.5B"); + const [adapterPath, setAdapterPath] = useState(""); + const [autoFineTune, setAutoFineTune] = useState(true); + const eventsEndRef = useRef(null); + const fineTuneEndRef = useRef(null); + + // Draw agent graph on canvas + useEffect(() => { + if (canvasRef.current) { + drawAgentGraph(canvasRef.current, agents); + } + }, [agents]); + + useEffect(() => { + const websocket = new WebSocket("ws://127.0.0.1:8765"); + + websocket.onopen = () => { + setConnected(true); + setWs(websocket); // set the websocket state when the connection opens + console.log("Connected to agent bridge"); + + // Subscribe to all event types + websocket.send(JSON.stringify({ + subscribe: [ + "agent_thought", + "agent_step", + "fine_tune_epoch", + "fine_tune_loss", + "swarm_status", + "objective_started", + "objective_completed", + "objective_failed", + "checkpoint_created", + "consensus_request", + "consensus_result", + "error" + ] + })); + }; + + websocket.onmessage = (event) => { + try { + const message: WebSocketMessage = JSON.parse(event.data); + handleMessage(message); + } catch (e) { + console.error("Failed to parse message:", e); + } + }; + + websocket.onclose = () => { + setConnected(false); + setWs(null); // clear the websocket state when the connection closes + console.log("Disconnected from agent bridge"); + }; + + websocket.onerror = (error) => { + console.error("WebSocket error:", error); + }; + + return () => { + websocket.close(); + }; + }, []); // Empty deps because we only want to run once + + const handleMessage = (message: WebSocketMessage) => { + const { event_type, payload } = message; + + switch (event_type) { + case "agent_thought": + updateAgentThought(payload); + break; + case "agent_step": + updateAgentStep(payload); + break; + case "fine_tune_epoch": + addFineTuneEvent(payload); + break; + case "fine_tune_loss": + updateFineTuneLoss(payload); + break; + case "swarm_status": + // Handle swarm status updates + break; + case "objective_started": + setIsRunning(true); + resetAgents(); + addEvent({ type: "objective_started", payload, timestamp: new Date().toISOString() }); + break; + case "objective_completed": + setIsRunning(false); + addEvent({ type: "objective_completed", payload, timestamp: new Date().toISOString() }); + break; + case "objective_failed": + setIsRunning(false); + addEvent({ type: "objective_failed", payload, timestamp: new Date().toISOString() }); + break; + case "error": + addEvent({ type: "error", payload, timestamp: new Date().toISOString() }); + break; + default: + // Handle response messages + if (payload?.response) { + handleResponse(payload); + } + } + }; + + const updateAgentThought = (payload: any) => { + const { agent, thought, step } = payload; + setAgents(prev => prev.map(a => { + if (a.id === agent) { + const newThoughts = [...a.thoughts, thought]; + return { + ...a, + lastThought: thought, + thoughts: newThoughts.slice(-10), // Keep last 10 thoughts + status: "active" as const, + progress: step || a.progress, + }; + } + return a; + })); + addEvent({ type: "agent_thought", payload, timestamp: new Date().toISOString() }); + }; + + const updateAgentStep = (payload: any) => { + const { agent, status, progress } = payload; + setAgents(prev => prev.map(a => + a.id === agent ? { ...a, status, progress: progress || a.progress } : a + )); + }; + + const addFineTuneEvent = (payload: any) => { + setFineTuneEvents(prev => [...prev.slice(-50), { ...payload, timestamp: new Date().toISOString() }]); + }; + + const updateFineTuneLoss = (payload: any) => { + setFineTuneEvents(prev => prev.map(e => + e.epoch === payload.epoch ? { ...e, ...payload } : e + )); + }; + + const addEvent = (event: AgentEvent) => { + setEvents(prev => [...prev.slice(-100), event]); + // Auto-scroll + setTimeout(() => eventsEndRef.current?.scrollIntoView({ behavior: "smooth" }), 0); + }; + + const handleResponse = (payload: any) => { + // Handle command responses + console.log("Response:", payload); + }; + + const resetAgents = () => { + setAgents(AGENT_ROLES.map(a => ({ ...a, status: "idle" as const, progress: 0, lastThought: "", thoughts: [] }))); + setFineTuneEvents([]); + }; + + const sendCommand = (commandType: string, payload: any) => { + if (ws && ws.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify({ + command_type: commandType, + payload, + request_id: crypto.randomUUID(), + timestamp: new Date().toISOString(), + })); + } + }; + + const handleSubmitObjective = () => { + if (!objective.trim()) return; + sendCommand("submit_objective", { objective }); + }; + + const handleTriggerFineTune = () => { + if (!adapterPath.trim()) return; + sendCommand("trigger_finetune", { adapter_path: adapterPath }); + }; + + const handlePauseHarness = () => { + sendCommand("pause_harness", {}); + }; + + const handleResumeHarness = () => { + sendCommand("resume_harness", {}); + }; + + const handleSwitchModel = () => { + sendCommand("switch_model", { model: modelName }); + }; + + const getStatusColor = (status: string) => { + switch (status) { + case "active": return "bg-green-500 animate-pulse"; + case "completed": return "bg-blue-500"; + case "error": return "bg-red-500"; + default: return "bg-gray-400"; + } + }; + + const getAgentById = (id: string) => agents.find(a => a.id === id); + + return ( +
+ {/* Sidebar - Agent Graph */} +
+ + + + + + Agent Swarm + + + {connected ? : } + {connected ? "Connected" : "Disconnected"} + + + + +
+ {/* Agent Graph Canvas */} + +
+
+
+ + {/* Controls */} + +
+ Controls + + +
+ +