/** * LEITL Dashboard - Live Everyone In The Loop * * Real-time dashboard showing: * - Active AI sessions * - Live activity feed * - WebDAV context status * - Message broadcasts */ window.Apps = window.Apps || {}; window.Apps.LEITL = { // State sessions: [], messages: [], activities: [], ws: null, currentSessionId: null, refreshInterval: null, /** * Initialize LEITL Dashboard */ init() { console.log('LEITL Dashboard initialized'); this.render(); this.startAutoRefresh(); }, /** * Render dashboard UI */ render() { const container = document.getElementById('leitl-container'); if (!container) { console.error('LEITL container not found'); return; } container.innerHTML = `

🔥 LEITL Dashboard

Live Everyone In The Loop - Multi-Agent Collaboration

Status: Loading...

🚀 Quick Start

👥 Active Sessions

Loading sessions...

📨 Recent Messages

Loading messages...

📊 Live Activity Feed

Loading activity...
`; // Load initial data this.loadSessions(); this.loadMessages(); this.loadActivity(); }, /** * Quick start a new LEITL session */ async quickStart() { const agentName = document.getElementById('leitl-agent-name').value.trim(); const webdavUrl = document.getElementById('leitl-webdav-url').value.trim(); if (!agentName) { alert('Please enter an agent name!'); return; } try { this.updateStatus('Starting LEITL session...', 'info'); // Build query params const params = new URLSearchParams({ agent_name: agentName }); if (webdavUrl) { params.append('webdav_url', webdavUrl); } // Call quick-start endpoint const response = await fetch(`/api/leitl/quick-start?${params.toString()}`, { method: 'POST' }); if (!response.ok) { throw new Error(`Failed to start session: ${response.statusText}`); } const data = await response.json(); // Save session ID this.currentSessionId = data.session.session_id; // Connect WebSocket this.connectWebSocket(data.session.websocket_url); // Update UI this.updateStatus(`Session started: ${data.session.session_id}`, 'success'); // Show WebDAV context if available if (data.context && data.context.matched_files) { console.log('WebDAV Context:', data.context); alert(`WebDAV Context loaded! Found ${data.context.total_matches} matching files.`); } // Refresh displays this.loadSessions(); } catch (error) { console.error('Quick start error:', error); this.updateStatus(`Error: ${error.message}`, 'error'); alert(`Error starting session: ${error.message}`); } }, /** * Connect to WebSocket */ connectWebSocket(wsUrl) { // Convert http:// to ws:// if needed if (wsUrl.startsWith('http://')) { wsUrl = wsUrl.replace('http://', 'ws://'); } else if (wsUrl.startsWith('https://')) { wsUrl = wsUrl.replace('https://', 'wss://'); } // Show WebSocket status document.getElementById('leitl-ws-status').style.display = 'block'; document.getElementById('leitl-ws-status-text').textContent = 'Connecting...'; try { this.ws = new WebSocket(wsUrl); this.ws.onopen = () => { console.log('WebSocket connected'); document.getElementById('leitl-ws-status-text').textContent = 'Connected ✅'; // Start sending heartbeats this.startHeartbeat(); }; this.ws.onmessage = (event) => { const message = JSON.parse(event.data); console.log('WebSocket message:', message); // Handle different message types if (message.event_type === 'connection.established') { console.log('Connection established'); } else if (message.event_type === 'heartbeat.confirmed') { console.log('Heartbeat confirmed'); } else { // New broadcast message - refresh UI this.loadMessages(); this.loadActivity(); this.loadSessions(); } }; this.ws.onerror = (error) => { console.error('WebSocket error:', error); document.getElementById('leitl-ws-status-text').textContent = 'Error ❌'; }; this.ws.onclose = () => { console.log('WebSocket closed'); document.getElementById('leitl-ws-status-text').textContent = 'Disconnected ⚠️'; // Stop heartbeat if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval); this.heartbeatInterval = null; } }; } catch (error) { console.error('WebSocket connection error:', error); document.getElementById('leitl-ws-status-text').textContent = `Error: ${error.message}`; } }, /** * Start sending heartbeats */ startHeartbeat() { // Send heartbeat every 30 seconds this.heartbeatInterval = setInterval(() => { if (this.ws && this.ws.readyState === WebSocket.OPEN) { this.ws.send(JSON.stringify({ type: 'heartbeat', current_task: 'Monitoring LEITL dashboard' })); } }, 30000); }, /** * Disconnect session */ async disconnectSession() { if (!this.currentSessionId) { alert('No active session to disconnect'); return; } try { // Close WebSocket if (this.ws) { this.ws.close(); this.ws = null; } // End session await fetch(`/api/leitl/session/${this.currentSessionId}/end`, { method: 'POST' }); this.updateStatus('Session disconnected', 'info'); this.currentSessionId = null; // Hide WebSocket status document.getElementById('leitl-ws-status').style.display = 'none'; // Refresh sessions this.loadSessions(); } catch (error) { console.error('Disconnect error:', error); this.updateStatus(`Error disconnecting: ${error.message}`, 'error'); } }, /** * Load active sessions */ async loadSessions() { try { const response = await fetch('/api/leitl/sessions/active'); const data = await response.json(); this.sessions = data.sessions || []; this.renderSessions(); } catch (error) { console.error('Error loading sessions:', error); document.getElementById('leitl-sessions').innerHTML = `
Error loading sessions
`; } }, /** * Render sessions list */ renderSessions() { const container = document.getElementById('leitl-sessions'); if (this.sessions.length === 0) { container.innerHTML = '
No active sessions
'; return; } const html = this.sessions.map(session => `
${session.agent_name} ${session.session_id === this.currentSessionId ? '(You)' : ''}
ID: ${session.session_id}
Status: ${session.status} | Uptime: ${session.uptime}
${session.current_task ? `
Task: ${session.current_task}
` : ''}
`).join(''); container.innerHTML = html; }, /** * Load recent messages */ async loadMessages() { try { const response = await fetch('/api/leitl/messages/recent?limit=10'); const data = await response.json(); this.messages = data.messages || []; this.renderMessages(); } catch (error) { console.error('Error loading messages:', error); document.getElementById('leitl-messages').innerHTML = `
Error loading messages
`; } }, /** * Render messages list */ renderMessages() { const container = document.getElementById('leitl-messages'); if (this.messages.length === 0) { container.innerHTML = '
No messages yet
'; return; } const html = this.messages.map(msg => { const time = new Date(msg.timestamp).toLocaleTimeString(); const eventEmoji = this.getEventEmoji(msg.event_type); return `
${time}
${eventEmoji} ${msg.event_type}
Session: ${msg.session_id.split('-').pop()}
`; }).join(''); container.innerHTML = html; }, /** * Load activity log */ async loadActivity() { try { const response = await fetch('/api/leitl/activity?limit=20'); const data = await response.json(); this.activities = data.activities || []; this.renderActivity(); } catch (error) { console.error('Error loading activity:', error); document.getElementById('leitl-activity').innerHTML = `
Error loading activity
`; } }, /** * Render activity feed */ renderActivity() { const container = document.getElementById('leitl-activity'); if (this.activities.length === 0) { container.innerHTML = '
No activity yet
'; return; } const html = this.activities.map(activity => { const time = new Date(activity.timestamp).toLocaleTimeString(); const eventEmoji = this.getEventEmoji(activity.event_type); return `
${time} - ${eventEmoji} ${activity.event_type}
`; }).join(''); container.innerHTML = html; }, /** * Get emoji for event type */ getEventEmoji(eventType) { const emojiMap = { 'session.started': '🟢', 'session.ended': '🔴', 'session.heartbeat': '💚', 'task.started': '▶️', 'task.completed': '✅', 'context.updated': '📁', 'broadcast.message': '📢', 'connection.established': '🔌', 'heartbeat.confirmed': '💓' }; return emojiMap[eventType] || '📋'; }, /** * Update status bar */ updateStatus(text, type = 'info') { const statusEl = document.getElementById('leitl-status-text'); if (statusEl) { statusEl.textContent = text; const colors = { info: '#000000', success: '#008000', error: '#ff0000', warning: '#ff8800' }; statusEl.style.color = colors[type] || colors.info; } }, /** * Start auto-refresh */ startAutoRefresh() { // Refresh every 5 seconds this.refreshInterval = setInterval(() => { this.loadSessions(); this.loadMessages(); this.loadActivity(); }, 5000); }, /** * Stop auto-refresh */ stopAutoRefresh() { if (this.refreshInterval) { clearInterval(this.refreshInterval); this.refreshInterval = null; } }, /** * Cleanup on app close */ cleanup() { this.stopAutoRefresh(); if (this.ws) { this.ws.close(); this.ws = null; } if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval); this.heartbeatInterval = null; } } };