// ========================================== // State Management (Mode-Aware) // ========================================== // In unmanaged mode: localStorage (same as before) // In managed mode: backend API with local cache // ========================================== const State = { settings: { apiEndpoint: '', apiKey: '', model: 'gpt-3.5-turbo', stream: true, saveHistory: true, showThinking: true, systemPrompt: '', maxTokens: 4096, temperature: 0.7, topP: 1, presencePenalty: 0 }, models: [], chats: [], currentChatId: null, isGenerating: false, abortController: null }; // ── Settings (localStorage + backend sync) ── function loadSettings() { const saved = Storage.get('chatSwitchboard_settings'); if (saved) { State.settings = { ...State.settings, ...saved }; } } function saveSettings() { Storage.set('chatSwitchboard_settings', State.settings); // Fire-and-forget sync to backend syncSettingsToBackend(); } async function syncSettingsToBackend() { if (!Backend.isManaged) return; try { // Only sync preferences, not apiEndpoint/apiKey (those live in api_configs) const prefs = { model: State.settings.model, stream: State.settings.stream, saveHistory: State.settings.saveHistory, showThinking: State.settings.showThinking, systemPrompt: State.settings.systemPrompt, maxTokens: State.settings.maxTokens, temperature: State.settings.temperature, topP: State.settings.topP, presencePenalty: State.settings.presencePenalty }; await Backend.updateSettings(prefs); } catch (e) { console.warn('Failed to sync settings to backend:', e); } } async function loadSettingsFromBackend() { if (!Backend.isManaged) return; try { const remote = await Backend.getSettings(); if (remote && Object.keys(remote).length > 0) { // Merge remote prefs over local, but keep apiEndpoint/apiKey local const localEndpoint = State.settings.apiEndpoint; const localKey = State.settings.apiKey; State.settings = { ...State.settings, ...remote }; State.settings.apiEndpoint = localEndpoint; State.settings.apiKey = localKey; Storage.set('chatSwitchboard_settings', State.settings); } } catch (e) { console.warn('Failed to load settings from backend:', e); } } // ── Models (always localStorage) ──────────── function loadModels() { State.models = Storage.get('chatSwitchboard_models', []); } function saveModels() { Storage.set('chatSwitchboard_models', State.models); } // ── Chats ─────────────────────────────────── async function loadChats() { if (Backend.isManaged) { try { const resp = await Backend.listChats(1, 100); State.chats = (resp.data || []).map(c => ({ id: c.id, title: c.title, model: c.model || '', systemPrompt: c.system_prompt || '', isArchived: c.is_archived, isPinned: c.is_pinned, messageCount: c.message_count || 0, messages: [], // loaded on demand createdAt: c.created_at, updatedAt: c.updated_at })); } catch (e) { console.error('Failed to load chats from backend:', e); showToast('⚠️ Failed to load chats', 'error'); } } else { State.chats = Storage.get('chatSwitchboard_chats', []); } } function saveChats() { // In managed mode, individual operations handle persistence. // In unmanaged mode, write to localStorage. if (!Backend.isManaged && State.settings.saveHistory) { Storage.set('chatSwitchboard_chats', State.chats); } } async function createChat(title, messages) { if (Backend.isManaged) { try { const resp = await Backend.createChat( title, State.settings.model, State.settings.systemPrompt ); const chat = { id: resp.id, title: resp.title, model: resp.model || '', systemPrompt: resp.system_prompt || '', messages: messages || [], messageCount: 0, createdAt: resp.created_at, updatedAt: resp.updated_at }; State.chats.unshift(chat); // Persist any initial messages (system prompt, first user msg) for (const msg of (messages || [])) { await Backend.createMessage(chat.id, msg.role, msg.content, msg.model); } chat.messageCount = (messages || []).length; return chat; } catch (e) { console.error('Failed to create chat:', e); showToast('⚠️ Failed to create chat', 'error'); return null; } } else { const chat = { id: Date.now().toString(), title, messages: messages || [], model: State.settings.model, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; State.chats.unshift(chat); if (State.chats.length > 100) { State.chats = State.chats.slice(0, 100); } saveChats(); return chat; } } async function updateChat(chatId, updates) { const chat = State.chats.find(c => c.id === chatId); if (!chat) return; if (Backend.isManaged) { // Separate backend-updateable fields from local-only fields const backendUpdates = {}; if (updates.title !== undefined) backendUpdates.title = updates.title; if (updates.model !== undefined) backendUpdates.model = updates.model; if (updates.is_archived !== undefined) backendUpdates.is_archived = updates.is_archived; if (updates.is_pinned !== undefined) backendUpdates.is_pinned = updates.is_pinned; if (Object.keys(backendUpdates).length > 0) { try { await Backend.updateChat(chatId, backendUpdates); } catch (e) { console.error('Failed to update chat:', e); } } // Update local cache Object.assign(chat, updates, { updatedAt: new Date().toISOString() }); } else { Object.assign(chat, updates, { updatedAt: new Date().toISOString() }); saveChats(); } } async function deleteChat(chatId) { if (Backend.isManaged) { try { await Backend.deleteChat(chatId); } catch (e) { console.error('Failed to delete chat:', e); showToast('⚠️ Failed to delete chat', 'error'); return; } } State.chats = State.chats.filter(c => c.id !== chatId); saveChats(); } function getChat(chatId) { return State.chats.find(c => c.id === chatId); } function getCurrentChat() { return State.currentChatId ? getChat(State.currentChatId) : null; } // ── Messages (managed mode) ───────────────── async function loadMessages(chatId) { if (!Backend.isManaged) return; const chat = getChat(chatId); if (!chat) return; // Skip if already loaded if (chat.messages && chat.messages.length > 0) return; try { const resp = await Backend.listMessages(chatId, 1, 200); chat.messages = (resp.data || []).map(m => ({ role: m.role, content: m.content, model: m.model || '', timestamp: m.created_at, _backendId: m.id })); } catch (e) { console.error('Failed to load messages:', e); showToast('⚠️ Failed to load messages', 'error'); } } async function persistMessage(chatId, role, content, model) { if (!Backend.isManaged) return; try { await Backend.createMessage(chatId, role, content, model); } catch (e) { console.error('Failed to persist message:', e); } }