/**
* 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...
👥 Active Sessions
Loading sessions...
📨 Recent Messages
Loading messages...
📊 Live Activity Feed
Loading activity...
WebSocket: Not connected
`;
// 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;
}
}
};