document.addEventListener('DOMContentLoaded', async () => { let currentUser = null; let supabase; let ws; let pingInterval; // --- 0. INIT --- try { const config = await (await fetch('/api/config')).json(); supabase = window.supabase.createClient(config.supabase_url, config.supabase_key); } catch(e) { console.error("Config failed", e); } // --- 1. LOGIN --- document.getElementById('login-form').addEventListener('submit', async (e) => { e.preventDefault(); const u = document.getElementById('login-user').value.trim(); const p = document.getElementById('login-pass').value.trim(); const { data: user } = await supabase.from('users').select('*').eq('username', u).single(); if (user && dcodeIO.bcrypt.compareSync(p, user.password_hash)) { currentUser = user; document.getElementById('login-modal').classList.remove('open'); document.getElementById('app-interface').classList.remove('hidden'); document.getElementById('current-username').textContent = user.username; // Инициализация чекбокса настроек document.getElementById('setting-discovery').checked = user.is_looking_for_friends || false; initApp(); } else { const err = document.getElementById('login-error'); err.textContent = "INVALID ACCESS"; err.classList.remove('hidden'); } }); // --- 2. TABS LOGIC --- const navBtns = document.querySelectorAll('.nav-btn[data-tab]'); const viewChats = document.getElementById('view-chats'); const viewDiscovery = document.getElementById('view-discovery'); const sidebarTitle = document.getElementById('sidebar-title'); navBtns.forEach(btn => { btn.addEventListener('click', () => { // Remove active class navBtns.forEach(b => b.classList.remove('active')); btn.classList.add('active'); const tab = btn.dataset.tab; if (tab === 'chats') { viewChats.classList.remove('hidden'); viewDiscovery.classList.add('hidden'); sidebarTitle.textContent = "CHANNELS"; } else if (tab === 'discovery') { viewChats.classList.add('hidden'); viewDiscovery.classList.remove('hidden'); sidebarTitle.textContent = "FIND FRIENDS"; loadDiscoveryUsers(); } }); }); // --- 3. DISCOVERY & SETTINGS --- const settingsModal = document.getElementById('settings-modal'); document.getElementById('open-settings').addEventListener('click', () => settingsModal.classList.add('open')); document.getElementById('save-settings').addEventListener('click', async () => { const isLooking = document.getElementById('setting-discovery').checked; // Update DB await supabase.from('users').update({ is_looking_for_friends: isLooking }).eq('id', currentUser.id); currentUser.is_looking_for_friends = isLooking; // local update settingsModal.classList.remove('open'); if(isLooking) alert("You are now visible in Discovery!"); }); async function loadDiscoveryUsers() { const list = document.getElementById('discovery-list'); list.innerHTML = '
Scanning sector...
'; const { data: users } = await supabase .from('users') .select('*') .eq('is_looking_for_friends', true) .neq('username', currentUser.username); // Exclude self list.innerHTML = ''; if (!users || users.length === 0) { list.innerHTML = '
No signals found.
'; return; } users.forEach(u => { const div = document.createElement('div'); div.className = 'flex items-center justify-between p-2 bg-white/5 rounded hover:bg-white/10 transition cursor-pointer'; div.innerHTML = `
${u.username}
`; // Logic for "Connect" -> Create DM channel would go here list.appendChild(div); }); } // --- 4. ADMIN & CHAT --- // (Admin Logic code from previous answer remains similar but linked to new modals) document.getElementById('admin-trigger').addEventListener('click', () => { document.getElementById('admin-modal').classList.add('open'); }); document.getElementById('admin-code').addEventListener('input', (e) => { if(e.target.value.length >= 4) { document.getElementById('admin-create-form').classList.remove('hidden'); } }); document.getElementById('admin-create-form').addEventListener('submit', async (e) => { e.preventDefault(); const code = document.getElementById('admin-code').value; const newU = document.getElementById('new-user').value; const newP = document.getElementById('new-pass').value; try { const res = await fetch('/api/admin/create_user', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({admin_code: code, new_username: newU, new_password: newP}) }); if(!res.ok) throw new Error("DENIED"); const data = await res.json(); await supabase.from('users').insert({ username: data.username, password_hash: data.password_hash, badge: "BETA" }); alert("CREATED"); document.getElementById('admin-modal').classList.remove('open'); } catch(e) { alert("ACCESS DENIED"); } }); // --- 5. APP INIT & WEBSOCKET FIX --- function initApp() { const msgContainer = document.getElementById('messages-container'); // Load Chat History supabase.from('messages').select('*').eq('channel_id', 'general').order('created_at') .then(({data}) => { if(data) data.forEach(renderMessage); }); // Realtime Subscription supabase.channel('public:messages') .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'messages' }, payload => { if(payload.new.channel_id === 'general') renderMessage(payload.new); }) .subscribe(); // WebSocket for Voice/Signaling connectWebSocket(); } function connectWebSocket() { // Secure WebSocket handling for HF Spaces const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; // Remove trailing slash if present const host = window.location.host; const wsUrl = `${protocol}//${host}/ws/signal`; console.log("Attempting WS connect to:", wsUrl); ws = new WebSocket(wsUrl); ws.onopen = () => console.log("Signal Line Established"); ws.onerror = (e) => console.log("Signal Error (Ignore if not using Voice)", e); } const chatForm = document.getElementById('chat-form'); chatForm.addEventListener('submit', async (e) => { e.preventDefault(); const inp = document.getElementById('msg-input'); const text = inp.value.trim(); if(!text) return; inp.value = ''; const moodRes = await fetch('/api/analyze_mood', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({text}) }); const mood = await moodRes.json(); await supabase.from('messages').insert({ content: text, user_id: currentUser.id, username: currentUser.username, mood_color: mood.mood_color, channel_id: 'general' }); }); function renderMessage(msg) { const div = document.createElement('div'); const isOwn = msg.username === currentUser.username; div.className = `flex gap-2 mb-4 ${isOwn ? 'flex-row-reverse' : ''} px-4`; div.innerHTML = `
${msg.username}
${msg.content}
`; const c = document.getElementById('messages-container'); c.appendChild(div); c.scrollTop = c.scrollHeight; } });