| |
|
|
| const API = 'http://' + location.hostname + ':9090'; |
|
|
| document.addEventListener('DOMContentLoaded', () => { |
| const tabs = document.querySelectorAll('.sidebar-tab'); |
| const panels = document.querySelectorAll('.panel'); |
| const splash = document.getElementById('splash'); |
|
|
| |
| function psychoAnimate(outEl, writeFn, data, onDone) { |
| const manifesto = data.manifesto || []; |
| const variants = data.variants || []; |
| const project = data.project || 'project'; |
| const totalLoc = data.total_loc || 0; |
| const files = data.files || 0; |
|
|
| let delay = 0; |
| manifesto.forEach(line => { |
| setTimeout(() => writeFn(' ' + line, 'psycho-manifesto'), delay); |
| delay += 800; |
| }); |
| setTimeout(() => { |
| writeFn('', ''); |
| writeFn('╔══════════════════════════════════════╗', 'psycho-banner'); |
| writeFn('║ P S Y C H O M O D E ║', 'psycho-banner'); |
| writeFn('║ E N G A G E D ║', 'psycho-banner'); |
| writeFn('╚══════════════════════════════════════╝', 'psycho-banner'); |
| writeFn('', ''); |
| writeFn(' Building: ' + project, 'psycho-banner'); |
| }, delay + 400); |
| delay += 1200; |
| variants.forEach((v, i) => { |
| setTimeout(() => writeFn(' ' + v, 'forge'), delay + i * 300); |
| }); |
| delay += variants.length * 300 + 400; |
| setTimeout(() => { |
| writeFn('', ''); |
| writeFn('╔══════════════════════════════════════╗', 'psycho-summary'); |
| writeFn('║ 6 VARIANTS | ' + String(totalLoc).padEnd(5) + ' LOC | ' + String(files).padEnd(4) + ' FILES ║', 'psycho-summary'); |
| writeFn('╚══════════════════════════════════════╝', 'psycho-summary'); |
| writeFn('', ''); |
| writeFn(' You wanted psycho. You got it.', 'psycho-banner'); |
| if (onDone) onDone(); |
| }, delay + 400); |
| } |
|
|
| |
| const PSYCHO_ORGAN_SEQ = ['Antennae', 'Spine', 'Hippocampus', 'Cerebellum', 'Prefrontal', 'Dream']; |
| let psychoClickSeq = []; |
| let psychoClickTimer = null; |
|
|
| document.addEventListener('click', (e) => { |
| const bot = e.target.closest('.nanobot'); |
| if (!bot) return; |
| const organ = bot.dataset && bot.dataset.organ; |
| if (!organ) return; |
| const expected = PSYCHO_ORGAN_SEQ[psychoClickSeq.length]; |
| if (organ === expected) { |
| psychoClickSeq.push(organ); |
| if (psychoClickTimer) clearTimeout(psychoClickTimer); |
| psychoClickTimer = setTimeout(() => { psychoClickSeq = []; }, 3000); |
| if (psychoClickSeq.length === PSYCHO_ORGAN_SEQ.length) { |
| psychoClickSeq = []; |
| const project = editor ? (editor.value.split('\n')[0] || 'project').trim().slice(0, 40) : 'project'; |
| api('/api/psycho/unlock', { project: project }).then(data => { |
| if (data.psycho && termOutput && termInput) { |
| psychoAnimate(termOutput, (text, cls) => { |
| const d = document.createElement('div'); |
| d.className = 'terminal-line ' + cls; |
| d.textContent = text; |
| termOutput.appendChild(d); |
| termOutput.scrollTop = termOutput.scrollHeight; |
| }, data, () => { |
| document.querySelector('[data-panel="deep"]').click(); |
| }); |
| } |
| }); |
| } |
| } else { |
| psychoClickSeq = []; |
| } |
| }); |
|
|
| |
| async function api(path, body) { |
| try { |
| const r = await fetch(API + path, { |
| method: body ? 'POST' : 'GET', |
| headers: { 'Content-Type': 'application/json' }, |
| body: body ? JSON.stringify(body) : null, |
| }); |
| return await r.json(); |
| } catch (e) { |
| return { output: ['[API] Server offline — run server.py'], error: true }; |
| } |
| } |
|
|
| |
| setTimeout(() => { if (splash) splash.style.opacity = '0'; }, 3200); |
| setTimeout(() => { if (splash) splash.style.display = 'none'; }, 4000); |
|
|
| |
| const onboarding = document.getElementById('onboarding'); |
| const onboardingBtn = document.getElementById('onboarding-start-btn'); |
| const onboardingCheck = document.getElementById('onboarding-dismiss-check'); |
| if (onboarding && !localStorage.getItem('fsi_onboarded')) { |
| setTimeout(() => { onboarding.classList.add('visible'); }, 4200); |
| } |
| if (onboardingBtn && onboarding) { |
| onboardingBtn.addEventListener('click', () => { |
| if (onboardingCheck && onboardingCheck.checked) { |
| localStorage.setItem('fsi_onboarded', 'true'); |
| } |
| onboarding.classList.remove('visible'); |
| }); |
| } |
|
|
| |
| const starContainer = document.querySelector('.splash-stars'); |
| if (starContainer) { |
| for (let i = 0; i < 60; i++) { |
| const s = document.createElement('div'); |
| s.className = 'splash-particle'; |
| const size = 1 + Math.random() * 2; |
| s.style.width = size + 'px'; |
| s.style.height = size + 'px'; |
| s.style.left = Math.random() * 100 + '%'; |
| s.style.top = Math.random() * 30 + '%'; |
| s.style.animationDelay = Math.random() * 2 + 's'; |
| s.style.animationDuration = (1.5 + Math.random() * 2) + 's'; |
| s.style.background = Math.random() > 0.7 ? '#ff6b35' : '#00d4ff'; |
| starContainer.appendChild(s); |
| } |
| } |
|
|
| |
| for (let i = 0; i < 40; i++) { |
| const b = document.createElement('div'); |
| b.className = 'bubble'; |
| const size = 2 + Math.random() * 6; |
| b.style.width = size + 'px'; |
| b.style.height = size + 'px'; |
| b.style.left = Math.random() * 100 + '%'; |
| b.style.bottom = '-10px'; |
| b.style.animationDuration = (6 + Math.random() * 8) + 's'; |
| b.style.animationDelay = Math.random() * 8 + 's'; |
| b.style.opacity = 0.1 + Math.random() * 0.2; |
| document.body.appendChild(b); |
| } |
|
|
| |
| tabs.forEach(tab => { |
| tab.addEventListener('click', () => { |
| const target = tab.dataset.panel; |
| tabs.forEach(t => t.classList.remove('active')); |
| panels.forEach(p => p.classList.remove('active')); |
| tab.classList.add('active'); |
| const panel = document.getElementById('panel-' + target); |
| if (panel) panel.classList.add('active'); |
| if (target === 'vault') loadVaultTree(); |
| }); |
| }); |
|
|
| |
| const editor = document.getElementById('code-editor'); |
| const gutter = document.getElementById('editor-gutter'); |
| if (editor && gutter) { |
| const updateGutter = () => { |
| const lines = editor.value.split('\n'); |
| gutter.innerHTML = lines.map((_, i) => '<div>' + (i + 1) + '</div>').join(''); |
| }; |
| editor.addEventListener('input', updateGutter); |
| editor.addEventListener('scroll', () => { gutter.scrollTop = editor.scrollTop; }); |
| updateGutter(); |
| } |
|
|
| |
| const termInput = document.getElementById('terminal-input'); |
| const termOutput = document.getElementById('terminal-output'); |
| const termHistory = []; |
| let termIdx = -1; |
|
|
| if (termInput && termOutput) { |
| const write = (text, cls = '') => { |
| const d = document.createElement('div'); |
| d.className = 'terminal-line ' + cls; |
| d.textContent = text; |
| termOutput.appendChild(d); |
| termOutput.scrollTop = termOutput.scrollHeight; |
| }; |
|
|
| write('╔══════════════════════════════════════╗', 'info'); |
| write('║ FSI_FELON Terminal v4.0 ║', 'info'); |
| write('║ Connecting to backend... ║', 'info'); |
| write('╚══════════════════════════════════════╝', 'info'); |
| write(''); |
|
|
| termInput.addEventListener('keydown', (e) => { |
| if (e.key === 'Enter') { |
| const cmd = termInput.value.trim(); |
| if (!cmd) return; |
| write('fsi> ' + cmd); |
| termHistory.push(cmd); |
| termIdx = termHistory.length; |
| termInput.value = ''; |
| termInput.disabled = true; |
|
|
| if (cmd.toLowerCase() === 'clear') { |
| termOutput.innerHTML = ''; |
| termInput.disabled = false; |
| termInput.focus(); |
| return; |
| } |
|
|
| api('/api/command', { cmd: cmd }).then(data => { |
| const lines = data.output || ['[API] No response']; |
| |
| if (data.psycho) { |
| psychoAnimate(termOutput, write, data, () => { |
| termInput.disabled = false; |
| termInput.focus(); |
| }); |
| } else { |
| lines.forEach(line => { |
| if (line === '__CLEAR__') { |
| termOutput.innerHTML = ''; |
| return; |
| } |
| let cls = 'info'; |
| if (line.includes('CHIMERA') || line.includes('ROUTED')) cls = 'chimera'; |
| else if (line.includes('Error') || line.includes('Unknown') || line.includes('error')) cls = 'error'; |
| else if (line.includes('✓') || line.includes('WOOGITY')) cls = 'prompt'; |
| else if (line.includes('FORGE') || line.includes('BUILD')) cls = 'forge'; |
| else if (line.includes('KELI') || line.includes('WHITE') || line.includes('RABBIT')) cls = 'rabbit'; |
| else if (line.includes('DREAM')) cls = 'dream'; |
| else if (line.includes('TEST')) cls = 'test'; |
| write(' ' + line, cls); |
| }); |
| termInput.disabled = false; |
| termInput.focus(); |
| } |
| }); |
| } else if (e.key === 'ArrowUp') { |
| if (termIdx > 0) { termIdx--; termInput.value = termHistory[termIdx]; } |
| e.preventDefault(); |
| } else if (e.key === 'ArrowDown') { |
| if (termIdx < termHistory.length - 1) { termIdx++; termInput.value = termHistory[termIdx]; } |
| else { termIdx = termHistory.length; termInput.value = ''; } |
| e.preventDefault(); |
| } |
| }); |
| } |
|
|
| |
| const forgeBtn = document.getElementById('forge-btn'); |
| if (forgeBtn) { |
| forgeBtn.addEventListener('click', async () => { |
| const code = editor ? editor.value : ''; |
| const lines = code.split('\n').filter(l => l.trim() && !l.trim().startsWith('#')); |
| const desc = (lines[0] || 'project').replace(/^def\s+|^class\s+|^#\s*/g, '').trim().slice(0, 60) || 'project'; |
| forgeBtn.textContent = '⚡ Forging...'; |
| forgeBtn.disabled = true; |
|
|
| const forgePanel = document.getElementById('panel-forge'); |
| document.querySelector('[data-panel="forge"]').click(); |
|
|
| const forgeOutput = document.getElementById('forge-output'); |
| const card = document.createElement('div'); |
| card.className = 'forge-card'; |
| card.innerHTML = '<div class="forge-card-header"><span style="font-weight:400;font-size:14px;color:#c8d6e5">' + desc + '</span><span class="forge-badge badge-pending">⟳ RUN</span></div><div style="font-size:12px;color:rgba(200,214,229,0.45)">Building...</div>'; |
| forgeOutput.prepend(card); |
|
|
| const data = await api('/api/build', { desc: desc, code: code }); |
| const files = data.files || '—'; |
| const loc = data.loc || '—'; |
| const time = data.time || '—'; |
| card.innerHTML = '<div class="forge-card-header"><span style="font-weight:400;font-size:14px;color:#c8d6e5">' + desc + '</span><span class="forge-badge badge-success">✓ PASS</span></div><div style="font-size:12px;color:rgba(200,214,229,0.45)">' + files + ' files · ' + loc + ' LOC · ' + time + '</div>'; |
|
|
| forgeBtn.textContent = '⚡ Forge It'; |
| forgeBtn.disabled = false; |
| }); |
| } |
|
|
| |
| const statusRows = document.querySelectorAll('.sidebar-status .value'); |
| async function pollStatus() { |
| const data = await api('/api/stats'); |
| if (data.training && statusRows.length >= 3) { |
| const t = data.training; |
| statusRows[0].textContent = 'step ' + t.step + ' · loss ' + t.loss; |
| statusRows[1].textContent = (data.chimera ? data.chimera.organs : '—') + ' organs · ' + (data.chimera ? (data.chimera.accuracy * 100).toFixed(0) + '% route' : '—'); |
| const meshEl = document.getElementById('sidebar-mesh-peers'); |
| if (meshEl) { |
| const id = await api('/api/mesh/identity'); |
| meshEl.textContent = (id.peers || 0) + ' peers'; |
| } |
| statusRows[3].textContent = (t.tokens_per_sec || '—') + ' tok/s'; |
| } |
| } |
| setInterval(pollStatus, 2000); |
|
|
| |
| const vaultTree = document.getElementById('vault-tree'); |
| const uploadZone = document.querySelector('.vault-upload-zone'); |
|
|
| async function loadVaultTree() { |
| if (!vaultTree) return; |
| const data = await api('/api/files?path=' + encodeURIComponent('/tmp/fsi_felon/workspace')); |
| if (data.error || !data.files) { |
| vaultTree.innerHTML = '<div class="tree-item"><span style="color:#ff6b35;">⚠ ' + (data.error || 'Could not load workspace') + '</span></div>'; |
| return; |
| } |
| let html = '<div class="tree-item folder" data-path="/tmp/fsi_felon/workspace"><span class="icon">📂</span><span>workspace</span></div>'; |
| const renderItems = (items, depth) => { |
| for (const item of items) { |
| if (item.type === 'folder') { |
| html += '<div class="tree-item folder" style="padding-left:' + (16 + depth * 20) + 'px" data-path="' + item.path + '"><span class="icon">' + (item.children ? '📁' : '📂') + '</span><span>' + item.name + '</span></div>'; |
| if (item.children) renderItems(item.children, depth + 1); |
| } else { |
| const size = item.size ? ' <span style="color:rgba(200,214,229,0.35);font-size:10px;">' + (item.size > 1024 ? (item.size / 1024).toFixed(1) + 'KB' : item.size + 'B') + '</span>' : ''; |
| html += '<div class="tree-item file" style="padding-left:' + (16 + depth * 20) + 'px" data-path="' + item.path + '"><span class="icon">📄</span><span>' + item.name + size + '</span></div>'; |
| } |
| } |
| }; |
| renderItems(data.files, 0); |
| vaultTree.innerHTML = html; |
| } |
|
|
| |
| document.addEventListener('click', (e) => { |
| const item = e.target.closest('.tree-item.file'); |
| if (!item) return; |
| const path = item.dataset.path; |
| if (!path) return; |
| api('/api/download?path=' + encodeURIComponent(path)).then(data => { |
| if (data.content && editor) { |
| editor.value = data.content; |
| if (gutter) { |
| const lines = data.content.split('\n'); |
| gutter.innerHTML = lines.map((_, i) => '<div>' + (i + 1) + '</div>').join(''); |
| } |
| document.querySelector('[data-panel="workshop"]').click(); |
| } |
| }); |
| }); |
|
|
| |
| if (uploadZone) { |
| const input = document.createElement('input'); |
| input.type = 'file'; |
| input.style.display = 'none'; |
| document.body.appendChild(input); |
|
|
| uploadZone.addEventListener('click', () => input.click()); |
| uploadZone.addEventListener('dragover', (e) => { e.preventDefault(); uploadZone.style.borderColor = '#00d4ff'; }); |
| uploadZone.addEventListener('dragleave', () => { uploadZone.style.borderColor = ''; }); |
| uploadZone.addEventListener('drop', (e) => { |
| e.preventDefault(); |
| uploadZone.style.borderColor = ''; |
| const files = e.dataTransfer.files; |
| if (files.length) uploadFile(files[0]); |
| }); |
| input.addEventListener('change', () => { |
| if (input.files.length) uploadFile(input.files[0]); |
| input.value = ''; |
| }); |
|
|
| async function uploadFile(file) { |
| const reader = new FileReader(); |
| reader.onload = async (e) => { |
| const data = await api('/api/upload', { |
| filename: file.name, |
| content: e.target.result, |
| path: '/tmp/fsi_felon/workspace', |
| }); |
| if (data.success) loadVaultTree(); |
| }; |
| reader.readAsText(file); |
| } |
| } |
|
|
| |
| const canvas = document.getElementById('dream-canvas'); |
| if (canvas) { |
| const count = 25; |
| const bots = []; |
| const colors = ['#00d4ff', '#ff6b35', '#ff3366', '#00ff88', '#ffffff']; |
| const organNames = ['Antennae', 'Spine', 'Hippocampus', 'Cerebellum', 'Prefrontal', 'Dream']; |
|
|
| for (let i = 0; i < count; i++) { |
| const bot = document.createElement('div'); |
| bot.className = 'nanobot'; |
| const size = 4 + Math.random() * 4; |
| const color = colors[Math.floor(Math.random() * colors.length)]; |
| const x = Math.random() * 92 + 4; |
| const y = Math.random() * 88 + 4; |
| bot.style.width = size + 'px'; |
| bot.style.height = size + 'px'; |
| bot.style.left = x + '%'; |
| bot.style.top = y + '%'; |
| bot.style.background = color; |
| bot.style.boxShadow = '0 0 ' + (4 + Math.random() * 8) + 'px ' + color + '60'; |
| bot.style.animation = 'nanobotFloat ' + (3 + Math.random() * 4) + 's ease-in-out infinite'; |
| bot.style.animationDelay = Math.random() * 3 + 's'; |
| const organName = organNames[Math.floor(Math.random() * organNames.length)]; |
| bot.dataset.organ = organName; |
| canvas.appendChild(bot); |
| bots.push({ |
| el: bot, x: x, y: y, |
| vx: (Math.random() - 0.5) * 0.15, |
| vy: (Math.random() - 0.5) * 0.15, |
| color: color, size: size, |
| organ: organName, |
| }); |
| } |
|
|
| const trails = []; |
| for (let i = 0; i < 15; i++) { |
| const t = document.createElement('div'); |
| t.className = 'nanobot-trail'; |
| const ts = 1 + Math.random() * 2; |
| t.style.width = ts + 'px'; |
| t.style.height = ts + 'px'; |
| t.style.background = '#00d4ff'; |
| t.style.animationDelay = Math.random() * 3 + 's'; |
| canvas.appendChild(t); |
| trails.push(t); |
| } |
|
|
| const lineLayer = document.createElement('div'); |
| lineLayer.style.position = 'absolute'; |
| lineLayer.style.inset = '0'; |
| lineLayer.style.pointerEvents = 'none'; |
| lineLayer.style.zIndex = '0'; |
| canvas.appendChild(lineLayer); |
|
|
| const lines = []; |
| for (let i = 0; i < 40; i++) { |
| const l = document.createElement('div'); |
| l.className = 'nanobot-line'; |
| l.style.background = 'rgba(0,212,255,0.15)'; |
| l.style.height = '1px'; |
| lineLayer.appendChild(l); |
| lines.push(l); |
| } |
|
|
| const statusEl = canvas.querySelector('.dream-status'); |
| const updateStatus = () => { |
| if (!statusEl) return; |
| api('/api/stats').then(data => { |
| const c = data.chimera || {}; |
| const t = data.training || {}; |
| statusEl.innerHTML = '<span>Swarm: ' + bots.length + ' active</span><span>Organs: ' + (c.organs || '—') + ' · ' + (c.accuracy ? (c.accuracy * 100).toFixed(0) + '%' : '—') + '</span><span>Loss: ' + (t.loss || '—') + '</span>'; |
| }); |
| }; |
|
|
| let frame = 0; |
| const animate = () => { |
| frame++; |
| bots.forEach(b => { |
| b.x += b.vx + Math.sin(frame * 0.01 + b.y) * 0.02; |
| b.y += b.vy + Math.cos(frame * 0.01 + b.x) * 0.02; |
| if (b.x < 0 || b.x > 94) { b.vx *= -1; b.x = Math.max(0, Math.min(94, b.x)); } |
| if (b.y < 0 || b.y > 94) { b.vy *= -1; b.y = Math.max(0, Math.min(94, b.y)); } |
| b.el.style.left = b.x + '%'; |
| b.el.style.top = b.y + '%'; |
| }); |
|
|
| const connections = []; |
| for (let i = 0; i < bots.length; i++) { |
| for (let j = i + 1; j < bots.length; j++) { |
| const dx = bots[i].x - bots[j].x; |
| const dy = bots[i].y - bots[j].y; |
| const dist = Math.sqrt(dx * dx + dy * dy); |
| if (dist < 12) connections.push({ i, j, dist }); |
| } |
| } |
|
|
| const w = canvas.offsetWidth; |
| const h = canvas.offsetHeight; |
| lines.forEach((l, idx) => { |
| if (idx < connections.length) { |
| const c = connections[idx]; |
| const b1 = bots[c.i]; |
| const b2 = bots[c.j]; |
| const x1 = b1.x / 100 * w; |
| const y1 = b1.y / 100 * h; |
| const x2 = b2.x / 100 * w; |
| const y2 = b2.y / 100 * h; |
| const len = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2); |
| const angle = Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI; |
| l.style.left = x1 + 'px'; |
| l.style.top = y1 + 'px'; |
| l.style.width = len + 'px'; |
| l.style.transform = 'rotate(' + angle + 'deg)'; |
| l.style.opacity = Math.max(0, (1 - c.dist / 12) * 0.6); |
| } else { |
| l.style.opacity = '0'; |
| } |
| }); |
|
|
| trails.forEach((t, idx) => { |
| const bot = bots[idx % bots.length]; |
| t.style.left = (bot.x + (Math.random() - 0.5) * 5) + '%'; |
| t.style.top = (bot.y + (Math.random() - 0.5) * 5) + '%'; |
| }); |
|
|
| if (frame % 30 === 0) updateStatus(); |
| requestAnimationFrame(animate); |
| }; |
| animate(); |
| } |
|
|
| |
| const meshNodeId = document.getElementById('mesh-node-id'); |
| const meshPubkey = document.getElementById('mesh-pubkey'); |
| const meshUptime = document.getElementById('mesh-uptime'); |
| const meshPeers = document.getElementById('mesh-peers'); |
| const meshSharedCount = document.getElementById('mesh-shared-count'); |
| const meshFeed = document.getElementById('mesh-feed'); |
| const meshInsights = document.getElementById('mesh-insights-content'); |
| const meshPublishBtn = document.getElementById('mesh-publish-btn'); |
| const meshToggleGlobal = document.getElementById('mesh-toggle-global'); |
| const meshToggleBench = document.getElementById('mesh-toggle-benchmarks'); |
| const meshToggleDreams = document.getElementById('mesh-toggle-dreams'); |
| const meshToggleStats = document.getElementById('mesh-toggle-stats'); |
|
|
| async function pollMesh() { |
| const identity = await api('/api/mesh/identity'); |
| if (identity.node_id && meshNodeId) { |
| meshNodeId.textContent = identity.node_id; |
| meshPubkey.textContent = identity.pubkey_fingerprint || '—'; |
| const up = identity.uptime || 0; |
| const h = Math.floor(up / 3600); |
| const m = Math.floor((up % 3600) / 60); |
| const s = up % 60; |
| meshUptime.textContent = h + 'h ' + m + 'm ' + s + 's'; |
| meshPeers.textContent = identity.peers || 0; |
| } |
| const discover = await api('/api/mesh/discover'); |
| if (discover.items && meshFeed) { |
| if (discover.items.length === 0) { |
| meshFeed.innerHTML = '<div class="mesh-feed-empty">🕸 No peers yet — shared content from other nodes appears here when they connect.</div>'; |
| } else { |
| meshFeed.innerHTML = discover.items.map(item => { |
| const ts = item.timestamp ? new Date(item.timestamp * 1000).toLocaleString() : ''; |
| return '<div class="mesh-feed-item">' + |
| '<div class="mesh-feed-item-header">' + |
| '<span class="mesh-feed-item-name">' + escapeHtml(item.name) + '</span>' + |
| '<span class="mesh-feed-item-node">' + (item.node_id ? item.node_id.slice(0, 8) + '..' : '—') + '</span>' + |
| '</div>' + |
| '<div class="mesh-feed-item-meta"><span>' + escapeHtml(item.type || 'project') + '</span><span>' + ts + '</span></div>' + |
| '</div>'; |
| }).join(''); |
| } |
| } |
| const shareData = await api('/api/mesh/discover?local=true'); |
| if (shareData.items && meshSharedCount) { |
| meshSharedCount.textContent = shareData.items.length + ' items shared'; |
| } |
| } |
|
|
| function escapeHtml(s) { |
| const d = document.createElement('div'); |
| d.textContent = s; |
| return d.innerHTML; |
| } |
|
|
| |
| if (meshPublishBtn && editor) { |
| meshPublishBtn.addEventListener('click', async () => { |
| const code = editor.value; |
| const desc = (code.split('\n').filter(l => l.trim() && !l.trim().startsWith('#'))[0] || 'untitled').replace(/^def\s+|^class\s+|^#\s*/g, '').trim().slice(0, 60) || 'untitled'; |
| meshPublishBtn.textContent = '📤 Publishing...'; |
| meshPublishBtn.disabled = true; |
| await api('/api/mesh/share', { |
| name: desc, |
| content: code, |
| type: 'project', |
| share_benchmarks: meshToggleBench ? meshToggleBench.checked : true, |
| share_dreams: meshToggleDreams ? meshToggleDreams.checked : false, |
| share_stats: meshToggleStats ? meshToggleStats.checked : false, |
| }); |
| meshPublishBtn.textContent = '✓ Published'; |
| setTimeout(() => { |
| meshPublishBtn.textContent = '📤 Publish Current Project'; |
| meshPublishBtn.disabled = false; |
| }, 2000); |
| pollMesh(); |
| }); |
| } |
|
|
| |
| let insightTimer = null; |
| if (editor && meshInsights) { |
| editor.addEventListener('input', () => { |
| clearTimeout(insightTimer); |
| insightTimer = setTimeout(async () => { |
| const code = editor.value.trim(); |
| if (code.length < 30) return; |
| const result = await api('/api/mesh/related', { text: code.slice(0, 500) }); |
| if (result.related && result.related.length > 0) { |
| meshInsights.innerHTML = result.related.map(r => |
| '<div class="mesh-insight-card">' + |
| '<div class="mesh-insight-card-title">💭 ' + escapeHtml(r.name) + '</div>' + |
| '<div class="mesh-insight-card-desc">' + escapeHtml(r.description || 'Related public project') + ' · node ' + (r.node_id ? r.node_id.slice(0, 8) + '..' : '—') + '</div>' + |
| '</div>' |
| ).join(''); |
| } else { |
| meshInsights.innerHTML = '<div class="mesh-feed-empty">💭 Working on something? Chimera will search the mesh for related public work.</div>'; |
| } |
| }, 1500); |
| }); |
| } |
|
|
| setInterval(pollMesh, 5000); |
|
|
| |
| const deviceSdk = document.getElementById('device-sdk'); |
| const deviceAdb = document.getElementById('device-adb'); |
| const deviceGradle = document.getElementById('device-gradle'); |
| const deviceDevices = document.getElementById('device-devices'); |
| const deviceOutput = document.getElementById('device-output'); |
| const deviceApkStatus = document.getElementById('device-apk-status'); |
| const deviceVerifyOutput = document.getElementById('device-verify-output'); |
| const deviceGenBtn = document.getElementById('device-gen-btn'); |
| const deviceBuildBtn = document.getElementById('device-build-btn'); |
| const deviceVerifyBtn = document.getElementById('device-verify-btn'); |
| const deviceInstallBtn = document.getElementById('device-install-btn'); |
|
|
| let currentApk = null; |
|
|
| function deviceWrite(text, cls = '') { |
| if (!deviceOutput) return; |
| if (deviceOutput.querySelector('.device-output-empty')) { |
| deviceOutput.innerHTML = ''; |
| } |
| const d = document.createElement('div'); |
| d.className = 'device-output-line ' + cls; |
| d.textContent = text; |
| deviceOutput.appendChild(d); |
| deviceOutput.scrollTop = deviceOutput.scrollHeight; |
| } |
|
|
| async function pollDevice() { |
| const env = await api('/api/android/env'); |
| if (deviceSdk) { |
| deviceSdk.textContent = env.sdk ? 'installed' : 'not found'; |
| deviceSdk.className = 'device-value' + (env.sdk ? ' good' : ' bad'); |
| } |
| if (deviceAdb) { |
| deviceAdb.textContent = env.adb ? 'available' : 'not found'; |
| deviceAdb.className = 'device-value' + (env.adb ? ' good' : ' bad'); |
| } |
| if (deviceGradle) { |
| deviceGradle.textContent = env.gradle || 'not found'; |
| deviceGradle.className = 'device-value' + (env.gradle ? ' good' : ' bad'); |
| } |
| if (deviceDevices) { |
| deviceDevices.textContent = (env.devices || 0) + ' connected'; |
| deviceDevices.className = 'device-value' + (env.devices > 0 ? ' good' : ''); |
| } |
| } |
|
|
| if (deviceGenBtn) { |
| deviceGenBtn.addEventListener('click', async () => { |
| const name = document.getElementById('device-app-name').value || 'FSIApp'; |
| const pkg = document.getElementById('device-package').value || 'com.fsi.app'; |
| const activity = document.getElementById('device-activity').value || 'MainActivity'; |
| deviceWrite('Generating project: ' + name + '...', 'info'); |
| deviceGenBtn.disabled = true; |
| const result = await api('/api/android/generate', { name, package: pkg, activity }); |
| if (result.success) { |
| deviceWrite('Project generated: ' + result.path, 'success'); |
| } else { |
| deviceWrite(result.error || 'Generation failed', 'error'); |
| } |
| deviceGenBtn.disabled = false; |
| }); |
| } |
|
|
| if (deviceBuildBtn) { |
| deviceBuildBtn.addEventListener('click', async () => { |
| const name = document.getElementById('device-app-name').value || 'FSIApp'; |
| const pkg = document.getElementById('device-package').value || 'com.fsi.app'; |
| const activity = document.getElementById('device-activity').value || 'MainActivity'; |
| deviceWrite('Building APK: ' + name + '...', 'info'); |
| deviceWrite('First build takes 3-5 min (downloads deps)...', 'info'); |
| deviceBuildBtn.disabled = true; |
| deviceBuildBtn.textContent = '⚙️ Building...'; |
| const result = await api('/api/android/build', { name, package: pkg, activity }); |
| if (result.success && result.apk) { |
| currentApk = result.apk; |
| deviceWrite('APK built: ' + result.apk, 'success'); |
| deviceWrite('Size: ' + (result.size_mb || '?') + ' MB', 'info'); |
| deviceApkStatus.textContent = 'APK ready: ' + result.apk; |
| deviceVerifyBtn.disabled = false; |
| deviceInstallBtn.disabled = false; |
| } else { |
| deviceWrite(result.error || 'Build failed', 'error'); |
| } |
| deviceBuildBtn.disabled = false; |
| deviceBuildBtn.textContent = '🔥 Build APK'; |
| }); |
| } |
|
|
| if (deviceVerifyBtn) { |
| deviceVerifyBtn.addEventListener('click', async () => { |
| deviceVerifyBtn.disabled = true; |
| deviceVerifyOutput.textContent = 'Verifying...'; |
| const result = await api('/api/android/verify'); |
| if (result.valid) { |
| deviceVerifyOutput.innerHTML = |
| '✓ Valid APK\n' + |
| 'Package: ' + (result.package || '—') + '\n' + |
| 'Version: ' + (result.version_name || '—') + ' (' + (result.version_code || '—') + ')\n' + |
| 'Min SDK: ' + (result.min_sdk || '—') + '\n' + |
| 'Target SDK: ' + (result.target_sdk || '—') + '\n' + |
| 'Activity: ' + (result.activity || '—') + '\n' + |
| 'Size: ' + (result.size ? (result.size / 1024 / 1024).toFixed(2) + ' MB' : '—'); |
| } else { |
| deviceVerifyOutput.textContent = '✗ ' + (result.error || 'Verification failed'); |
| } |
| deviceVerifyBtn.disabled = false; |
| }); |
| } |
|
|
| if (deviceInstallBtn) { |
| deviceInstallBtn.addEventListener('click', async () => { |
| deviceInstallBtn.disabled = true; |
| deviceInstallBtn.textContent = '⏳ Installing...'; |
| const result = await api('/api/android/install'); |
| if (result.success) { |
| deviceVerifyOutput.textContent = '✓ Installed successfully'; |
| } else { |
| deviceVerifyOutput.textContent = '✗ ' + (result.error || 'Install failed'); |
| } |
| deviceInstallBtn.disabled = false; |
| deviceInstallBtn.textContent = '📤 Install'; |
| }); |
| } |
|
|
| setInterval(pollDevice, 10000); |
|
|
| |
| loadVaultTree(); |
| }); |
|
|