// 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
`;
}
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');
});