← back to Dw War Room

src/main.ts

238 lines

// ═══════════════════════════════════════════════
// DW War Room 3D — Main Entry (Enhanced)
// Three.js command center with full data wiring
// System health, governance, feed, agent click
// ═══════════════════════════════════════════════

import { WarRoom } from './scene/WarRoom';
import { HudOverlay } from './panels/HudOverlay';
import { DualWebSocketClient } from './data/WebSocketClient';
import * as Api from './data/ApiClient';

async function main() {
  const container = document.getElementById('war-room')!;

  // Initialize 3D scene
  const warRoom = new WarRoom(container);

  // Initialize HUD overlay
  const hud = new HudOverlay(container);

  // Initialize WebSocket connections
  const ws = new DualWebSocketClient();

  // Agent click → show tooltip
  warRoom.onAgentClick = (agent, screenX, screenY) => {
    hud.showAgentTooltip(agent, screenX, screenY);
  };

  // Load initial data (all in parallel)
  try {
    const [agentData, decisionStats, escalations, meetingState, governanceStatus] = await Promise.all([
      Api.getAgents(),
      Api.getDecisionStats(),
      Api.getActiveEscalations(),
      Api.getActiveMeeting(),
      Api.getGovernanceStatus().catch(() => null),
    ]);

    // Build agent hierarchy
    warRoom.agentNodes.buildHierarchy(agentData.agents, agentData.executives);
    const allAgents = warRoom.agentNodes.getAllAgents();
    hud.updateAgentCount(allAgents.length, 0);

    // Update HUD panels
    hud.updateDecisions(decisionStats);
    hud.updateEscalations(escalations);
    hud.updateMeeting(meetingState);
    if (governanceStatus) hud.updateGovernance(governanceStatus);

    // Wire data to 3D DataWalls
    warRoom.dataWalls.updatePanelData('decisions', {
      Pending: String(decisionStats.pending || 0),
      Approved: String(decisionStats.approved || 0),
      Rejected: String(decisionStats.rejected || 0),
      Deferred: String(decisionStats.deferred || 0),
    });

    warRoom.dataWalls.updatePanelData('escalations', {
      Active: String(escalations.length),
      'Avg Age': escalations.length > 0
        ? `${(escalations.reduce((s, e) => s + e.hours_elapsed, 0) / escalations.length).toFixed(1)}h`
        : '0h',
      Critical: String(escalations.filter(e => e.hours_elapsed > 24).length),
      Resolved: '\u2014',
    });

    warRoom.dataWalls.updatePanelData('meeting', {
      Status: meetingState.active ? 'ACTIVE' : 'IDLE',
      Phase: meetingState.phase || '\u2014',
      Agents: String(allAgents.length),
      Duration: '\u2014',
    });

    // Set escalation pulse
    warRoom.setEscalationPulse(escalations.length > 0);
  } catch (err) {
    console.error('[WarRoom] Initial data load failed:', err);
  }

  // Load system health (separate try-catch — may not be available)
  try {
    const health = await Api.getSystemHealth();
    if (health) {
      hud.updateSystemHealth(health);
      warRoom.dataWalls.updatePanelData('status', {
        CPU: health.cpu || '\u2014',
        Memory: health.memory || '\u2014',
        Services: String(health.services || 0),
        Uptime: health.uptime || '\u2014',
      });
    }
  } catch {
    console.warn('[WarRoom] System health not available');
  }

  // Load activity feed (separate try-catch — may not be available)
  try {
    const feed = await Api.getFeed();
    if (feed && feed.length > 0) {
      hud.loadFeed(feed);
    }
  } catch {
    console.warn('[WarRoom] Feed not available');
  }

  // WebSocket event handling
  ws.onEvent((event) => {
    switch (event.type) {
      case 'meeting_message': {
        const msg = event.payload;
        hud.addFeedMessage(msg.agent_name, msg.message);
        warRoom.agentNodes.setSpeaking(msg.agent_id, true);
        setTimeout(() => warRoom.agentNodes.setSpeaking(msg.agent_id, false), 3000);
        // Update speaking count
        const agents = warRoom.agentNodes.getAllAgents();
        hud.updateAgentCount(agents.length, agents.filter(a => a.speaking).length);
        break;
      }

      case 'meeting_started':
      case 'meeting_completed':
      case 'meeting_halted':
        Api.getActiveMeeting().then(state => {
          hud.updateMeeting(state);
          warRoom.dataWalls.updatePanelData('meeting', {
            Status: state.active ? 'ACTIVE' : 'IDLE',
            Phase: state.phase || '\u2014',
          });
        }).catch(() => {});
        if (event.type === 'meeting_completed' || event.type === 'meeting_halted') {
          warRoom.agentNodes.clearAllSpeaking();
        }
        break;

      case 'meeting_phase':
        hud.updateMeeting({
          active: true,
          phase: event.payload.phase,
          type: event.payload.meetingType,
          messages: [],
        });
        warRoom.dataWalls.updatePanelData('meeting', {
          Status: 'ACTIVE',
          Phase: event.payload.phase,
        });
        break;

      case 'new_decision':
      case 'decision_resolved':
        Api.getDecisionStats().then(stats => {
          hud.updateDecisions(stats);
          warRoom.dataWalls.updatePanelData('decisions', {
            Pending: String(stats.pending || 0),
            Approved: String(stats.approved || 0),
            Rejected: String(stats.rejected || 0),
            Deferred: String(stats.deferred || 0),
          });
        }).catch(() => {});
        break;

      case 'escalation':
        Api.getActiveEscalations().then(esc => {
          hud.updateEscalations(esc);
          warRoom.setEscalationPulse(esc.length > 0);
          warRoom.dataWalls.updatePanelData('escalations', {
            Active: String(esc.length),
            'Avg Age': esc.length > 0
              ? `${(esc.reduce((s, e) => s + e.hours_elapsed, 0) / esc.length).toFixed(1)}h`
              : '0h',
            Critical: String(esc.filter(e => e.hours_elapsed > 24).length),
          });
        }).catch(() => {});
        break;
    }
  });

  ws.connect();

  // Polling fallback — refresh every 30s
  setInterval(async () => {
    try {
      const [stats, escalations, meeting, governance] = await Promise.all([
        Api.getDecisionStats(),
        Api.getActiveEscalations(),
        Api.getActiveMeeting(),
        Api.getGovernanceStatus().catch(() => null),
      ]);
      hud.updateDecisions(stats);
      hud.updateEscalations(escalations);
      hud.updateMeeting(meeting);
      hud.updateConnection(ws.govConnected, ws.brConnected);
      warRoom.setEscalationPulse(escalations.length > 0);

      if (governance) hud.updateGovernance(governance);

      // Update 3D DataWalls
      warRoom.dataWalls.updatePanelData('decisions', {
        Pending: String(stats.pending || 0),
        Approved: String(stats.approved || 0),
        Rejected: String(stats.rejected || 0),
        Deferred: String(stats.deferred || 0),
      });
    } catch {}
  }, 30000);

  // System health polling — every 60s
  setInterval(async () => {
    try {
      const health = await Api.getSystemHealth();
      if (health) {
        hud.updateSystemHealth(health);
        warRoom.dataWalls.updatePanelData('status', {
          CPU: health.cpu || '\u2014',
          Memory: health.memory || '\u2014',
          Services: String(health.services || 0),
          Uptime: health.uptime || '\u2014',
        });
      }
    } catch {}
  }, 60000);

  // Clock update
  setInterval(() => hud.updateClock(), 1000);
  hud.updateClock();

  // Camera view buttons
  document.getElementById('btn-overview')?.addEventListener('click', () => warRoom.viewOverview());
  document.getElementById('btn-meeting')?.addEventListener('click', () => warRoom.viewMeetingFocus());
  document.getElementById('btn-agents')?.addEventListener('click', () => warRoom.viewAgentFocus());

  // Start animation loop
  warRoom.animate();

  console.log('[WarRoom] Initialized — enhanced with system health, governance, agent tooltips');
}

main().catch(err => console.error('[WarRoom] Fatal:', err));