Spaces:
Sleeping
Sleeping
| // static/js/app.js v7 | |
| function switchView(id, navItem) { | |
| document.querySelectorAll('.view-container').forEach(function(v) { v.classList.remove('active'); }); | |
| var el = document.getElementById(id); | |
| if (el) el.classList.add('active'); | |
| if (navItem) { | |
| document.querySelectorAll('.nav-item').forEach(function(n) { n.classList.remove('active'); }); | |
| navItem.classList.add('active'); | |
| if (id === 'projects-view') loadPublishedProjects(); | |
| if (id === 'ai-chat-view') loadAIChatHistory(); | |
| if (id === 'apps-view') checkGithubStatus(); | |
| if (id === 'terminal-view' && typeof initTerminal === 'function') { | |
| setTimeout(function() { initTerminal(); }, 100); | |
| } | |
| if (id === 'editor-view' && typeof initEditor === 'function') { | |
| setTimeout(function() { initEditor(); }, 100); | |
| } | |
| if (window.innerWidth <= 768) { | |
| document.getElementById('sidebar').classList.remove('open'); | |
| document.getElementById('sidebar-overlay').classList.remove('active'); | |
| } | |
| } | |
| } | |
| window.toggleTheme = function() { | |
| var r = document.documentElement; | |
| var isLight = r.classList.toggle('light'); | |
| var icon = document.getElementById('theme-icon'); | |
| localStorage.setItem('theme', isLight ? 'light' : 'dark'); | |
| if (icon) { icon.className = isLight ? 'fa-solid fa-sun' : 'fa-solid fa-moon'; } | |
| if (typeof editor !== 'undefined' && editor) editor.setTheme(isLight ? 'ace/theme/github' : 'ace/theme/tomorrow_night_eighties'); | |
| if (typeof updateTermTheme === 'function') updateTermTheme(); | |
| var sel = document.getElementById('setting-theme-mode'); | |
| if (sel) sel.value = isLight ? 'light' : 'dark'; | |
| }; | |
| function loadTheme() { | |
| if (localStorage.getItem('theme') === 'light') { | |
| document.documentElement.classList.add('light'); | |
| var icon = document.getElementById('theme-icon'); | |
| if (icon) { icon.className = 'fa-solid fa-sun'; } | |
| } | |
| } | |
| document.addEventListener('DOMContentLoaded', function() { | |
| loadTheme(); | |
| if (typeof marked !== 'undefined') { | |
| marked.setOptions({ | |
| highlight: function(code, lang) { | |
| if (typeof hljs !== 'undefined') { | |
| var l = hljs.getLanguage(lang) ? lang : 'plaintext'; | |
| return hljs.highlight(code, { language: l }).value; | |
| } | |
| return code; | |
| }, | |
| breaks: true, gfm: true | |
| }); | |
| } | |
| }); | |
| // Toast | |
| function showToast(msg, type) { | |
| type = type || 'info'; | |
| var c = document.getElementById('toast-container'); | |
| if (!c) { c = document.createElement('div'); c.id = 'toast-container'; document.body.appendChild(c); } | |
| var t = document.createElement('div'); | |
| var icon = 'fa-info-circle', col = 'var(--info)'; | |
| if (type === 'success') { icon = 'fa-check-circle'; col = 'var(--success)'; } | |
| if (type === 'error') { icon = 'fa-exclamation-circle'; col = 'var(--error)'; } | |
| if (type === 'warning') { icon = 'fa-exclamation-triangle'; col = 'var(--warning)'; } | |
| t.style.cssText = 'background:var(--bg-elevated);color:var(--text-1);border:1px solid var(--border);border-left:3px solid ' + col + ';padding:12px 16px;border-radius:var(--r-sm);box-shadow:var(--elev-2);display:flex;align-items:center;gap:10px;transform:translateY(20px);opacity:0;transition:all 0.2s ease;font-size:14px;min-height:44px;'; | |
| t.innerHTML = '<i class="fas ' + icon + '" style="color:' + col + ';font-size:16px"></i><span>' + msg + '</span>'; | |
| c.appendChild(t); | |
| requestAnimationFrame(function() { t.style.transform = 'translateY(0)'; t.style.opacity = '1'; }); | |
| setTimeout(function() { t.style.transform = 'translateY(20px)'; t.style.opacity = '0'; setTimeout(function() { t.remove(); }, 200); }, 3500); | |
| } | |
| // AI Chat | |
| var aiChatHistory = []; | |
| async function sendChatMessage() { | |
| var inputEl = document.getElementById('ai-chat-input'); | |
| var msg = inputEl.value.trim(); | |
| if (!msg || !currentToken) return; | |
| inputEl.value = ''; | |
| appendChat('user', msg); | |
| var bid = 'ai-' + Date.now(); | |
| appendChat('system', '<i class="fa-solid fa-spinner fa-spin"></i> Thinking...', bid); | |
| try { | |
| var sr = await fetch('/api/settings/get', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({token: currentToken}) }).catch(function() { return {}; }); | |
| var settings = {}; | |
| if (sr.ok) { var r = await sr.json(); if (r.settings) settings = r.settings; } | |
| var resp = await fetch('/api/chat', { | |
| method: 'POST', headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify({ token: currentToken, message: msg, history: aiChatHistory, model: settings.aiModel, openrouter_key: settings.openrouterKey, nvidia_key: settings.nvidiaKey, system_prompt: settings.aiPrompt }) | |
| }); | |
| if (!resp.ok) throw new Error('HTTP ' + resp.status); | |
| var bubble = document.getElementById(bid); | |
| var reader = resp.body.getReader(), dec = new TextDecoder(), full = ''; | |
| while (true) { | |
| var d = await reader.read(); if (d.done) break; | |
| full += dec.decode(d.value, { stream: true }); | |
| bubble.innerHTML = DOMPurify.sanitize(marked.parse(full)); | |
| var h = document.getElementById('ai-chat-history'); | |
| h.scrollTop = h.scrollHeight; | |
| } | |
| aiChatHistory.push({role: 'user', content: msg}); | |
| aiChatHistory.push({role: 'assistant', content: full}); | |
| } catch (e) { | |
| var b = document.getElementById(bid); | |
| if (b) b.innerHTML = '<span style="color:var(--error)">Error: ' + e.message + '</span>'; | |
| } | |
| } | |
| function appendChat(role, content, bid) { | |
| var h = document.getElementById('ai-chat-history'); | |
| var w = document.createElement('div'); | |
| w.className = 'chat-msg ' + role; | |
| var av = role === 'user' | |
| ? '<div class="chat-avatar" style="background:var(--text-3);color:#000" aria-hidden="true"><i class="fa-solid fa-user"></i></div>' | |
| : '<div class="chat-avatar" style="background:var(--accent);color:var(--accent-text)" aria-hidden="true"><i class="fa-solid fa-robot"></i></div>'; | |
| w.innerHTML = av + '<div class="chat-bubble markdown-body" ' + (bid ? 'id="' + bid + '"' : '') + '>' + content + '</div>'; | |
| h.appendChild(w); | |
| h.scrollTop = h.scrollHeight; | |
| } | |
| function handleChatInput(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendChatMessage(); } } | |
| async function loadAIChatHistory() { | |
| if (!currentToken) return; | |
| try { | |
| var res = await fetch('/api/chat/history', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({token: currentToken}) }); | |
| var data = await res.json(); | |
| if (data.history) { | |
| aiChatHistory = data.history; | |
| var h = document.getElementById('ai-chat-history'); | |
| h.innerHTML = ''; | |
| if (data.history.length === 0) { | |
| appendChat('system', 'Hello! How can I help you build today?'); | |
| } else { | |
| data.history.forEach(function(m) { | |
| if (m.role === 'system') return; | |
| var html = m.role === 'assistant' ? DOMPurify.sanitize(marked.parse(m.content)) : m.content; | |
| appendChat(m.role, html); | |
| }); | |
| } | |
| } | |
| } catch (e) { console.error(e); } | |
| } | |
| async function clearAIChatHistory() { | |
| if (!confirm('Clear chat history?')) return; | |
| try { | |
| var res = await fetch('/api/chat/history/clear', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({token: currentToken}) }); | |
| var data = await res.json(); | |
| if (data.success) { | |
| aiChatHistory = []; | |
| document.getElementById('ai-chat-history').innerHTML = ''; | |
| appendChat('system', 'Chat cleared. How can I help?'); | |
| } | |
| } catch (e) { showToast('Failed to clear', 'error'); } | |
| } | |
| // GitHub | |
| window.addEventListener('message', function(e) { if (e.data === 'github_oauth_success') { showToast('GitHub connected!', 'success'); checkGithubStatus(); } }); | |
| function connectGithub() { | |
| if (!currentToken) return; | |
| var w = 500, h = 600, l = (screen.width - w) / 2, t = (screen.height - h) / 2; | |
| window.open('/api/github/login?user_token=' + currentToken, 'gh', 'width=' + w + ',height=' + h + ',top=' + t + ',left=' + l); | |
| } | |
| async function checkGithubStatus() { | |
| if (!currentToken) return; | |
| try { | |
| var res = await fetch('/api/github/status', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({token: currentToken}) }); | |
| var data = await res.json(); | |
| var s = document.getElementById('github-auth-status'), r = document.getElementById('github-repos-section'); | |
| if (data.connected) { s.innerHTML = '<span style="color:var(--success);font-weight:600"><i class="fa-solid fa-check-circle"></i> Connected</span>'; r.style.display = 'block'; fetchGithubRepos(); } | |
| else { s.innerHTML = '<button class="ed-btn primary" onclick="connectGithub()">Connect</button>'; r.style.display = 'none'; } | |
| } catch (e) { console.error(e); } | |
| } | |
| async function fetchGithubRepos() { | |
| var list = document.getElementById('github-repos-list'); | |
| list.innerHTML = '<p style="color:var(--text-3)"><i class="fa-solid fa-spinner fa-spin"></i> Loading...</p>'; | |
| try { | |
| var res = await fetch('/api/github/repos', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({token: currentToken}) }); | |
| var data = await res.json(); | |
| if (data.error) { list.innerHTML = '<p style="color:var(--error)">' + data.error + '</p>'; return; } | |
| list.innerHTML = ''; | |
| if (!data.repos.length) { list.innerHTML = '<p style="color:var(--text-3)">No repos found.</p>'; return; } | |
| data.repos.forEach(function(repo) { | |
| var priv = repo.private ? ' <span style="font-size:10px;background:rgba(255,255,255,0.06);padding:2px 6px;border-radius:4px"><i class="fa-solid fa-lock"></i></span>' : ''; | |
| var card = document.createElement('div'); | |
| card.style.cssText = 'background:rgba(255,255,255,0.03);border:1px solid var(--border);padding:var(--sp-3);border-radius:var(--r-sm)'; | |
| card.innerHTML = '<div style="display:flex;justify-content:space-between;align-items:start;gap:8px;margin-bottom:8px"><h4 style="font-size:13px;color:var(--accent);font-weight:600;word-break:break-all">' + repo.name + priv + '</h4></div><p style="font-size:12px;color:var(--text-3);margin:0 0 8px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden">' + (repo.description || 'No description') + '</p><button class="ed-btn" style="width:100%;justify-content:center" onclick="importOauthRepo(\'' + repo.clone_url + '\')"><i class="fa-solid fa-download"></i> Import</button>'; | |
| list.appendChild(card); | |
| }); | |
| } catch (e) { list.innerHTML = '<p style="color:var(--error)">Error loading repos</p>'; } | |
| } | |
| async function importOauthRepo(url) { | |
| document.getElementById('import-loading-overlay').style.display = 'flex'; | |
| try { | |
| var res = await fetch('/api/github/import_oauth', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({token: currentToken, github_url: url}) }); | |
| var data = await res.json(); | |
| if (data.success) { showToast(data.message, 'success'); if (typeof loadFiles !== 'undefined') loadFiles(); } | |
| else showToast('Failed: ' + (data.error || 'Unknown'), 'error'); | |
| } catch (e) { showToast('Network error', 'error'); } | |
| finally { document.getElementById('import-loading-overlay').style.display = 'none'; } | |
| } | |