From 978be535d3962a7db9a2c6172d1e3830ce350bca Mon Sep 17 00:00:00 2001 From: Erwin Date: Fri, 27 Mar 2026 20:31:23 +0000 Subject: [PATCH] Add port 3000 mapping to docker-compose --- docker-compose.yml | 2 + src/components/MissionControlDashboard.tsx | 2 +- src/hooks/useOpenClaw.ts | 50 ++++++---- src/lib/openclaw-api.ts | 105 +++++++++++++-------- 4 files changed, 99 insertions(+), 60 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index aeb9b66..402bf62 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,6 +5,8 @@ services: dockerfile: Dockerfile container_name: mission-control-ui restart: unless-stopped + ports: + - "3000:80" labels: - "traefik.enable=true" - "traefik.http.routers.mission-control-ui.rule=Host(`mission-control.danielarroyo.cl`)" diff --git a/src/components/MissionControlDashboard.tsx b/src/components/MissionControlDashboard.tsx index e126ac9..b442097 100644 --- a/src/components/MissionControlDashboard.tsx +++ b/src/components/MissionControlDashboard.tsx @@ -60,7 +60,7 @@ export function MissionControlDashboard() { {/* Status Bar */} void; + connected: boolean; + refetch: () => void; } -const POLLING_INTERVAL = 30000; - -export function useOpenClaw(): UseOpenClawReturn { +export function useOpenClaw(options: UseOpenClawOptions = {}): UseOpenClawReturn { + const { pollInterval = 30000 } = options; + const [agents, setAgents] = useState([]); + const [connections] = useState([]); const [events, setEvents] = useState([]); const [health, setHealth] = useState<{ status: string; uptime: number; sessions: number } | null>(null); - const [isLoading, setIsLoading] = useState(true); + const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - const [lastUpdate, setLastUpdate] = useState(null); + const [lastUpdated, setLastUpdated] = useState(null); + const [connected, setConnected] = useState(false); const intervalRef = useRef(null); const isMountedRef = useRef(true); @@ -37,19 +47,21 @@ export function useOpenClaw(): UseOpenClawReturn { setAgents(agentsData); setEvents(eventsData); setHealth(healthData); + setConnected(true); setError(null); - setLastUpdate(new Date()); - setIsLoading(false); + setLastUpdated(new Date()); + setLoading(false); } } catch (err) { if (isMountedRef.current) { setError(err instanceof Error ? err.message : 'Failed to connect to gateway'); - setIsLoading(false); + setConnected(false); + setLoading(false); } } }, []); - const refresh = useCallback(() => { + const refetch = useCallback(() => { fetchData(); }, [fetchData]); @@ -57,7 +69,7 @@ export function useOpenClaw(): UseOpenClawReturn { isMountedRef.current = true; fetchData(); - intervalRef.current = window.setInterval(fetchData, POLLING_INTERVAL); + intervalRef.current = window.setInterval(fetchData, pollInterval); return () => { isMountedRef.current = false; @@ -66,15 +78,17 @@ export function useOpenClaw(): UseOpenClawReturn { intervalRef.current = null; } }; - }, [fetchData]); + }, [fetchData, pollInterval]); return { agents, + connections, events, health, - isLoading, + lastUpdated, + loading, error, - lastUpdate, - refresh, + connected, + refetch, }; } diff --git a/src/lib/openclaw-api.ts b/src/lib/openclaw-api.ts index e259575..d54423f 100644 --- a/src/lib/openclaw-api.ts +++ b/src/lib/openclaw-api.ts @@ -1,4 +1,4 @@ -import type { Agent, Connection, TimelineEvent } from '../types/agent'; +import type { Agent, TimelineEvent, AgentRole, AgentStatus } from '../types/agent'; const GATEWAY_URL = import.meta.env.VITE_OPENCLAW_GATEWAY_URL || 'http://127.0.0.1:18789'; const GATEWAY_TOKEN = import.meta.env.VITE_OPENCLAW_GATEWAY_TOKEN || ''; @@ -19,6 +19,35 @@ interface GatewayAgentsResponse { }>; } +const roleMap: Record = { + orchestrator: 'orchestrator', + architect: 'architect', + coder: 'developer', + developer: 'developer', + designer: 'designer', + reviewer: 'reviewer', + assistant: 'assistant', + docs: 'assistant', +}; + +const statusMap: Record = { + active: 'active', + busy: 'thinking', + thinking: 'thinking', + blocked: 'blocked', + idle: 'idle', + completed: 'completed', +}; + +const agentPositions = [ + { id: 'erwin', x: 10, y: 38 }, + { id: 'bulma', x: 36, y: 12 }, + { id: 'rocket', x: 36, y: 64 }, + { id: 'sherlock', x: 66, y: 24 }, + { id: 'hiro', x: 66, y: 64 }, + { id: 'claudia', x: 66, y: 50 }, +]; + export class OpenClawGatewayClient { private baseUrl: string; private token: string; @@ -53,16 +82,25 @@ export class OpenClawGatewayClient { async fetchAgents(): Promise { try { const data = await this.fetch('/api/agents'); - return data.agents.map((agent, index) => ({ - id: agent.id, - name: agent.name, - role: this.getRoleFromAgentId(agent.id), - status: agent.status === 'active' ? 'active' : agent.status === 'busy' ? 'busy' : 'idle', - queue: 0, - icon: this.getIconFromIndex(index), - x: this.getAgentX(index), - y: this.getAgentY(index), - })); + return data.agents.map((agent) => { + const pos = agentPositions.find(p => p.id === agent.id) || { x: 50, y: 50 }; + const role = roleMap[agent.id] || 'assistant'; + const status = statusMap[agent.status] || 'idle'; + + return { + id: agent.id, + name: agent.name, + role, + status, + position: { x: pos.x, y: pos.y }, + connections: this.getDefaultConnections(agent.id), + stats: { + tasksCompleted: 0, + tasksInProgress: 0, + tasksBlocked: 0, + }, + }; + }); } catch { return this.getDefaultAgents(); } @@ -77,41 +115,26 @@ export class OpenClawGatewayClient { } } - private getRoleFromAgentId(id: string): string { - const roles: Record = { - orchestrator: 'Coordina flujo y delegación', - architect: 'Define solución y tareas', - coder: 'Implementa cambios', - reviewer: 'Revisa calidad y riesgos', - docs: 'Documenta decisiones y cambios', - assistant: 'Asistente general', + private getDefaultConnections(agentId: string): string[] { + const connectionsMap: Record = { + erwin: ['bulma', 'rocket', 'sherlock', 'claudia'], + bulma: ['rocket', 'sherlock'], + rocket: ['sherlock', 'claudia'], + sherlock: ['claudia', 'erwin'], + hiro: ['erwin'], + claudia: ['erwin'], }; - return roles[id] || 'Agente'; - } - - private getIconFromIndex(index: number) { - const icons = ['Workflow', 'Layers3', 'Bot', 'ShieldCheck', 'FileText', 'Activity']; - return icons[index % icons.length]; - } - - private getAgentX(index: number): number { - const positions = [10, 36, 36, 66, 66, 66]; - return positions[index] || 50; - } - - private getAgentY(index: number): number { - const positions = [38, 12, 64, 24, 64, 50]; - return positions[index] || 50; + return connectionsMap[agentId] || []; } private getDefaultAgents(): Agent[] { return [ - { id: 'erwin', name: 'Erwin', role: 'Orquestador', status: 'active', queue: 0, icon: 'Workflow', x: 10, y: 38 }, - { id: 'bulma', name: 'Bulma', role: 'Arquitecto', status: 'active', queue: 0, icon: 'Layers3', x: 36, y: 12 }, - { id: 'rocket', name: 'Rocket', role: 'Desarrollador', status: 'busy', queue: 0, icon: 'Bot', x: 36, y: 64 }, - { id: 'sherlock', name: 'Sherlock', role: 'Revisor', status: 'idle', queue: 0, icon: 'ShieldCheck', x: 66, y: 24 }, - { id: 'hiro', name: 'Hiro', role: 'Diseñador', status: 'idle', queue: 0, icon: 'FileText', x: 66, y: 64 }, - { id: 'claudia', name: 'Claudia', role: 'Asistente', status: 'idle', queue: 0, icon: 'Activity', x: 66, y: 50 }, + { id: 'erwin', name: 'Erwin', role: 'orchestrator', status: 'active', position: { x: 10, y: 38 }, connections: ['bulma', 'rocket'], stats: { tasksCompleted: 0, tasksInProgress: 0, tasksBlocked: 0 } }, + { id: 'bulma', name: 'Bulma', role: 'architect', status: 'active', position: { x: 36, y: 12 }, connections: ['rocket'], stats: { tasksCompleted: 0, tasksInProgress: 0, tasksBlocked: 0 } }, + { id: 'rocket', name: 'Rocket', role: 'developer', status: 'thinking', position: { x: 36, y: 64 }, connections: ['sherlock'], stats: { tasksCompleted: 0, tasksInProgress: 0, tasksBlocked: 0 } }, + { id: 'sherlock', name: 'Sherlock', role: 'reviewer', status: 'idle', position: { x: 66, y: 24 }, connections: ['claudia'], stats: { tasksCompleted: 0, tasksInProgress: 0, tasksBlocked: 0 } }, + { id: 'hiro', name: 'Hiro', role: 'designer', status: 'idle', position: { x: 66, y: 64 }, connections: [], stats: { tasksCompleted: 0, tasksInProgress: 0, tasksBlocked: 0 } }, + { id: 'claudia', name: 'Claudia', role: 'assistant', status: 'idle', position: { x: 66, y: 50 }, connections: ['erwin'], stats: { tasksCompleted: 0, tasksInProgress: 0, tasksBlocked: 0 } }, ]; } }