/** * Antigravity Console - Main Entry * * This file orchestrates Alpine.js initialization. * Components are loaded via separate script files that register themselves * to window.Components before this script runs. */ document.addEventListener('alpine:init', () => { // Register Components (loaded from separate files via window.Components) Alpine.data('dashboard', window.Components.dashboard); Alpine.data('models', window.Components.models); Alpine.data('accountManager', window.Components.accountManager); Alpine.data('claudeConfig', window.Components.claudeConfig); Alpine.data('logsViewer', window.Components.logsViewer); // View Loader Directive Alpine.directive('load-view', (el, { expression }, { evaluate }) => { if (!window.viewCache) window.viewCache = new Map(); // Evaluate the expression to get the actual view name (removes quotes) const viewName = evaluate(expression); if (window.viewCache.has(viewName)) { el.innerHTML = window.viewCache.get(viewName); Alpine.initTree(el); return; } fetch(`views/${viewName}.html?t=${Date.now()}`) .then(response => { if (!response.ok) throw new Error(`HTTP ${response.status}`); return response.text(); }) .then(html => { // Update cache (optional, or remove if we want always-fresh) // keeping cache for session performance, but initial load will now bypass browser cache window.viewCache.set(viewName, html); el.innerHTML = html; Alpine.initTree(el); }) .catch(err => { console.error('Failed to load view:', viewName, err); el.innerHTML = `
Error loading view: ${viewName}
${err.message}
`; }); }); // Main App Controller Alpine.data('app', () => ({ get connectionStatus() { return Alpine.store('data')?.connectionStatus || 'connecting'; }, get loading() { return Alpine.store('data')?.loading || false; }, sidebarOpen: window.innerWidth >= 1024, toggleSidebar() { this.sidebarOpen = !this.sidebarOpen; }, init() { console.log('App controller initialized'); // Handle responsive sidebar transitions let lastWidth = window.innerWidth; window.addEventListener('resize', () => { const currentWidth = window.innerWidth; const lgBreakpoint = 1024; // Desktop -> Mobile: Auto-close sidebar to prevent overlay blocking screen if (lastWidth >= lgBreakpoint && currentWidth < lgBreakpoint) { this.sidebarOpen = false; } // Mobile -> Desktop: Auto-open sidebar (restore standard desktop layout) if (lastWidth < lgBreakpoint && currentWidth >= lgBreakpoint) { this.sidebarOpen = true; } lastWidth = currentWidth; }); // Theme setup document.documentElement.setAttribute('data-theme', 'black'); document.documentElement.classList.add('dark'); // Chart Defaults if (typeof Chart !== 'undefined') { Chart.defaults.color = window.utils.getThemeColor('--color-text-dim'); Chart.defaults.borderColor = window.utils.getThemeColor('--color-space-border'); Chart.defaults.font.family = '"JetBrains Mono", monospace'; } // Start Data Polling this.startAutoRefresh(); document.addEventListener('refresh-interval-changed', () => this.startAutoRefresh()); // Initial Fetch Alpine.store('data').fetchData(); }, refreshTimer: null, fetchData() { Alpine.store('data').fetchData(); }, startAutoRefresh() { if (this.refreshTimer) clearInterval(this.refreshTimer); const interval = parseInt(Alpine.store('settings')?.refreshInterval || 60); if (interval > 0) { this.refreshTimer = setInterval(() => Alpine.store('data').fetchData(), interval * 1000); } }, t(key) { return Alpine.store('global')?.t(key) || key; }, async addAccountWeb(reAuthEmail = null) { const password = Alpine.store('global').webuiPassword; try { const urlPath = reAuthEmail ? `/api/auth/url?email=${encodeURIComponent(reAuthEmail)}` : '/api/auth/url'; const { response, newPassword } = await window.utils.request(urlPath, {}, password); if (newPassword) Alpine.store('global').webuiPassword = newPassword; const data = await response.json(); if (data.status === 'ok') { // Show info toast that OAuth is in progress Alpine.store('global').showToast(Alpine.store('global').t('oauthInProgress'), 'info'); // Open OAuth window const oauthWindow = window.open(data.url, 'google_oauth', 'width=600,height=700,scrollbars=yes'); // Poll for account changes instead of relying on postMessage // (since OAuth callback is now on port 51121, not this server) const initialAccountCount = Alpine.store('data').accounts.length; let pollCount = 0; const maxPolls = 60; // 2 minutes (2 second intervals) let cancelled = false; // Show progress modal Alpine.store('global').oauthProgress = { active: true, current: 0, max: maxPolls, cancel: () => { cancelled = true; clearInterval(pollInterval); Alpine.store('global').oauthProgress.active = false; Alpine.store('global').showToast(Alpine.store('global').t('oauthCancelled'), 'info'); if (oauthWindow && !oauthWindow.closed) { oauthWindow.close(); } } }; const pollInterval = setInterval(async () => { if (cancelled) { clearInterval(pollInterval); return; } pollCount++; Alpine.store('global').oauthProgress.current = pollCount; // Check if OAuth window was closed manually if (oauthWindow && oauthWindow.closed && !cancelled) { clearInterval(pollInterval); Alpine.store('global').oauthProgress.active = false; Alpine.store('global').showToast(Alpine.store('global').t('oauthWindowClosed'), 'warning'); return; } // Refresh account list await Alpine.store('data').fetchData(); // Check if new account was added const currentAccountCount = Alpine.store('data').accounts.length; if (currentAccountCount > initialAccountCount) { clearInterval(pollInterval); Alpine.store('global').oauthProgress.active = false; const actionKey = reAuthEmail ? 'accountReauthSuccess' : 'accountAddedSuccess'; Alpine.store('global').showToast( Alpine.store('global').t(actionKey), 'success' ); document.getElementById('add_account_modal')?.close(); if (oauthWindow && !oauthWindow.closed) { oauthWindow.close(); } } // Stop polling after max attempts if (pollCount >= maxPolls) { clearInterval(pollInterval); Alpine.store('global').oauthProgress.active = false; Alpine.store('global').showToast( Alpine.store('global').t('oauthTimeout'), 'warning' ); } }, 2000); // Poll every 2 seconds } else { Alpine.store('global').showToast(data.error || Alpine.store('global').t('failedToGetAuthUrl'), 'error'); } } catch (e) { Alpine.store('global').showToast(Alpine.store('global').t('failedToStartOAuth') + ': ' + e.message, 'error'); } } })); });