// Initialize document.addEventListener('DOMContentLoaded', async () => { // Check if multi-user mode is enabled (retry if server not ready yet) for (let attempt = 0; attempt < 5; attempt++) { try { const configResp = await fetch('/api/config'); const config = await configResp.json(); IS_MULTI_USER = config.multiUser; if (config.multiUser && !SESSION_ID) { await showUsernameOverlay(); } break; } catch (e) { if (attempt < 4) { await new Promise(r => setTimeout(r, 1000)); } // After all retries fail, continue in single-user mode } } // Fetch agent registry from backend (single source of truth) try { const agentsResp = await apiFetch('/api/agents'); const agentsData = await agentsResp.json(); for (const agent of agentsData.agents) { AGENT_REGISTRY[agent.key] = agent; } } catch (e) { console.error('Failed to fetch agent registry, using fallback'); // Minimal fallback so the app still works if backend is slow AGENT_REGISTRY = { command: { label: 'MAIN', hasCounter: false, inMenu: false, inLauncher: false, placeholder: 'Enter message...' }, agent: { label: 'AGENT', hasCounter: true, inMenu: true, inLauncher: true, placeholder: 'Enter message...' }, code: { label: 'CODE', hasCounter: true, inMenu: true, inLauncher: true, placeholder: 'Enter message...' }, research: { label: 'RESEARCH', hasCounter: true, inMenu: true, inLauncher: true, placeholder: 'Enter message...' }, image: { label: 'IMAGE', hasCounter: true, inMenu: true, inLauncher: true, placeholder: 'Describe an image or paste a URL...' }, }; } // Initialize settings.agents with registry keys (before loadSettings merges saved values) settings.agents = Object.fromEntries(Object.keys(AGENT_REGISTRY).map(k => [k, ''])); await loadSettings(); applyTheme(settings.themeColor || 'forest'); initializeEventListeners(); initializeSessionListeners(); updateUserIndicator(); // Always show session selector on load (don't auto-resume last session) const sessionsData = await fetchSessions(); showSessionSelector(sessionsData.sessions); // Compute line heights for the static welcome diagram (after layout) requestAnimationFrame(() => updateTimelineLines()); }); // ============================================================ // Dynamic HTML generation from AGENT_REGISTRY // ============================================================ function renderNewTabMenu() { const menu = document.getElementById('newTabMenu'); if (!menu) return; menu.innerHTML = ''; for (const [key, agent] of Object.entries(AGENT_REGISTRY)) { if (!agent.inMenu) continue; const item = document.createElement('div'); item.className = 'menu-item'; item.dataset.type = key; item.textContent = agent.label; menu.appendChild(item); } } function renderLauncherButtons() { const container = document.getElementById('launcherButtons'); if (!container) return; // Insert before the debug button (keep it at the end) const debugBtn = container.querySelector('#debugBtn'); for (const [key, agent] of Object.entries(AGENT_REGISTRY)) { if (!agent.inLauncher) continue; const btn = document.createElement('button'); btn.className = 'launcher-btn'; btn.dataset.type = key; btn.textContent = agent.label; container.insertBefore(btn, debugBtn); } } function renderAgentModelSelectors() { const grid = document.getElementById('agentModelsGrid'); if (!grid) return; grid.innerHTML = ''; for (const [key, agent] of Object.entries(AGENT_REGISTRY)) { const label = document.createElement('label'); label.textContent = `${agent.label}:`; const select = document.createElement('select'); select.id = `setting-agent-${key}`; select.className = 'settings-select'; grid.appendChild(label); grid.appendChild(select); } } function initializeEventListeners() { // Generate dynamic UI elements from registry renderLauncherButtons(); renderNewTabMenu(); renderAgentModelSelectors(); // Launcher buttons in command center document.querySelectorAll('.launcher-btn').forEach(btn => { btn.addEventListener('click', (e) => { e.stopPropagation(); const type = btn.dataset.type; createAgentTab(type); }); }); // Command center chat functionality const commandContent = document.querySelector('.tab-content[data-content-id="0"]'); if (commandContent) { setupInputListeners(commandContent, 0); } // Sidebar checkboxes const compactViewCheckbox = document.getElementById('compactViewCheckbox'); const collapseAgentsCheckbox = document.getElementById('collapseAgentsCheckbox'); const collapseToolsCheckbox = document.getElementById('collapseToolsCheckbox'); // Compact view: affects timeline only (collapse agents + hide turns) if (compactViewCheckbox) { compactViewCheckbox.addEventListener('change', () => { const compact = compactViewCheckbox.checked; if (compact) { // Collapse all agent boxes in timeline Object.entries(timelineData).forEach(([id, data]) => { if (data.parentTabId !== null) { collapsedAgents.add(String(id)); } }); showAllTurns = false; } else { collapsedAgents.clear(); showAllTurns = true; } renderTimeline(); }); } // Chat collapse: agents — affects action-widgets in chat only if (collapseAgentsCheckbox) { collapseAgentsCheckbox.addEventListener('change', () => { const collapse = collapseAgentsCheckbox.checked; document.querySelectorAll('.action-widget').forEach(w => { const toggle = w.querySelector('.widget-collapse-toggle'); if (collapse) { w.classList.add('collapsed'); if (toggle) toggle.classList.add('collapsed'); } else { w.classList.remove('collapsed'); if (toggle) toggle.classList.remove('collapsed'); } }); }); } // Chat collapse: tools — affects tool-cells in chat only if (collapseToolsCheckbox) { collapseToolsCheckbox.addEventListener('change', () => { const collapse = collapseToolsCheckbox.checked; document.querySelectorAll('.tool-cell').forEach(w => { const toggle = w.querySelector('.widget-collapse-toggle'); if (collapse) { w.classList.add('collapsed'); if (toggle) toggle.classList.add('collapsed'); } else { w.classList.remove('collapsed'); if (toggle) toggle.classList.remove('collapsed'); } }); }); } // New tab button - toggle menu const newTabBtn = document.getElementById('newTabBtn'); const newTabMenu = document.getElementById('newTabMenu'); if (newTabBtn && newTabMenu) { newTabBtn.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); // Position the menu below the button const rect = newTabBtn.getBoundingClientRect(); newTabMenu.style.top = `${rect.bottom}px`; newTabMenu.style.left = `${rect.left}px`; newTabMenu.classList.toggle('active'); }); } // Close menu when clicking outside document.addEventListener('click', (e) => { if (!e.target.closest('.new-tab-wrapper')) { newTabMenu.classList.remove('active'); } }); // Menu items document.querySelectorAll('.menu-item').forEach(item => { item.addEventListener('click', () => { const type = item.dataset.type; createAgentTab(type); newTabMenu.classList.remove('active'); }); }); // Tab switching document.addEventListener('click', (e) => { const tab = e.target.closest('.tab'); if (tab && !e.target.closest('.new-tab-wrapper')) { if (e.target.classList.contains('tab-close')) { closeTab(parseInt(tab.dataset.tabId)); } else { switchToTab(parseInt(tab.dataset.tabId)); } } }); // Settings button - now handled in settings panel section below // (removed old openSettings() call) // Save settings button const saveSettingsBtn = document.getElementById('saveSettingsBtn'); if (saveSettingsBtn) { saveSettingsBtn.addEventListener('click', () => { saveSettings(); }); } // Cancel settings button const cancelSettingsBtn = document.getElementById('cancelSettingsBtn'); if (cancelSettingsBtn) { cancelSettingsBtn.addEventListener('click', () => closeAllPanels()); } // Theme color picker const themePicker = document.getElementById('theme-color-picker'); if (themePicker) { themePicker.addEventListener('click', (e) => { const themeOption = e.target.closest('.theme-option'); if (themeOption) { // Remove selected from all themePicker.querySelectorAll('.theme-option').forEach(opt => { opt.classList.remove('selected'); }); // Add selected to clicked themeOption.classList.add('selected'); // Update hidden input const themeValue = themeOption.dataset.theme; document.getElementById('setting-theme-color').value = themeValue; } }); } // Settings group collapse/expand document.addEventListener('click', (e) => { const header = e.target.closest('.settings-group-header'); if (!header) return; const group = header.closest('.settings-group'); if (!group) return; group.classList.toggle('collapsed'); const toggle = header.querySelector('.widget-collapse-toggle'); if (toggle) toggle.classList.toggle('collapsed'); }); // Double-click on collapse toggle: collapse/uncollapse all widgets in the same chat document.addEventListener('dblclick', (e) => { const toggle = e.target.closest('.widget-collapse-toggle'); if (!toggle) return; e.stopPropagation(); const chatContainer = toggle.closest('.chat-container'); if (!chatContainer) return; const widgets = chatContainer.querySelectorAll('.tool-cell, .code-cell, .action-widget'); if (widgets.length === 0) return; // If clicked widget is now collapsed, collapse all; otherwise uncollapse all const clickedWidget = toggle.closest('.tool-cell, .code-cell, .action-widget'); const shouldCollapse = !clickedWidget?.classList.contains('collapsed'); widgets.forEach(w => { w.classList.toggle('collapsed', shouldCollapse); const t = w.querySelector('.widget-collapse-toggle'); if (t) t.classList.toggle('collapsed', shouldCollapse); }); }); // Resizable sidebar const sidebar = document.getElementById('agentsSidebar'); const resizeHandle = document.getElementById('sidebarResizeHandle'); if (sidebar && resizeHandle) { const savedWidth = localStorage.getItem('agentui_sidebar_width'); if (savedWidth) { const w = parseInt(savedWidth); if (w >= 140 && w <= 400) { sidebar.style.setProperty('--sidebar-width', w + 'px'); sidebar.style.width = w + 'px'; } } let dragging = false; resizeHandle.addEventListener('mousedown', (e) => { e.preventDefault(); dragging = true; resizeHandle.classList.add('dragging'); document.body.style.cursor = 'col-resize'; document.body.style.userSelect = 'none'; }); document.addEventListener('mousemove', (e) => { if (!dragging) return; const w = Math.min(400, Math.max(140, e.clientX)); sidebar.style.setProperty('--sidebar-width', w + 'px'); sidebar.style.width = w + 'px'; }); document.addEventListener('mouseup', () => { if (!dragging) return; dragging = false; resizeHandle.classList.remove('dragging'); document.body.style.cursor = ''; document.body.style.userSelect = ''; const w = parseInt(sidebar.style.width); if (w) localStorage.setItem('agentui_sidebar_width', w); }); } }