Spaces:
Paused
Paused
| <html lang="da"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>WidgeTDC | Omni-Link v5.1</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;700;900&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet"> | |
| <style> | |
| :root { | |
| --bg-void: #020203; | |
| --panel-glass: rgba(10, 15, 30, 0.85); | |
| --border-color: rgba(255, 255, 255, 0.08); | |
| --neon-cyan: #22d3ee; | |
| --neon-purple: #c084fc; | |
| --neon-green: #4ade80; | |
| --neon-amber: #fbbf24; | |
| --font-display: 'Orbitron', sans-serif; | |
| --font-code: 'JetBrains Mono', monospace; | |
| } | |
| body { background-color: var(--bg-void); color: #e2e8f0; font-family: var(--font-code); overflow: hidden; } | |
| .font-display { font-family: var(--font-display); } | |
| #neural-canvas { position: absolute; inset: 0; z-index: 0; } | |
| .ui-layer { position: absolute; inset: 0; pointer-events: none; z-index: 10; } | |
| .pointer-auto { pointer-events: auto; } | |
| /* Professional Glassmorphism */ | |
| .glass-panel { | |
| background: var(--panel-glass); | |
| border: 1px solid var(--border-color); | |
| backdrop-filter: blur(16px); | |
| box-shadow: 0 4px 24px rgba(0,0,0,0.4); | |
| } | |
| .status-dot { width: 6px; height: 6px; border-radius: 50%; display: inline-block; margin-right: 8px; position: relative; } | |
| .status-dot::after { content: ''; position: absolute; inset: -2px; border-radius: 50%; opacity: 0.4; background: inherit; animation: ping 2s cubic-bezier(0, 0, 0.2, 1) infinite; } | |
| .status-dot.online { background-color: var(--neon-green); box-shadow: 0 0 8px var(--neon-green); } | |
| .status-dot.offline { background-color: var(--neon-amber); } | |
| .inspector-panel { transform: translateX(110%); transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1); } | |
| .inspector-panel.active { transform: translateX(0); } | |
| .chat-msg { margin-bottom: 6px; padding: 6px 10px; border-left: 2px solid transparent; font-size: 11px; line-height: 1.4; background: rgba(255,255,255,0.02); } | |
| .chat-msg.sys { border-color: var(--neon-cyan); } | |
| .chat-msg.api { border-color: var(--neon-purple); } | |
| @keyframes slideIn { from { opacity: 0; transform: translateX(-10px); } to { opacity: 1; transform: translateX(0); } } | |
| @keyframes ping { 75%, 100% { transform: scale(2); opacity: 0; } } | |
| .omni-input { background: transparent; border: none; outline: none; width: 100%; color: white; font-family: var(--font-code); font-size: 13px; } | |
| /* Blueprint Grid */ | |
| .grid-bg { | |
| position: fixed; inset: 0; pointer-events: none; z-index: -1; opacity: 0.08; | |
| background-image: | |
| linear-gradient(rgba(34, 211, 238, 0.3) 1px, transparent 1px), | |
| linear-gradient(90deg, rgba(34, 211, 238, 0.3) 1px, transparent 1px); | |
| background-size: 40px 40px; | |
| } | |
| /* Scrollbar */ | |
| ::-webkit-scrollbar { width: 4px; } | |
| ::-webkit-scrollbar-track { background: transparent; } | |
| ::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; } | |
| ::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.2); } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="grid-bg"></div> | |
| <canvas id="neural-canvas"></canvas> | |
| <div class="ui-layer p-6 flex flex-col h-screen"> | |
| <!-- HEADER --> | |
| <header class="flex justify-between items-start pointer-auto mb-6 shrink-0"> | |
| <div> | |
| <h1 class="font-display text-2xl text-white tracking-[0.2em] uppercase bg-clip-text text-transparent bg-gradient-to-r from-white to-gray-400"> | |
| Omni-Link <span class="text-cyan-500 text-xs align-top font-bold tracking-normal ml-1">PRO</span> | |
| </h1> | |
| <div class="flex items-center gap-2 mt-2 text-[10px] text-gray-400 font-medium uppercase tracking-widest"> | |
| <span id="conn-dot" class="status-dot offline"></span> | |
| <span id="conn-status">System Offline</span> | |
| </div> | |
| </div> | |
| <div class="glass-panel px-4 py-3 rounded-md text-[11px] font-mono text-gray-400 flex gap-6 border-t border-white/10"> | |
| <div class="flex flex-col"> | |
| <span class="text-[9px] text-gray-600 tracking-wider">NODES</span> | |
| <span id="stat-nodes" class="text-white font-bold text-sm">0</span> | |
| </div> | |
| <div class="w-px bg-white/10"></div> | |
| <div class="flex flex-col"> | |
| <span class="text-[9px] text-gray-600 tracking-wider">LATENCY</span> | |
| <span id="stat-latency" class="text-cyan-400 font-bold text-sm">--</span> | |
| </div> | |
| </div> | |
| </header> | |
| <div class="flex-1 flex gap-6 overflow-hidden relative"> | |
| <!-- STREAM --> | |
| <div class="w-72 glass-panel rounded-md flex flex-col pointer-auto z-20 border-l-2 border-l-cyan-500/30"> | |
| <div class="p-3 border-b border-white/5 bg-black/20 flex justify-between items-center"> | |
| <span class="font-display text-[10px] text-cyan-500 tracking-widest">ACTIVITY LOG</span> | |
| <div class="w-1.5 h-1.5 rounded-full bg-cyan-500 animate-pulse"></div> | |
| </div> | |
| <div id="chat-stream" class="flex-1 overflow-y-auto p-3"></div> | |
| </div> | |
| <!-- INSPECTOR --> | |
| <div id="inspector" class="inspector-panel absolute right-0 top-0 bottom-0 w-96 glass-panel rounded-l-xl flex flex-col pointer-auto z-30 border-r-0 border-y border-l border-white/10 shadow-2xl"> | |
| <div class="p-5 border-b border-white/5 bg-gradient-to-r from-black/40 to-transparent flex justify-between items-center"> | |
| <h2 class="font-display text-xs text-gray-400 tracking-[0.2em]">NODE TELEMETRY</h2> | |
| <button onclick="closeInspector()" class="text-gray-500 hover:text-white transition-colors text-lg leading-none">×</button> | |
| </div> | |
| <div class="p-6 text-center relative group"> | |
| <div class="absolute inset-0 bg-cyan-500/5 blur-xl opacity-0 group-hover:opacity-100 transition-opacity duration-700"></div> | |
| <span id="inspect-label" class="text-xl font-display text-white relative z-10 block tracking-wide">Select Node</span> | |
| <span id="inspect-type" class="text-[10px] text-cyan-400 font-mono border border-cyan-900/50 bg-cyan-950/20 px-2 py-1 rounded-sm mt-3 inline-block relative z-10 tracking-wider uppercase">SYSTEM</span> | |
| <button id="btn-nudge" class="w-full mt-6 py-3 rounded-sm bg-white/5 hover:bg-white/10 text-cyan-200 text-xs font-bold border border-white/10 transition-all relative z-10 flex items-center justify-center gap-2 group-hover:border-cyan-500/30"> | |
| <span class="w-2 h-2 bg-cyan-400 rounded-full shadow-[0_0_8px_#22d3ee]"></span> | |
| TRIGGER REFLEX | |
| </button> | |
| </div> | |
| <div class="flex-1 overflow-y-auto p-0 font-mono bg-black/20"> | |
| <div id="inspect-json" class="p-4"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- OMNI BAR --> | |
| <footer class="mt-6 mx-auto w-full max-w-2xl pointer-auto z-40 shrink-0"> | |
| <div class="glass-panel rounded-full pl-5 pr-2 py-2 flex items-center gap-4 shadow-[0_0_30px_rgba(0,0,0,0.5)] border border-white/10 ring-1 ring-white/5"> | |
| <span class="text-cyan-500 font-bold animate-pulse text-lg">›</span> | |
| <input id="omni-input" type="text" class="omni-input" placeholder="Enter command to inject thought... (e.g., /inject Memory Protocol_7)"> | |
| <div class="text-[9px] text-gray-600 font-display tracking-wider px-3 py-1 border border-white/5 rounded-full">READY</div> | |
| </div> | |
| </footer> | |
| </div> | |
| <script> | |
| const API_URL = '/api/cortex'; | |
| // Professional Palette matching Project Theme | |
| const config = { | |
| colors: { | |
| bg: '#020203', | |
| node: '#3b82f6', // Blue | |
| agent: '#c084fc', // Purple | |
| ingestion: '#f59e0b', // Amber | |
| memory: '#10b981', // Emerald | |
| system: '#64748b', // Slate | |
| link: 'rgba(59, 130, 246, 0.15)' | |
| }, | |
| physics: { friction: 0.94, spring: 0.03, repulsion: 350 } | |
| }; | |
| let state = { | |
| nodes: [], | |
| links: [], | |
| camera: { x: 0, y: 0, zoom: 1 }, | |
| selection: null, | |
| drag: { active: false, node: null }, | |
| particles: [] // For data flow animation | |
| }; | |
| // --- CORE LOGIC --- | |
| async function fetchGraph() { | |
| try { | |
| const start = Date.now(); | |
| const res = await fetch(`${API_URL}/graph`); | |
| const data = await res.json(); | |
| if(data.success) { | |
| const latency = Date.now() - start; | |
| document.getElementById('stat-latency').innerText = latency + 'ms'; | |
| document.getElementById('conn-status').innerText = 'SYSTEM ONLINE'; | |
| document.getElementById('conn-dot').className = 'status-dot online'; | |
| // Merge existing nodes to preserve position/velocity if possible | |
| const newNodes = data.graph.nodes.map(n => { | |
| const existing = state.nodes.find(en => en.id === n.id); | |
| return existing ? { ...n, x: existing.x, y: existing.y, vx: existing.vx, vy: existing.vy } : { ...n, vx:0, vy:0 }; | |
| }); | |
| state.nodes = newNodes; | |
| state.links = data.graph.links.map(l => ({ | |
| source: state.nodes.find(n => n.id === l.source), | |
| target: state.nodes.find(n => n.id === l.target) | |
| })).filter(l => l.source && l.target); | |
| updateStats(); | |
| // Spawn particles for new data | |
| if (state.particles.length < 20) spawnParticles(); | |
| } | |
| } catch (err) { | |
| document.getElementById('conn-status').innerText = 'DISCONNECTED'; | |
| document.getElementById('conn-dot').className = 'status-dot offline'; | |
| } | |
| } | |
| async function sendNudge(node) { | |
| log('OPERATOR', `Manual override: [${node.label}]`, 'sys'); | |
| try { | |
| const res = await fetch(`${API_URL}/nudge`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ nodeId: node.id }) | |
| }); | |
| const data = await res.json(); | |
| if(data.success) { | |
| log('CORTEX', `Response: ${data.reaction}`, 'api'); | |
| // Visual pulse | |
| node.pulse = 1.5; | |
| } | |
| } catch(err) { log('ERROR', 'Command rejected.', 'sys'); } | |
| } | |
| async function injectNode(label) { | |
| try { | |
| await fetch(`${API_URL}/inject`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ label, type: 'Memory', data: { origin: 'Omni-Link Console' } }) | |
| }); | |
| fetchGraph(); | |
| } catch(err) { log('ERROR', 'Injection rejected.', 'sys'); } | |
| } | |
| function spawnParticles() { | |
| state.links.forEach(l => { | |
| if (Math.random() > 0.7) { | |
| state.particles.push({ | |
| link: l, | |
| pos: 0, | |
| speed: 0.02 + Math.random() * 0.03, | |
| color: l.target.type === 'Agent' ? config.colors.agent : config.colors.node | |
| }); | |
| } | |
| }); | |
| } | |
| // --- VISUALS --- | |
| const canvas = document.getElementById('neural-canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| function resize() { | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| } | |
| window.addEventListener('resize', resize); | |
| resize(); | |
| function render() { | |
| try { | |
| // Physics | |
| state.nodes.forEach(n1 => { | |
| state.nodes.forEach(n2 => { | |
| if (n1 === n2) return; | |
| const dx = n1.x - n2.x, dy = n1.y - n2.y; | |
| const dist = Math.sqrt(dx*dx + dy*dy) || 1; | |
| if (dist < 400) { | |
| const f = config.physics.repulsion / (dist*dist); | |
| n1.vx += (dx/dist)*f; n1.vy += (dy/dist)*f; | |
| } | |
| }); | |
| n1.vx *= config.physics.friction; n1.vy *= config.physics.friction; | |
| n1.x += n1.vx; n1.y += n1.vy; | |
| // Dampen pulse | |
| if (n1.pulse > 1) n1.pulse -= 0.02; | |
| }); | |
| state.links.forEach(l => { | |
| const dx = l.target.x - l.source.x, dy = l.target.y - l.source.y; | |
| const dist = Math.sqrt(dx*dx + dy*dy); | |
| const f = (dist - 180) * config.physics.spring; | |
| const fx = (dx/dist)*f, fy = (dy/dist)*f; | |
| l.source.vx += fx; l.source.vy += fy; | |
| l.target.vx -= fx; l.target.vy -= fy; | |
| }); | |
| // Clear & Center | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| const cx = canvas.width/2 + state.camera.x; | |
| const cy = canvas.height/2 + state.camera.y; | |
| // Draw Links | |
| ctx.lineWidth = 1; | |
| state.links.forEach(l => { | |
| ctx.beginPath(); | |
| ctx.moveTo(cx + l.source.x, cy + l.source.y); | |
| ctx.lineTo(cx + l.target.x, cy + l.target.y); | |
| const grad = ctx.createLinearGradient(cx + l.source.x, cy + l.source.y, cx + l.target.x, cy + l.target.y); | |
| grad.addColorStop(0, 'rgba(59, 130, 246, 0.05)'); | |
| grad.addColorStop(0.5, 'rgba(59, 130, 246, 0.2)'); | |
| grad.addColorStop(1, 'rgba(59, 130, 246, 0.05)'); | |
| ctx.strokeStyle = grad; | |
| ctx.stroke(); | |
| }); | |
| // Draw Particles | |
| for (let i = state.particles.length - 1; i >= 0; i--) { | |
| let p = state.particles[i]; | |
| p.pos += p.speed; | |
| if (p.pos >= 1) { state.particles.splice(i, 1); continue; } | |
| const x = cx + p.link.source.x + (p.link.target.x - p.link.source.x) * p.pos; | |
| const y = cy + p.link.source.y + (p.link.target.y - p.link.source.y) * p.pos; | |
| ctx.fillStyle = p.color; | |
| ctx.shadowBlur = 6; | |
| ctx.shadowColor = p.color; | |
| ctx.beginPath(); | |
| ctx.arc(x, y, 2, 0, Math.PI*2); | |
| ctx.fill(); | |
| ctx.shadowBlur = 0; | |
| } | |
| if (Math.random() > 0.9) spawnParticles(); | |
| // Draw Nodes | |
| state.nodes.forEach(n => { | |
| const x = cx + n.x, y = cy + n.y; | |
| let r = (n.radius || 25) * (n.pulse || 1); | |
| if (r <= 0) r = 10; // Safety | |
| // Types & Colors | |
| let baseColor = config.colors.node; | |
| let icon = "⚫"; | |
| if (n.type === 'Ingestion') { baseColor = config.colors.ingestion; icon = n.label.includes('Outlook') ? "📧" : "📂"; } | |
| else if (n.type === 'Memory') { baseColor = config.colors.memory; icon = "🧠"; } | |
| else if (n.type === 'Agent') { baseColor = config.colors.agent; icon = "🤖"; } | |
| else if (n.type === 'System') { baseColor = config.colors.system; icon = "⚙️"; } | |
| // Selection Glow | |
| if (state.selection === n) { | |
| ctx.beginPath(); | |
| ctx.arc(x, y, r + 4, 0, Math.PI*2); | |
| ctx.fillStyle = 'rgba(255, 255, 255, 0.1)'; | |
| ctx.fill(); | |
| ctx.strokeStyle = 'rgba(255, 255, 255, 0.4)'; | |
| ctx.lineWidth = 1; | |
| ctx.stroke(); | |
| } | |
| // Sphere Body | |
| ctx.beginPath(); | |
| ctx.arc(x, y, r, 0, Math.PI*2); | |
| try { | |
| const grad = ctx.createRadialGradient(x - r/3, y - r/3, r/10, x, y, r); | |
| grad.addColorStop(0, adjustColor(baseColor, 140)); | |
| grad.addColorStop(0.4, baseColor); | |
| grad.addColorStop(1, adjustColor(baseColor, -60)); | |
| ctx.fillStyle = grad; | |
| } catch (e) { | |
| // Fallback if gradient fails (e.g. invalid color) | |
| ctx.fillStyle = baseColor; | |
| } | |
| ctx.fill(); | |
| // Inner Rim Light | |
| ctx.beginPath(); | |
| ctx.arc(x, y, r * 0.85, 0, Math.PI*2); | |
| ctx.strokeStyle = 'rgba(255,255,255,0.1)'; | |
| ctx.lineWidth = 1; | |
| ctx.stroke(); | |
| // Icon | |
| ctx.font = `${r*0.5}px sans-serif`; | |
| ctx.textAlign = "center"; | |
| ctx.textBaseline = "middle"; | |
| ctx.fillStyle = "rgba(255,255,255,0.95)"; | |
| ctx.fillText(icon, x, y); | |
| // Label | |
| ctx.font = "10px 'JetBrains Mono'"; | |
| ctx.fillStyle = "rgba(255,255,255,0.6)"; | |
| ctx.fillText(n.label, x, y + r + 14); | |
| }); | |
| } catch (err) { | |
| console.error("Render Error:", err); | |
| } | |
| requestAnimationFrame(render); | |
| } | |
| // Utility: Adjust Hex Color Brightness (Robust) | |
| function adjustColor(color, amount) { | |
| if (!color || typeof color !== 'string' || !color.startsWith('#')) return color || '#888888'; | |
| const num = parseInt(color.replace('#',''), 16); | |
| if (isNaN(num)) return color; | |
| let r = (num >> 16) + amount; | |
| let g = ((num >> 8) & 0x00FF) + amount; | |
| let b = (num & 0x0000FF) + amount; | |
| r = Math.max(Math.min(255, r), 0); | |
| g = Math.max(Math.min(255, g), 0); | |
| b = Math.max(Math.min(255, b), 0); | |
| return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); | |
| } | |
| // --- INPUTS --- | |
| canvas.addEventListener('mousedown', e => { | |
| const mx = e.clientX - canvas.width/2 - state.camera.x; | |
| const my = e.clientY - canvas.height/2 - state.camera.y; | |
| const hit = state.nodes.find(n => Math.hypot(n.x - mx, n.y - my) < 30); | |
| if (hit) { state.selection = hit; state.drag = { active: true, node: hit }; openInspector(hit); } | |
| else { closeInspector(); } | |
| }); | |
| canvas.addEventListener('mousemove', e => { | |
| if(state.drag.active) { | |
| state.drag.node.x = e.clientX - canvas.width/2 - state.camera.x; | |
| state.drag.node.y = e.clientY - canvas.height/2 - state.camera.y; | |
| state.drag.node.vx = 0; state.drag.node.vy = 0; | |
| } | |
| }); | |
| canvas.addEventListener('mouseup', () => state.drag.active = false); | |
| document.getElementById('omni-input').addEventListener('keydown', e => { | |
| if(e.key === 'Enter') { | |
| const cmd = e.target.value.trim(); | |
| e.target.value = ""; | |
| if(cmd.startsWith('/inject')) injectNode(cmd.split(' ')[1] || 'Node'); | |
| } | |
| }); | |
| function openInspector(node) { | |
| document.getElementById('inspector').classList.add('active'); | |
| document.getElementById('inspect-label').innerText = node.label; | |
| document.getElementById('inspect-type').innerText = node.type; | |
| const container = document.getElementById('inspect-json'); | |
| container.innerHTML = ''; | |
| if (node.data) { | |
| const table = document.createElement('table'); | |
| table.className = "w-full text-left text-[11px] border-collapse"; | |
| for (const [key, value] of Object.entries(node.data)) { | |
| const row = table.insertRow(); | |
| row.className = "border-b border-white/5"; | |
| const cellKey = row.insertCell(); | |
| cellKey.className = "py-2 pr-2 text-gray-500 font-medium w-1/2 align-top"; | |
| cellKey.textContent = key.toUpperCase(); | |
| const cellVal = row.insertCell(); | |
| cellVal.className = "py-2 text-gray-200 font-mono text-right break-all"; | |
| if (Array.isArray(value)) { | |
| cellVal.innerHTML = value.map(v => `<span class="inline-block bg-white/10 px-1.5 py-0.5 rounded text-[9px] mr-1 mb-1">${v}</span>`).join(''); | |
| } else { | |
| cellVal.textContent = value; | |
| } | |
| } | |
| container.appendChild(table); | |
| } else { | |
| container.innerHTML = '<div class="py-8 text-gray-600 text-xs italic text-center">Awaiting telemetry stream...</div>'; | |
| } | |
| const btn = document.getElementById('btn-nudge'); | |
| const newBtn = btn.cloneNode(true); | |
| btn.parentNode.replaceChild(newBtn, btn); // Clean event listeners | |
| newBtn.addEventListener('click', () => sendNudge(node)); | |
| } | |
| function closeInspector() { | |
| document.getElementById('inspector').classList.remove('active'); | |
| state.selection = null; | |
| } | |
| function log(src, msg, type) { | |
| const div = document.createElement('div'); | |
| div.className = `chat-msg ${type}`; | |
| div.innerHTML = `<span class="opacity-50 font-bold mr-2 tracking-wider text-[9px]">${src}</span> ${msg}`; | |
| div.style.animation = 'slideIn 0.3s cubic-bezier(0,0,0.2,1)'; | |
| const container = document.getElementById('chat-stream'); | |
| container.appendChild(div); | |
| container.scrollTop = container.scrollHeight; | |
| } | |
| function updateStats() { | |
| document.getElementById('stat-nodes').innerText = state.nodes.length; | |
| } | |
| // Init | |
| fetchGraph(); | |
| render(); | |
| log('SYSTEM', 'Omni-Link v5.1 Initialized', 'sys'); | |
| </script> | |
| </body> | |
| </html> |