devportal2 / static /js /app.js
Akay Borana
Fix input: force focus on touch/click for terminal + editor
c79e79a
Raw
History Blame Contribute Delete
11.8 kB
// 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'; }
}