// settings.js — Settings modal import { send, on, off } from './ws.js'; import { openModal, closeModal, openDeviceSessionModal } from './modals.js'; import { isAuthenticated, currentUser, userProfile, userSettings } from './auth.js'; import { deleteAllSessions } from './sessions.js'; import { escHtml, showNotification } from './ui.js'; export function applyTheme(theme) { document.documentElement.setAttribute('data-theme', theme === 'light' ? 'light' : 'dark'); } export function openSettings(tab = 'chat') { // Always fetch fresh settings from server before building the modal if (isAuthenticated()) { send({ type: 'settings:get' }); // Wait for the settings response, then open modal with fresh data const handler = (msg) => { off('settings:data', handler); const freshSettings = msg.settings || userSettings || _defaultSettings(); _openSettingsModal(tab, freshSettings); }; on('settings:data', handler); // Fallback: if no response in 1.5s, open with cached settings setTimeout(() => { off('settings:data', handler); _openSettingsModal(tab, userSettings || _defaultSettings()); }, 1500); } else { // Guest: use localStorage cached settings const stored = (() => { try { return JSON.parse(localStorage.getItem('ipai_settings') || '{}'); } catch { return {}; } })(); _openSettingsModal(tab, { ..._defaultSettings(), ...stored }); } } function _defaultSettings() { return { theme: 'dark', webSearch: true, imageGen: true, videoGen: true, audioGen: true }; } function _openSettingsModal(activeTab, settings) { openModal(buildSettingsHtml(activeTab, settings), { wide: true, onOpen(b) { setupSettingsTabs(b); setupChatSettings(b); if (isAuthenticated()) { setupAccountSettings(b); } b.querySelectorAll('[data-disabled]').forEach(btn => { btn.disabled = true; btn.title = 'Coming soon'; btn.style.opacity = '0.45'; }); } }); } function buildSettingsHtml(activeTab, settings) { const authed = isAuthenticated(); return `
${authed ? `` : ''}
Theme
Light or dark interface
Available Tools
${buildToolToggle('webSearch', 'Web Search', 'Search the web for current information', settings.webSearch !== false)} ${buildToolToggle('imageGen', 'Image Generation','Generate images from prompts', settings.imageGen !== false)} ${buildToolToggle('videoGen', 'Video Generation','Generate videos from prompts', settings.videoGen !== false)} ${buildToolToggle('audioGen', 'Audio / SFX', 'Generate music and sound effects', settings.audioGen !== false)}
${authed ? buildAccountPane(activeTab) : ''} `; } function buildToolToggle(key, label, desc, enabled) { return `
${escHtml(label)}
${escHtml(desc)}
`; } function buildAccountPane(activeTab) { const u = currentUser; const p = userProfile; const email = u?.email || ''; const username = p?.username || ''; return `
Current Plan
Loading…
Data
Devices & Sessions
Loading sessions…
`; } function setupSettingsTabs(b) { b.querySelector('#settings-close')?.addEventListener('click', closeModal); b.querySelector('#settings-cancel')?.addEventListener('click', closeModal); b.querySelectorAll('.settings-tab').forEach(tab => { tab.addEventListener('click', () => { b.querySelectorAll('.settings-tab').forEach(t => t.classList.remove('active')); b.querySelectorAll('.settings-pane').forEach(p => p.classList.remove('active')); tab.classList.add('active'); b.querySelector(`[data-pane="${tab.dataset.tab}"]`)?.classList.add('active'); }); }); b.querySelector('#settings-apply')?.addEventListener('click', () => applySettings(b)); } function setupChatSettings(b) { b.querySelectorAll('[data-theme-btn]').forEach(btn => { btn.addEventListener('click', () => { b.querySelectorAll('[data-theme-btn]').forEach(t => t.classList.remove('active-theme')); btn.classList.add('active-theme'); applyTheme(btn.dataset.themeBtn); }); }); } function setupAccountSettings(b) { // Username b.querySelector('#username-save')?.addEventListener('click', async () => { const val = b.querySelector('#username-input')?.value; const msgEl = b.querySelector('#username-msg'); send({ type: 'account:setUsername', username: val }); const handler = (msg) => { off('account:usernameResult', handler); msgEl.style.display = ''; if (msg.success) { msgEl.textContent = `Username set to @${msg.username}`; msgEl.style.color = 'var(--plan-core)'; } else { msgEl.textContent = msg.error || 'Failed'; msgEl.style.color = '#f87171'; } }; on('account:usernameResult', handler); }); // Billing portal b.querySelector('#billing-portal-btn')?.addEventListener('click', () => { window.open('https://sharktide-lightning.hf.space/portal', '_blank'); }); // Delete all sessions b.querySelector('#delete-sessions-btn')?.addEventListener('click', () => { if (confirm('Delete all chats? This cannot be undone.')) { deleteAllSessions(); closeModal(); } }); // Revoke all devices b.querySelector('#revoke-all-btn')?.addEventListener('click', () => { if (confirm('Log out all other devices?')) { send({ type: 'account:revokeAllOthers' }); showNotification({ type: 'success', message: 'Other sessions logged out', duration: 2500 }); } }); // Delete account b.querySelector('#delete-account-btn')?.addEventListener('click', async () => { if (!confirm('Delete your account permanently? This cannot be undone.')) return; const auth = JSON.parse(localStorage.getItem('ipai_auth_v1') || '{}'); if (!auth.access_token) return; const res = await fetch('https://dpixehhdbtzsbckfektd.supabase.co/functions/v1/delete_account', { method: 'POST', headers: { Authorization: `Bearer ${auth.access_token}` }, }); if (res.ok) { closeModal(); import('./auth.js').then(a => a.logout()); } else { const d = await res.json().catch(() => ({})); showNotification({ type: 'error', message: d.error || 'Delete failed', duration: 4000 }); } }); // Load subscription + device sessions send({ type: 'account:getSubscription' }); send({ type: 'account:getSessions' }); const subHandler = (msg) => { off('account:subscription', subHandler); const planEl = b.querySelector('#plan-name-display'); if (planEl && msg.info) { const pKey = msg.info.planKey || 'free'; const pName = msg.info.planName || 'Free Tier'; planEl.innerHTML = `${escHtml(pName)}`; } }; on('account:subscription', subHandler); const sessHandler = (msg) => { off('account:deviceSessions', sessHandler); const listEl = b.querySelector('#device-sessions-list'); if (!listEl) return; const sessions = msg.sessions || []; const currentToken = msg.currentToken; if (sessions.length === 0) { listEl.innerHTML = '
No sessions found.
'; return; } listEl.innerHTML = sessions.map(s => `
💻
${escHtml(s.userAgent?.slice(0,50) || 'Unknown device')}
${escHtml(s.ip || '—')} · Last seen ${escHtml(s.lastSeen ? new Date(s.lastSeen).toLocaleDateString() : '—')}
${s.token === currentToken ? '
Current session
' : ''}
`).join(''); listEl.querySelectorAll('.device-session-item').forEach(el => { el.addEventListener('click', () => { const token = el.dataset.token; const session = sessions.find(s => s.token === token); if (session) openDeviceSessionModal(session, token === currentToken); }); }); }; on('account:deviceSessions', sessHandler); } function applySettings(b) { const theme = b.querySelector('[data-theme-btn].active-theme')?.dataset.themeBtn || (document.documentElement.getAttribute('data-theme') === 'light' ? 'light' : 'dark'); const tools = {}; b.querySelectorAll('[data-toggle]').forEach(label => { const key = label.dataset.toggle; const checked = label.querySelector('input[type="checkbox"]').checked; tools[key] = checked; }); const newSettings = { theme, ...tools }; applyTheme(theme); // Sync tool buttons in chat UI document.querySelectorAll('[data-tool]').forEach(btn => { const t = btn.dataset.tool; if (t in tools) { btn.classList.toggle('active', !!tools[t]); btn.style.display = ''; } }); // Cache for guests if (!isAuthenticated()) { try { localStorage.setItem('ipai_settings', JSON.stringify(newSettings)); } catch {} } if (isAuthenticated()) { send({ type: 'settings:save', settings: newSettings }); } closeModal(); } // Init theme on load from stored settings on('auth:ok', (msg) => { if (msg.settings?.theme) applyTheme(msg.settings.theme); }); on('auth:guestOk', () => { const stored = (() => { try { return JSON.parse(localStorage.getItem('ipai_settings') || '{}'); } catch { return {}; } })(); applyTheme(stored.theme || 'dark'); });