// modals.js — All modal dialogs import { send, on } from './ws.js'; import { escHtml } from './ui.js'; import { isAuthenticated, loginWithEmail, signUpWithEmail, loginWithOAuth, logout, currentUser, userProfile, userSettings } from './auth.js'; let overlay, box; function getOverlay() { return overlay || (overlay = document.getElementById('modal-overlay')); } function getBox() { return box || (box = document.getElementById('modal-box')); } export function openModal(html, opts = {}) { const o = getOverlay(), b = getBox(); b.className = 'modal-box' + (opts.wide ? ' wide' : ''); b.innerHTML = html; o.classList.remove('hidden'); if (opts.onOpen) opts.onOpen(b); // Close on overlay click o.onclick = (e) => { if (e.target === o) closeModal(); }; // Close on Escape document.addEventListener('keydown', escHandler); } const escHandler = (e) => { if (e.key === 'Escape') closeModal(); }; export function closeModal() { getOverlay().classList.add('hidden'); getBox().innerHTML = ''; document.removeEventListener('keydown', escHandler); } // ── Auth modal ──────────────────────────────────────────────────────────── export function openAuthModal(initialTab = 'signin') { openModal(` `, { onOpen(b) { // Tab switching b.querySelectorAll('.auth-tab').forEach(tab => { tab.addEventListener('click', () => { b.querySelectorAll('.auth-tab').forEach(t => t.classList.remove('active')); tab.classList.add('active'); const name = tab.dataset.tab; b.querySelector('#auth-signin').style.display = name === 'signin' ? '' : 'none'; b.querySelector('#auth-signup').style.display = name === 'signup' ? '' : 'none'; }); }); // Sign in b.querySelector('#signin-submit').addEventListener('click', async () => { const email = b.querySelector('#signin-email').value.trim(); const pass = b.querySelector('#signin-password').value; const errEl = b.querySelector('#signin-error'); errEl.style.display = 'none'; try { await loginWithEmail(email, pass); closeModal(); } catch (e) { errEl.textContent = e.message; errEl.style.display = ''; } }); // Sign up b.querySelector('#signup-submit').addEventListener('click', async () => { const email = b.querySelector('#signup-email').value.trim(); const pass = b.querySelector('#signup-password').value; const errEl = b.querySelector('#signup-error'); errEl.style.display = 'none'; try { const result = await signUpWithEmail(email, pass); if (result.access_token) { closeModal(); } else { errEl.textContent = 'Check your email to confirm your account.'; errEl.style.display = ''; } } catch (e) { errEl.textContent = e.message; errEl.style.display = ''; } }); b.querySelector('#github-btn').addEventListener('click', () => loginWithOAuth('github')); b.querySelector('#google-btn').addEventListener('click', () => loginWithOAuth('google')); b.querySelector('#forgot-pw').addEventListener('click', () => openForgotPasswordModal()); // Enter key [['#signin-email','#signin-password','#signin-submit'], ['#signup-email','#signup-password','#signup-submit']].forEach(([e, p, s]) => { [e, p].forEach(sel => { b.querySelector(sel)?.addEventListener('keydown', ev => { if (ev.key === 'Enter') b.querySelector(s)?.click(); }); }); }); } }); } function openForgotPasswordModal() { openModal(` `, { onOpen(b) { b.querySelector('#reset-submit').addEventListener('click', async () => { const email = b.querySelector('#reset-email').value.trim(); const msgEl = b.querySelector('#reset-msg'); const SUPABASE_URL = 'https://dpixehhdbtzsbckfektd.supabase.co'; const SUPABASE_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImRwaXhlaGhkYnR6c2Jja2Zla3RkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjExNDI0MjcsImV4cCI6MjA3NjcxODQyN30.nR1KCSRQj1E_evQWnE2VaZzg7PgLp2kqt4eDKP2PkpE'; try { await fetch(`${SUPABASE_URL}/auth/v1/recover`, { method: 'POST', headers: { 'Content-Type':'application/json','apikey':SUPABASE_KEY }, body: JSON.stringify({ email }), }); msgEl.textContent = 'Reset link sent. Check your email.'; msgEl.style.color = 'var(--plan-core)'; msgEl.style.display = ''; } catch { msgEl.textContent = 'Error. Try again.'; msgEl.style.display = ''; } }); } }); } // ── Share modal ─────────────────────────────────────────────────────────── export function showShareModal(sessionId) { if (!isAuthenticated()) return openAuthModal('signin'); openModal(` `, { onOpen(b) { b.querySelector('#share-generate-btn').addEventListener('click', () => { b.querySelector('#share-loading').style.display = ''; b.querySelector('#share-generate-btn').disabled = true; send({ type: 'sessions:share', sessionId }); on('sessions:shareUrl', function handler(msg) { if (msg.sessionId !== sessionId) return; import('./ws.js').then(({ off }) => off('sessions:shareUrl', handler)); b.querySelector('#share-loading').style.display = 'none'; b.querySelector('#share-url-wrap').style.display = ''; const input = b.querySelector('#share-url-input'); input.value = msg.url; b.querySelector('#share-copy-btn').addEventListener('click', async () => { await navigator.clipboard.writeText(msg.url).catch(() => {}); b.querySelector('#share-copy-btn').textContent = 'Copied!'; }); }); }); } }); } // ── Tool call modal ─────────────────────────────────────────────────────── export function showToolCallModal(call) { const names = { ollama_search: 'Web Search', read_web_page: 'Read Web Page', generate_image: 'Image Generation', generate_video: 'Video Generation', generate_audio: 'Audio Generation', }; const displayName = names[call.name] || call.name; let argsDisplay = call.args || call.arguments || '{}'; if (typeof argsDisplay !== 'string') argsDisplay = JSON.stringify(argsDisplay, null, 2); let resultDisplay = call.result || '—'; if (typeof resultDisplay !== 'string') resultDisplay = JSON.stringify(resultDisplay, null, 2); openModal(` `); } // ── Image modal ─────────────────────────────────────────────────────────── export function openImageModal(src) { openModal(` `, { onOpen(b) { b.querySelector('#img-dl-btn').addEventListener('click', () => { const a = document.createElement('a'); a.href = src; a.download = `image-${Date.now()}.png`; document.body.appendChild(a); a.click(); document.body.removeChild(a); }); } }); } // ── Chat limit modal ────────────────────────────────────────────────────── export function openLimitModal() { openModal(` `, { onOpen(b) { b.querySelector('#limit-signin').addEventListener('click', () => { closeModal(); openAuthModal('signin'); }); b.querySelector('#limit-signup').addEventListener('click', () => { closeModal(); openAuthModal('signup'); }); } }); } // ── Device session detail modal ─────────────────────────────────────────── export function openDeviceSessionModal(session, isCurrentSession) { openModal(` `, { onOpen(b) { if (!isCurrentSession) { b.querySelector('#revoke-session-btn')?.addEventListener('click', () => { send({ type: 'account:revokeSession', token: session.token }); closeModal(); }); } } }); } // ── Pasted content editor ───────────────────────────────────────────────── export function openPasteEditor(content, onSave) { openModal(` `, { onOpen(b) { b.querySelector('#paste-save').addEventListener('click', () => { const val = b.querySelector('#paste-editor').value; onSave(val); closeModal(); }); } }); } // Auto-handle limit event on('chat:limitReached', () => openLimitModal()); export { closeModal, openModal };