// ========================================== // Chat Switchboard - Main Application // ========================================== async function init() { console.log('🔀 Chat Switchboard initializing...'); loadSettings(); loadModels(); Backend.init(); // Auto-detect backend at same origin let health = null; if (!Backend.baseUrl) { health = await Backend.checkHealth(''); if (health && health.database) { Backend.baseUrl = window.location.origin; Backend.save(); console.log('🔗 Backend detected at same origin'); } } // If we have saved auth, validate it if (Backend.accessToken) { health = health || await Backend.checkHealth(Backend.baseUrl); if (!health) { console.log('⚠️ Backend unreachable, falling back to unmanaged'); Backend.clear(); } } // Decide: splash gate or straight to app if (Backend.baseUrl && !Backend.accessToken) { // Online but not authenticated → show splash, hide app showSplashGate(health); initAuthListeners(); return; // Don't init app yet — authSuccess() will do it } // Already authed or no backend → go straight to app hideSplashGate(); await loadSettingsFromBackend(); await initApp(); } async function initApp() { await loadChats(); initializeEventListeners(); // In managed mode, fetch models from backend (ignore localStorage cache) if (Backend.isManaged) { fetchModels(false); // async, non-blocking } updateQuickModelSelector(); renderChatHistory(); updateConnectionStatus(); initAdmin(); console.log(`✅ Chat Switchboard ready (${Backend.isManaged ? 'managed' : 'unmanaged'} mode)`); } function showSplashGate(health) { const splash = document.getElementById('splashGate'); const app = document.getElementById('appContainer'); splash.classList.add('visible'); splash.style.display = 'flex'; // Fallback for cached CSS app.classList.add('app-hidden'); app.style.display = 'none'; // Fallback for cached CSS // Hide register tab if registration disabled const registerTab = document.getElementById('authTabRegister'); if (health && health.registration_enabled === false) { registerTab.style.display = 'none'; } else { registerTab.style.display = ''; } } function hideSplashGate() { const splash = document.getElementById('splashGate'); const app = document.getElementById('appContainer'); splash.classList.remove('visible'); splash.style.display = 'none'; // Hide splash app.classList.remove('app-hidden'); app.style.display = ''; // Show app } async function authSuccess() { hideSplashGate(); await loadSettingsFromBackend(); await initApp(); showToast(`✅ Welcome, ${Backend.user?.display_name || Backend.user?.username || 'user'}!`, 'success'); } function initializeEventListeners() { // Settings modal document.getElementById('settingsBtn').addEventListener('click', openSettings); document.getElementById('configureBtn').addEventListener('click', openSettings); document.getElementById('closeModalBtn').addEventListener('click', closeSettings); document.getElementById('cancelBtn').addEventListener('click', closeSettings); document.getElementById('saveBtn').addEventListener('click', handleSaveSettings); document.getElementById('fetchModelsBtn').addEventListener('click', () => fetchModels(true)); // Profile document.getElementById('profileChangePwBtn').addEventListener('click', () => { document.getElementById('profileChangePwForm').style.display = ''; }); document.getElementById('profileSavePwBtn').addEventListener('click', handleChangePassword); // Providers (managed mode) document.getElementById('providerShowAddBtn').addEventListener('click', showProviderForm); document.getElementById('providerCancelBtn').addEventListener('click', hideProviderForm); document.getElementById('providerCreateBtn').addEventListener('click', handleCreateProvider); document.getElementById('providerType').addEventListener('change', updateProviderEndpointHint); // Chat controls document.getElementById('newChatBtn').addEventListener('click', newChat); document.getElementById('sendBtn').addEventListener('click', sendMessage); document.getElementById('stopBtn').addEventListener('click', stopGeneration); document.getElementById('regenerateBtn').addEventListener('click', regenerateResponse); document.getElementById('toggleSidebarBtn').addEventListener('click', toggleSidebar); document.getElementById('quickFetchBtn').addEventListener('click', () => fetchModels(false)); // Quick model selector document.getElementById('quickModel').addEventListener('change', function() { if (this.value) { State.settings.model = this.value; saveSettings(); } }); // Export dropdown document.getElementById('exportBtn').addEventListener('click', function(e) { e.stopPropagation(); document.getElementById('exportDropdown').classList.toggle('show'); }); document.addEventListener('click', function() { document.getElementById('exportDropdown').classList.remove('show'); }); // Message input const messageInput = document.getElementById('messageInput'); messageInput.addEventListener('keydown', handleKeyDown); messageInput.addEventListener('input', function() { this.style.height = 'auto'; this.style.height = Math.min(this.scrollHeight, 200) + 'px'; }); // Settings modal model sync document.getElementById('model').addEventListener('change', function() { if (this.value) document.getElementById('modelCustom').value = ''; }); document.getElementById('modelCustom').addEventListener('input', function() { if (this.value) document.getElementById('model').value = ''; }); // Close modals on overlay click document.getElementById('settingsModal').addEventListener('click', function(e) { if (e.target === this) closeSettings(); }); // Connection status click document.getElementById('connectionStatus').addEventListener('click', handleConnectionClick); // Admin panel document.getElementById('adminBtn').addEventListener('click', openAdmin); document.getElementById('adminCloseBtn').addEventListener('click', closeAdmin); document.querySelectorAll('.admin-tab').forEach(tab => { tab.addEventListener('click', () => switchAdminTab(tab.dataset.tab)); }); document.getElementById('adminSaveSettings').addEventListener('click', saveAdminSettings); document.getElementById('adminModal').addEventListener('click', function(e) { if (e.target === this) closeAdmin(); }); // Global keyboard shortcuts document.addEventListener('keydown', function(e) { if (e.ctrlKey && e.key === ',') { e.preventDefault(); openSettings(); } if (e.ctrlKey && e.key === 'b') { e.preventDefault(); toggleSidebar(); } if (e.key === 'Escape') { if (State.isGenerating) { stopGeneration(); } else if (document.getElementById('settingsModal').classList.contains('active')) { closeSettings(); } } }); } function handleKeyDown(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } } function handleSaveSettings() { State.settings.apiEndpoint = document.getElementById('apiEndpoint').value.trim(); State.settings.apiKey = document.getElementById('apiKey').value.trim(); const modelCustom = document.getElementById('modelCustom').value.trim(); const modelSelect = document.getElementById('model').value; State.settings.model = modelCustom || modelSelect || 'gpt-3.5-turbo'; State.settings.stream = document.getElementById('streamResponse').checked; State.settings.saveHistory = document.getElementById('saveHistory').checked; State.settings.showThinking = document.getElementById('showThinking').checked; State.settings.systemPrompt = document.getElementById('systemPrompt').value.trim(); State.settings.maxTokens = parseInt(document.getElementById('maxTokens').value) || 4096; State.settings.temperature = parseFloat(document.getElementById('temperature').value) || 0.7; State.settings.topP = parseFloat(document.getElementById('topP').value) || 1; State.settings.presencePenalty = parseFloat(document.getElementById('presencePenalty').value) || 0; saveSettings(); saveProfile(); // async, fire-and-forget updateQuickModelSelector(); closeSettings(); showToast('✅ Settings saved', 'success'); } function newChat() { State.currentChatId = null; document.getElementById('chatMessages').innerHTML = `
Type a message below to begin