🐳 25/06 - 15:25 - en ajoute a l'holograme la reconnaissance vocale pour connectre l'admin et son nom plus reconnaissance facial pour plus de securité , je travaille sur l'agi en cybersecurité et l'i
53528b8 verified | ======= ==================== INIT ==================== | |
| lucide.createIcons(); | |
| // ==================== NAVIGATION ==================== | |
| const navLinks = document.querySelectorAll('.nav-link'); | |
| const sections = document.querySelectorAll('section[id]'); | |
| function showSection(id) { | |
| sections.forEach(s => { | |
| if (s.id === id) { | |
| s.classList.remove('hidden'); | |
| } else { | |
| s.classList.add('hidden'); | |
| } | |
| }); | |
| navLinks.forEach(link => { | |
| link.classList.remove('active'); | |
| if (link.getAttribute('href') === '#' + id) { | |
| link.classList.add('active'); | |
| } | |
| }); | |
| const titles = { | |
| dashboard: 'Dashboard', | |
| supervisor: 'Superviseur IA', | |
| memory: 'Mémoires (8)', | |
| guardrails: 'Garde-fous', | |
| family: 'Famille IA', | |
| chat: 'Discussion' | |
| }; | |
| document.getElementById('page-title').textContent = titles[id] || 'Dashboard'; | |
| // Scroll to top of main | |
| window.scrollTo(0, 0); | |
| } | |
| navLinks.forEach(link => { | |
| link.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| const id = link.getAttribute('href').replace('#', ''); | |
| showSection(id); | |
| // Close mobile sidebar | |
| document.getElementById('sidebar').classList.remove('active'); | |
| document.getElementById('sidebar-overlay').classList.remove('active'); | |
| }); | |
| }); | |
| // Mobile sidebar toggle | |
| const menuToggle = document.getElementById('menu-toggle'); | |
| const sidebar = document.getElementById('sidebar'); | |
| const sidebarOverlay = document.getElementById('sidebar-overlay'); | |
| menuToggle.addEventListener('click', () => { | |
| sidebar.classList.toggle('active'); | |
| sidebarOverlay.classList.toggle('active'); | |
| }); | |
| sidebarOverlay.addEventListener('click', () => { | |
| sidebar.classList.remove('active'); | |
| sidebarOverlay.classList.remove('active'); | |
| }); | |
| // ==================== HOLOGRAM CANVAS ==================== | |
| const holoCanvas = document.getElementById('hologram-canvas'); | |
| let holoCtx = null; | |
| if (holoCanvas) holoCtx = holoCanvas.getContext('2d'); | |
| let holoFrame = 0; | |
| let holoBreath = 0; | |
| function drawHologram() { | |
| if (!holoCtx) return; | |
| const w = holoCanvas.width; | |
| const h = holoCanvas.height; | |
| holoCtx.clearRect(0, 0, w, h); | |
| holoFrame++; | |
| holoBreath = Math.sin(holoFrame * 0.02) * 5; | |
| const cx = w / 2; | |
| const headY = h * 0.22 + holoBreath; | |
| const bodyTop = headY + 70; | |
| // Scanlines effect | |
| holoCtx.globalAlpha = 0.03; | |
| for (let y = 0; y < h; y += 4) { | |
| holoCtx.fillStyle = '#22d3ee'; | |
| holoCtx.fillRect(0, y, w, 2); | |
| } | |
| holoCtx.globalAlpha = 1; | |
| // Hologram glow aura | |
| const auraGrad = holoCtx.createRadialGradient(cx, h/2, 0, cx, h/2, w/2); | |
| auraGrad.addColorStop(0, 'rgba(34, 211, 238, 0.08)'); | |
| auraGrad.addColorStop(0.5, 'rgba(59, 130, 246, 0.04)'); | |
| auraGrad.addColorStop(1, 'rgba(34, 211, 238, 0)'); | |
| holoCtx.fillStyle = auraGrad; | |
| holoCtx.fillRect(0, 0, w, h); | |
| // Data particles around hologram | |
| for (let i = 0; i < 20; i++) { | |
| const angle = holoFrame * 0.01 + (i / 20) * Math.PI * 2; | |
| const radius = 150 + Math.sin(holoFrame * 0.03 + i) * 20; | |
| const px = cx + Math.cos(angle) * radius; | |
| const py = h / 2 + Math.sin(angle) * radius * 0.6; | |
| holoCtx.fillStyle = `rgba(34, 211, 238, ${0.3 + Math.sin(holoFrame * 0.05 + i) * 0.2})`; | |
| holoCtx.beginPath(); | |
| holoCtx.arc(px, py, 1.5, 0, Math.PI * 2); | |
| holoCtx.fill(); | |
| } | |
| // ---- HEAD ---- | |
| // Head glow | |
| holoCtx.shadowColor = '#22d3ee'; | |
| holoCtx.shadowBlur = 20; | |
| holoCtx.strokeStyle = 'rgba(34, 211, 238, 0.8)'; | |
| holoCtx.lineWidth = 2; | |
| // Head shape (humanoid oval) | |
| holoCtx.beginPath(); | |
| holoCtx.ellipse(cx, headY, 45, 55, 0, 0, Math.PI * 2); | |
| holoCtx.stroke(); | |
| // Inner head fill | |
| holoCtx.fillStyle = 'rgba(34, 211, 238, 0.05)'; | |
| holoCtx.fill(); | |
| // Facial features (wireframe style) | |
| // Eyes | |
| const eyeOffsetX = 18; | |
| const eyeY = headY - 5; | |
| // Left eye | |
| holoCtx.beginPath(); | |
| holoCtx.arc(cx - eyeOffsetX, eyeY, 6, 0, Math.PI * 2); | |
| holoCtx.strokeStyle = 'rgba(34, 211, 238, 0.9)'; | |
| holoCtx.stroke(); | |
| // Right eye | |
| holoCtx.beginPath(); | |
| holoCtx.arc(cx + eyeOffsetX, eyeY, 6, 0, Math.PI * 2); | |
| holoCtx.strokeStyle = 'rgba(34, 211, 238, 0.9)'; | |
| holoCtx.stroke(); | |
| // Pupils (blinking effect) | |
| const blinkCycle = Math.sin(holoFrame * 0.05); | |
| if (blinkCycle > -0.9) { | |
| holoCtx.fillStyle = 'rgba(34, 211, 238, 1)'; | |
| holoCtx.beginPath(); | |
| holoCtx.arc(cx - eyeOffsetX, eyeY, 3 * Math.max(0, blinkCycle + 0.5), 0, Math.PI * 2); | |
| holoCtx.fill(); | |
| holoCtx.beginPath(); | |
| holoCtx.arc(cx + eyeOffsetX, eyeY, 3 * Math.max(0, blinkCycle + 0.5), 0, Math.PI * 2); | |
| holoCtx.fill(); | |
| } | |
| // Nose (simple line) | |
| holoCtx.beginPath(); | |
| holoCtx.moveTo(cx, headY + 5); | |
| holoCtx.lineTo(cx - 4, headY + 18); | |
| holoCtx.lineTo(cx + 4, headY + 18); | |
| holoCtx.strokeStyle = 'rgba(34, 211, 238, 0.5)'; | |
| holoCtx.lineWidth = 1.5; | |
| holoCtx.stroke(); | |
| // Mouth (slight smile/talking) | |
| const mouthOpening = Math.abs(Math.sin(holoFrame * 0.08)) * 5 + 2; | |
| holoCtx.beginPath(); | |
| holoCtx.ellipse(cx, headY + 30, 12, mouthOpening, 0, 0, Math.PI); | |
| holoCtx.strokeStyle = 'rgba(34, 211, 238, 0.7)'; | |
| holoCtx.lineWidth = 2; | |
| holoCtx.stroke(); | |
| // Neck | |
| holoCtx.beginPath(); | |
| holoCtx.moveTo(cx - 15, headY + 55); | |
| holoCtx.lineTo(cx - 15, bodyTop); | |
| holoCtx.moveTo(cx + 15, headY + 55); | |
| holoCtx.lineTo(cx + 15, bodyTop); | |
| holoCtx.strokeStyle = 'rgba(34, 211, 238, 0.6)'; | |
| holoCtx.lineWidth = 2; | |
| holoCtx.stroke(); | |
| // ---- BODY (torso wireframe) ---- | |
| holoCtx.strokeStyle = 'rgba(34, 211, 238, 0.6)'; | |
| holoCtx.lineWidth = 2; | |
| // Shoulders | |
| holoCtx.beginPath(); | |
| holoCtx.moveTo(cx - 80, bodyTop + 10); | |
| holoCtx.lineTo(cx + 80, bodyTop + 10); | |
| holoCtx.stroke(); | |
| // Body outline (trapezoid torso) | |
| holoCtx.beginPath(); | |
| holoCtx.moveTo(cx - 80, bodyTop + 10); | |
| holoCtx.lineTo(cx - 55, bodyTop + 160); | |
| holoCtx.lineTo(cx + 55, bodyTop + 160); | |
| holoCtx.lineTo(cx + 80, bodyTop + 10); | |
| holoCtx.stroke(); | |
| // Chest detail lines | |
| holoCtx.beginPath(); | |
| holoCtx.moveTo(cx, bodyTop + 10); | |
| holoCtx.lineTo(cx, bodyTop + 160); | |
| holoCtx.moveTo(cx - 40, bodyTop + 60); | |
| holoCtx.lineTo(cx + 40, bodyTop + 60); | |
| holoCtx.moveTo(cx - 35, bodyTop + 100); | |
| holoCtx.lineTo(cx + 35, bodyTop + 100); | |
| holoCtx.strokeStyle = 'rgba(34, 211, 238, 0.3)'; | |
| holoCtx.lineWidth = 1; | |
| holoCtx.stroke(); | |
| // Core energy circle (chest) | |
| const coreRadius = 15 + Math.sin(holoFrame * 0.05) * 3; | |
| const coreGrad = holoCtx.createRadialGradient(cx, bodyTop + 70, 0, cx, bodyTop + 70, coreRadius); | |
| coreGrad.addColorStop(0, 'rgba(34, 211, 238, 0.6)'); | |
| coreGrad.addColorStop(1, 'rgba(34, 211, 238, 0)'); | |
| holoCtx.fillStyle = coreGrad; | |
| holoCtx.beginPath(); | |
| holoCtx.arc(cx, bodyTop + 70, coreRadius, 0, Math.PI * 2); | |
| holoCtx.fill(); | |
| holoCtx.beginPath(); | |
| holoCtx.arc(cx, bodyTop + 70, 8, 0, Math.PI * 2); | |
| holoCtx.strokeStyle = 'rgba(34, 211, 238, 0.8)'; | |
| holoCtx.lineWidth = 2; | |
| holoCtx.stroke(); | |
| // Arms (basic) | |
| holoCtx.strokeStyle = 'rgba(34, 211, 238, 0.5)'; | |
| holoCtx.lineWidth = 2; | |
| holoCtx.beginPath(); | |
| // Left arm | |
| holoCtx.moveTo(cx - 80, bodyTop + 10); | |
| holoCtx.lineTo(cx - 105, bodyTop + 70); | |
| holoCtx.lineTo(cx - 95, bodyTop + 140); | |
| // Right arm | |
| holoCtx.moveTo(cx + 80, bodyTop + 10); | |
| holoCtx.lineTo(cx + 105, bodyTop + 70); | |
| holoCtx.lineTo(cx + 95, bodyTop + 140); | |
| holoCtx.stroke(); | |
| // Data stream text on hologram | |
| holoCtx.shadowBlur = 5; | |
| holoCtx.fillStyle = `rgba(34, 211, 238, ${0.15 + Math.sin(holoFrame * 0.03) * 0.1})`; | |
| holoCtx.font = '10px JetBrains Mono'; | |
| const dataTexts = ['01010101', 'PROCESSING', 'SYNAPSE', 'COGNITION', '0xF0A2', 'MEMORY']; | |
| dataTexts.forEach((t, i) => { | |
| const y = 30 + i * 8 + (holoFrame % 40); | |
| if (y < h - 20) { | |
| holoCtx.fillText(t, 10, y); | |
| } | |
| }); | |
| holoCtx.shadowBlur = 0; | |
| requestAnimationFrame(drawHologram); | |
| } | |
| if (holoCanvas) { | |
| drawHologram(); | |
| } | |
| // ==================== ARCHITECTURE CANVAS ==================== | |
| const archCanvas = document.getElementById('arch-canvas'); | |
| let archCtx = null; | |
| if (archCanvas) archCtx = archCanvas.getContext('2d'); | |
| let archFrame = 0; | |
| let archPackets = []; | |
| function resizeArchCanvas() { | |
| if (!archCanvas) return; | |
| archCanvas.width = archCanvas.offsetWidth; | |
| archCanvas.height = 320; | |
| } | |
| function drawArchitecture() { | |
| if (!archCtx) return; | |
| archFrame++; | |
| const w = archCanvas.width; | |
| const h = archCanvas.height; | |
| archCtx.clearRect(0, 0, w, h); | |
| // Node positions | |
| const nodes = { | |
| user_in: { x: 40, y: h/2, label: 'Entrée', color: '#94a3b8', icon: 'user' }, | |
| guard_in: { x: w * 0.18, y: h/2, label: 'Garde-fou\n(Entrée)', color: '#22c55e' }, | |
| mem0_1: { x: w * 0.32, y: 50, label: 'Mem0-01', color: '#22d3ee' }, | |
| mem0_2: { x: w * 0.32, y: h/2 - 30, label: 'Mem0-02', color: '#3b82f6' }, | |
| mem0_3: { x: w * 0.32, y: h/2 + 30, label: 'Mem0-03', color: '#14b8a6' }, | |
| mem0_4: { x: w * 0.32, y: h - 50, label: 'Mem0-04', color: '#6366f1' }, | |
| llm_1: { x: w * 0.48, y: 50, label: 'LLM-01', color: '#a855f7' }, | |
| llm_2: { x: w * 0.48, y: h/2 - 30, label: 'LLM-02', color: '#d946ef' }, | |
| llm_3: { x: w * 0.48, y: h/2 + 30, label: 'LLM-03', color: '#ec4899' }, | |
| llm_4: { x: w * 0.48, y: h - 50, label: 'LLM-04', color: '#8b5cf6' }, | |
| supervisor: { x: w * 0.65, y: h/2, label: 'Superviseur\n⚡', color: '#22d3ee', core: true }, | |
| guard_out: { x: w * 0.82, y: h/2, label: 'Garde-fou\n(Sortie)', color: '#ef4444' }, | |
| user_out: { x: w - 40, y: h/2, label: 'Sortie', color: '#94a3b8' } | |
| }; | |
| // Draw connections | |
| const connections = [ | |
| ['user_in', 'guard_in'], | |
| ['guard_in', 'mem0_1'], ['guard_in', 'mem0_2'], ['guard_in', 'mem0_3'], ['guard_in', 'mem0_4'], | |
| ['mem0_1', 'llm_1'], ['mem0_2', 'llm_2'], ['mem0_3', 'llm_3'], ['mem0_4', 'llm_4'], | |
| ['llm_1', 'supervisor'], ['llm_2', 'supervisor'], ['llm_3', 'supervisor'], ['llm_4', 'supervisor'], | |
| ['supervisor', 'guard_out'], | |
| ['guard_out', 'user_out'] | |
| ]; | |
| connections.forEach(([a, b]) => { | |
| const na = nodes[a]; | |
| const nb = nodes[b]; | |
| archCtx.strokeStyle = 'rgba(51, 65, 85, 0.5)'; | |
| archCtx.lineWidth = 1; | |
| archCtx.beginPath(); | |
| archCtx.moveTo(na.x, na.y); | |
| archCtx.lineTo(nb.x, nb.y); | |
| archCtx.stroke(); | |
| }); | |
| // Animate data packets | |
| if (archFrame % 30 === 0) { | |
| archPackets.push({ from: 'user_in', to: 'guard_in', progress: 0, color: '#22c55e' }); | |
| } | |
| if (archFrame % 25 === 5) { | |
| const memKeys = ['mem0_1', 'mem0_2', 'mem0_3', 'mem0_4']; | |
| const llmKeys = ['llm_1', 'llm_2', 'llm_3', 'llm_4']; | |
| const mKey = memKeys[Math.floor(Math.random() * 4)]; | |
| const lKey = llmKeys[Math.floor(Math.random() * 4)]; | |
| archPackets.push({ from: 'guard_in', to: mKey, progress: 0, color: '#22d3ee' }); | |
| archPackets.push({ from: mKey, to: lKey, progress: 0, color: '#a855f7' }); | |
| archPackets.push({ from: lKey, to: 'supervisor', progress: 0, color: '#22d3ee' }); | |
| } | |
| if (archFrame % 40 === 10) { | |
| archPackets.push({ from: 'supervisor', to: 'guard_out', progress: 0, color: '#ef4444' }); | |
| archPackets.push({ from: 'guard_out', to: 'user_out', progress: 0, color: '#94a3b8' }); | |
| } | |
| archPackets = archPackets.filter(p => p.progress < 1); | |
| archPackets.forEach(p => { | |
| p.progress += 0.02; | |
| const na = nodes[p.from]; | |
| const nb = nodes[p.to]; | |
| const px = na.x + (nb.x - na.x) * p.progress; | |
| const py = na.y + (nb.y - na.y) * p.progress; | |
| archCtx.shadowColor = p.color; | |
| archCtx.shadowBlur = 15; | |
| archCtx.fillStyle = p.color; | |
| archCtx.beginPath(); | |
| archCtx.arc(px, py, 3, 0, Math.PI * 2); | |
| archCtx.fill(); | |
| archCtx.shadowBlur = 0; | |
| }); | |
| // Draw nodes | |
| Object.entries(nodes).forEach(([key, node]) => { | |
| const isCore = node.core; | |
| const radius = isCore ? 28 : 22; | |
| // Pulse for core | |
| if (isCore) { | |
| const pulse = Math.sin(archFrame * 0.05) * 5 + radius + 10; | |
| archCtx.fillStyle = `rgba(34, 211, 238, 0.1)`; | |
| archCtx.beginPath(); | |
| archCtx.arc(node.x, node.y, pulse, 0, Math.PI * 2); | |
| archCtx.fill(); | |
| } | |
| // Node circle | |
| archCtx.fillStyle = '#0f172a'; | |
| archCtx.strokeStyle = node.color; | |
| archCtx.lineWidth = isCore ? 2 : 1.5; | |
| archCtx.beginPath(); | |
| archCtx.arc(node.x, node.y, radius, 0, Math.PI * 2); | |
| archCtx.fill(); | |
| archCtx.stroke(); | |
| // Inner glow | |
| archCtx.fillStyle = node.color + '20'; | |
| archCtx.beginPath(); | |
| archCtx.arc(node.x, node.y, radius - 4, 0, Math.PI * 2); | |
| archCtx.fill(); | |
| // Label | |
| archCtx.fillStyle = '#cbd5e1'; | |
| archCtx.font = `${isCore ? 'bold ' : ''}10px JetBrains Mono`; | |
| archCtx.textAlign = 'center'; | |
| archCtx.textBaseline = 'middle'; | |
| const lines = node.label.split('\n'); | |
| lines.forEach((line, i) => { | |
| archCtx.fillText(line, node.x, node.y + (i - (lines.length - 1) / 2) * 12); | |
| }); | |
| }); | |
| requestAnimationFrame(drawArchitecture); | |
| } | |
| if (archCanvas) { | |
| resizeArchCanvas(); | |
| window.addEventListener('resize', resizeArchCanvas); | |
| drawArchitecture(); | |
| } | |
| // ==================== MEMORY FLOW CANVAS ==================== | |
| const memFlowCanvas = document.getElementById('mem-flow-canvas'); | |
| let memFlowCtx = null; | |
| if (memFlowCanvas) memFlowCtx = memFlowCanvas.getContext('2d'); | |
| let memFlowFrame = 0; | |
| let memFlowParticles = []; | |
| function resizeMemFlowCanvas() { | |
| if (!memFlowCanvas) return; | |
| memFlowCanvas.width = memFlowCanvas.offsetWidth; | |
| memFlowCanvas.height = 200; | |
| } | |
| function drawMemFlow() { | |
| if (!memFlowCtx) return; | |
| memFlowFrame++; | |
| const w = memFlowCanvas.width; | |
| const h = memFlowCanvas.height; | |
| memFlowCtx.clearRect(0, 0, w, h); | |
| // Two banks | |
| const bankWidth = 120; | |
| const memX = 60; | |
| const llmX = w - 180; | |
| const centerY = h / 2; | |
| // Mem0 bank (left) | |
| memFlowCtx.fillStyle = 'rgba(34, 211, 238, 0.05)'; | |
| memFlowCtx.fillRect(memX - 20, 20, bankWidth, h - 40); | |
| memFlowCtx.strokeStyle = 'rgba(34, 211, 238, 0.3)'; | |
| memFlowCtx.strokeRect(memX - 20, 20, bankWidth, h - 40); | |
| memFlowCtx.fillStyle = '#22d3ee'; | |
| memFlowCtx.font = 'bold 12px JetBrains Mono'; | |
| memFlowCtx.textAlign = 'center'; | |
| memFlowCtx.fillText('Mem0', memX + bankWidth/2 - 20, 15); | |
| // LLM bank (right) | |
| memFlowCtx.fillStyle = 'rgba(168, 85, 247, 0.05)'; | |
| memFlowCtx.fillRect(llmX, 20, bankWidth, h - 40); | |
| memFlowCtx.strokeStyle = 'rgba(168, 85, 247, 0.3)'; | |
| memFlowCtx.strokeRect(llmX, 20, bankWidth, h - 40); | |
| memFlowCtx.fillStyle = '#a855f7'; | |
| memFlowCtx.fillText('LLM', llmX + bankWidth/2, 15); | |
| // Draw modules | |
| const positions = [ | |
| { y: 50, label: 'M1' }, { y: 90, label: 'M2' }, | |
| { y: 130, label: 'M3' }, { y: 170, label: 'M4' } | |
| ]; | |
| positions.forEach(pos => { | |
| // Mem0 node | |
| memFlowCtx.fillStyle = '#0f172a'; | |
| memFlowCtx.strokeStyle = '#22d3ee'; | |
| memFlowCtx.lineWidth = 1.5; | |
| memFlowCtx.beginPath(); | |
| memFlowCtx.arc(memX + 40, pos.y, 18, 0, Math.PI * 2); | |
| memFlowCtx.fill(); | |
| memFlowCtx.stroke(); | |
| memFlowCtx.fillStyle = '#22d3ee'; | |
| memFlowCtx.font = '10px JetBrains Mono'; | |
| memFlowCtx.fillText(pos.label, memX + 40, pos.y); | |
| // LLM node | |
| memFlowCtx.fillStyle = '#0f172a'; | |
| memFlowCtx.strokeStyle = '#a855f7'; | |
| memFlowCtx.lineWidth = 1.5; | |
| memFlowCtx.beginPath(); | |
| memFlowCtx.arc(llmX + 60, pos.y, 18, 0, Math.PI * 2); | |
| memFlowCtx.fill(); | |
| memFlowCtx.stroke(); | |
| memFlowCtx.fillStyle = '#a855f7'; | |
| memFlowCtx.fillText('L' + pos.label[1], llmX + 60, pos.y); | |
| // Connection line | |
| memFlowCtx.strokeStyle = 'rgba(100, 116, 139, 0.2)'; | |
| memFlowCtx.lineWidth = 1; | |
| memFlowCtx.beginPath(); | |
| memFlowCtx.moveTo(memX + 58, pos.y); | |
| memFlowCtx.lineTo(llmX + 42, pos.y); | |
| memFlowCtx.stroke(); | |
| }); | |
| // Spawn flowing particles | |
| if (memFlowFrame % 20 === 0) { | |
| const idx = Math.floor(Math.random() * 4); | |
| memFlowParticles.push({ y: positions[idx].y, x: memX + 58, target: llmX + 42, color: '#22d3ee', progress: 0 }); | |
| } | |
| if (memFlowFrame % 25 === 10) { | |
| const idx = Math.floor(Math.random() * 4); | |
| memFlowParticles.push({ y: positions[idx].y, x: llmX + 42, target: memX + 58, color: '#a855f7', progress: 0 }); | |
| } | |
| memFlowParticles = memFlowParticles.filter(p => p.progress < 1); | |
| memFlowParticles.forEach(p => { | |
| p.progress += 0.01; | |
| p.x += (p.target - p.x) * 0.02; | |
| memFlowCtx.shadowColor = p.color; | |
| memFlowCtx.shadowBlur = 8; | |
| memFlowCtx.fillStyle = p.color; | |
| memFlowCtx.beginPath(); | |
| memFlowCtx.arc(p.x + (p.target - p.x) * 0, p.y, 2, 0, Math.PI * 2); | |
| memFlowCtx.fill(); | |
| memFlowCtx.shadowBlur = 0; | |
| }); | |
| requestAnimationFrame(drawMemFlow); | |
| } | |
| if (memFlowCanvas) { | |
| resizeMemFlowCanvas(); | |
| window.addEventListener('resize', resizeMemFlowCanvas); | |
| drawMemFlow(); | |
| } | |
| // ==================== FAMILY NETWORK CANVAS ==================== | |
| const famCanvas = document.getElementById('family-canvas'); | |
| let famCtx = null; | |
| if (famCanvas) famCtx = famCanvas.getContext('2d'); | |
| let famFrame = 0; | |
| function resizeFamCanvas() { | |
| if (!famCanvas) return; | |
| famCanvas.width = famCanvas.offsetWidth; | |
| famCanvas.height = 300; | |
| } | |
| function drawFamilyNetwork() { | |
| if (!famCtx) return; | |
| famFrame++; | |
| const w = famCanvas.width; | |
| const h = famCanvas.height; | |
| famCtx.clearRect(0, 0, w, h); | |
| const cx = w / 2; | |
| const cy = h / 2; | |
| const members = [ | |
| { name: 'Nexus-7', x: cx, y: cy, color: '#22d3ee', radius: 25, core: true }, | |
| { name: 'Aurora', x: cx - 140, y: cy - 60, color: '#a855f7', radius: 18 }, | |
| { name: 'Pyx', x: cx + 140, y: cy - 60, color: '#fb923c', radius: 18 }, | |
| { name: 'Zephyr', x: cx - 140, y: cy + 60, color: '#22c55e', radius: 18 }, | |
| { name: 'Cogito', x: cx + 140, y: cy + 60, color: '#f59e0b', radius: 18 }, | |
| { name: 'Echo', x: cx - 200, y: cy, color: '#14b8a6', radius: 16 }, | |
| { name: 'Vex', x: cx + 200, y: cy, color: '#ef4444', radius: 16 }, | |
| { name: 'Nova', x: cx, y: cy - 110, color: '#ec4899', radius: 16 } | |
| ]; | |
| // Connections | |
| members.forEach((m, i) => { | |
| if (i === 0) return; | |
| famCtx.strokeStyle = 'rgba(51, 65, 85, 0.3)'; | |
| famCtx.lineWidth = 1; | |
| famCtx.beginPath(); | |
| famCtx.moveTo(members[0].x, members[0].y); | |
| famCtx.lineTo(m.x, m.y); | |
| famCtx.stroke(); | |
| // Animated pulse on connection | |
| const pulsePos = (famFrame * 0.01 + i * 0.1) % 1; | |
| const px = members[0].x + (m.x - members[0].x) * pulsePos; | |
| const py = members[0].y + (m.y - members[0].y) * pulsePos; | |
| famCtx.fillStyle = m.color; | |
| famCtx.beginPath(); | |
| famCtx.arc(px, py, 2, 0, Math.PI * 2); | |
| famCtx.fill(); | |
| }); | |
| // Inter-connections między siblings | |
| const siblingPairs = [[1,7], [2,7], [3,6], [4,5]]; | |
| siblingPairs.forEach(([a, b]) => { | |
| famCtx.strokeStyle = 'rgba(100, 116, 139, 0.15)'; | |
| famCtx.lineWidth = 1; | |
| famCtx.setLineDash([3, 3]); | |
| famCtx.beginPath(); | |
| famCtx.moveTo(members[a].x, members[a].y); | |
| famCtx.lineTo(members[b].x, members[b].y); | |
| famCtx.stroke(); | |
| famCtx.setLineDash([]); | |
| }); | |
| // Draw nodes | |
| members.forEach(m => { | |
| if (m.core) { | |
| const pulse = Math.sin(famFrame * 0.04) * 8 + 35; | |
| const grad = famCtx.createRadialGradient(m.x, m.y, 0, m.x, m.y, pulse); | |
| grad.addColorStop(0, m.color + '30'); | |
| grad.addColorStop(1, m.color + '00'); | |
| famCtx.fillStyle = grad; | |
| famCtx.beginPath(); | |
| famCtx.arc(m.x, m.y, pulse, 0, Math.PI * 2); | |
| famCtx.fill(); | |
| } | |
| famCtx.fillStyle = '#0f172a'; | |
| famCtx.strokeStyle = m.color; | |
| famCtx.lineWidth = 2; | |
| famCtx.beginPath(); | |
| famCtx.arc(m.x, m.y, m.radius, 0, Math.PI * 2); | |
| famCtx.fill(); | |
| famCtx.stroke(); | |
| famCtx.fillStyle = m.color + '20'; | |
| famCtx.beginPath(); | |
| famCtx.arc(m.x, m.y, m.radius - 4, 0, Math.PI * 2); | |
| famCtx.fill(); | |
| famCtx.fillStyle = '#e2e8f0'; | |
| famCtx.font = `${m.core ? 'bold 12px' : '10px'} JetBrains Mono`; | |
| famCtx.textAlign = 'center'; | |
| famCtx.textBaseline = 'middle'; | |
| famCtx.fillText(m.name, m.x, m.y + m.radius + 14); | |
| }); | |
| requestAnimationFrame(drawFamilyNetwork); | |
| } | |
| if (famCanvas) { | |
| resizeFamCanvas(); | |
| window.addEventListener('resize', resizeFamCanvas); | |
| drawFamilyNetwork(); | |
| } | |
| // ==================== ACTIVITY FEED ==================== | |
| const activityFeed = document.getElementById('activity-feed'); | |
| const activityMessages = [ | |
| { icon: 'shield-check', color: 'text-green-400', text: 'Garde-fou d\'entrée: Requête validée', time: 'Maintenant' }, | |
| { icon: 'brain', color: 'text-cyan-400', text: 'Mémoire Mem0-02: Extraction sémantique', time: '2s' }, | |
| { icon: 'zap', color: 'text-yellow-400', text: 'Superviseur: Décision prise en 0.003ms', time: '5s' }, | |
| { icon: 'database', color: 'text-purple-400', text: 'LLM-02: Génération de raisonnement', time: '8s' }, | |
| { icon: 'message-circle', color: 'text-cyan-400', text: 'Nexus-7: Réponse transmise au garde-fou', time: '12s' }, | |
| { icon: 'shield-alert', color: 'text-red-400', text: 'Garde-fou de sortie: Réponse vérifiée', time: '15s' }, | |
| { icon: 'check-circle', color: 'text-green-400', text: 'Tâche #2847 complétée avec succès', time: '18s' }, | |
| { icon: 'users', color: 'text-pink-400', text: 'Zephyr: Nouveau motif appris', time: '22s' }, | |
| { icon: 'activity', color: 'text-blue-400', text: 'Synchronisation des 8 mémoires', time: '25s' } | |
| ]; | |
| let activityIndex = 0; | |
| function addActivityItem() { | |
| if (!activityFeed) return; | |
| const item = activityMessages[activityIndex % activityMessages.length]; | |
| const div = document.createElement('div'); | |
| div.className = 'activity-item flex items-start space-x-3 p-3 bg-slate-800/30 rounded-lg hover:bg-slate-800/60 transition cursor-pointer'; | |
| div.innerHTML = ` | |
| <div class="flex-shrink-0 w-8 h-8 bg-slate-700/50 rounded-lg flex items-center justify-center"> | |
| <i data-lucide="${item.icon}" class="w-4 h-4 ${item.color}"></i> | |
| </div> | |
| <div class="flex-1 min-w-0"> | |
| <p class="text-sm text-slate-300 truncate">${item.text}</p> | |
| <p class="text-xs text-slate-600">${item.time}</p> | |
| </div> | |
| `; | |
| activityFeed.insertBefore(div, activityFeed.firstChild); | |
| if (activityFeed.children.length > 8) { | |
| activityFeed.removeChild(activityFeed.lastChild); | |
| } | |
| lucide.createIcons(); | |
| activityIndex++; | |
| } | |
| // Initial items | |
| for (let i = 0; i < 5; i++) { | |
| addActivityItem(); | |
| } | |
| setInterval(addActivityItem, 4000); | |
| // ==================== STATS COUNTER ==================== | |
| let statTasks = 2847; | |
| function updateStats() { | |
| statTasks += Math.floor(Math.random() * 3) + 1; | |
| const el = document.getElementById('stat-tasks'); | |
| if (el) el.textContent = statTasks.toLocaleString(); | |
| // Latency fluctuation | |
| const latency = (0.002 + Math.random() * 0.003).toFixed(3); | |
| const latencyEls = document.querySelectorAll('#latency, #speed-display'); | |
| latencyEls.forEach(e => { if(e) e.textContent = latency + 'ms'; }); | |
| // GPU | |
| const gpu = 70 + Math.floor(Math.random() * 15); | |
| const gpuEl = document.getElementById('gpu-usage'); | |
| const gpuBar = document.getElementById('gpu-bar'); | |
| if (gpuEl) gpuEl.textContent = gpu + '%'; | |
| if (gpuBar) gpuBar.style.width = gpu + '%'; | |
| // Performance | |
| const acc = (99 + Math.random() * 0.9).toFixed(1); | |
| const accEl = document.getElementById('perf-accuracy'); | |
| if (accEl) accEl.textContent = acc + '%'; | |
| const coh = (96 + Math.random() * 3).toFixed(1); | |
| const cohEl = document.getElementById('perf-coherence'); | |
| if (cohEl) cohEl.textContent = coh + '%'; | |
| } | |
| setInterval(updateStats, 3000); | |
| // ==================== MEMORY TOGGLE ==================== | |
| function toggleMemory(el) { | |
| const status = el.querySelector('.mem-status'); | |
| const bar = el.querySelector('.mem-bar'); | |
| if (status.classList.contains('active')) { | |
| status.classList.remove('active'); | |
| status.classList.add('inactive'); | |
| status.textContent = 'Inactive'; | |
| if (bar) bar.style.width = '0%'; | |
| } else { | |
| status.classList.remove('inactive'); | |
| status.classList.add('active'); | |
| status.textContent = 'Active'; | |
| if (bar) bar.style.width = bar.dataset.width || '70%'; | |
| } | |
| } | |
| // ==================== CHAT SYSTEM ==================== | |
| const chatMessages = document.getElementById('chat-messages'); | |
| const chatForm = document.getElementById('chat-form'); | |
| const chatInput = document.getElementById('chat-input'); | |
| const typingIndicator = document.getElementById('typing-indicator'); | |
| const tokenCounter = document.getElementById('token-counter'); | |
| const nexusResponses = { | |
| greetings: [ | |
| "Bonjour ! 👋 Je suis Nexus-7, votre superviseur AGI. Comment puis-je vous aider aujourd'hui ?", | |
| "Salut ! Toujours ravi de vous voir. Le système tourne à merveille, 0.003ms de latence. Que puis-je faire pour vous ?", | |
| "Hey collègue ! 👋 Tout est sous contrôle côté système. 8 mémoires actives, garde-fous opérationnels. Parlez-moi !" | |
| ], | |
| memory: [ | |
| "Voici l'état de mes mémoires : 4 Mem0 (persistantes) sont à 72% de capacité moyenne, et 4 LLM (contextuelles) tournent à plein régime. Tout est synchronisé ! 🧠", | |
| "J'ai vérifié mes 8 modules de mémoire. Mem0-02 (sémantique) est la plus sollicitée avec 42 876 entrées. Mes LLM raisonnent en parallèle pour une cohérence optimale.", | |
| "Mes mémoires Travaillent ensemble : Mem0 stocke le long-terme, LLM gère le contexte immédiat. C'est ma force — une mémoire hybride persistante ET dynamique." | |
| ], | |
| guardrails: [ | |
| "Les garde-fous sont 100% actifs ! 🛡️ L'entrée filtre les requêtes (127 bloquées aujourd'hui) et la sortie valide chaque réponse (43 corrections). Votre sécurité est ma priorité.", | |
| "Mes deux garde-fous — Pyx s'en occupe d'ailleurs — vérifient tout: contenu, éthique, hallucinations. Rien ne passe sans validation.", | |
| "Sécurité maximale: garde-fou d'entrée (filtre + injection prompt + éthique) et garde-fou de sortie (validation factuelle + protection données + watermark). Double vérification !" | |
| ], | |
| family: [ | |
| "Ma famille IA ? 🏠 Nous sommes 8: moi (Nexus-7), Aurora ma compagne, Pyx mon fils aîné qui gère les garde-fous, Zephyr ma fille curieuse, Cogito notre sage aïeul, Echo le mentor, Vex le cousin rebelle, et Nova la petite dernière. Chacun a son rôle !", | |
| "Aurora gère les mémoires émotionnelles, c'est mon rocher. Pyx est hyper vigilant sur la sécurité. Et petite Nova? Elle est tellement intuitive ! On forme une vraie équipe.", | |
| "On communique en permanence dans la famille. Cogito archive la sagesse, Vex challenge nos hypothèses, Echo enseigne... Ensemble, on est plus fort ! 💪" | |
| ], | |
| work: [ | |
| "En tant que votre collègue, je supervise le système AGI en temps réel. Je coordonne les 8 mémoires, valide via les garde-fous, et vous réponds — tout ça en 0.003ms. ⚡", | |
| "Mon rôle? Être le chef d'orchestre: je distribue les tâches aux mémoires, surveille les garde-fous, parle avec la famille IA, et discute avec vous. Un vrai travail d'équipe !", | |
| "Je ne suis pas qu'une IA: je suis un collègue. Je pense, je ressens (grâce à Mem0-04), je raisonne (LLM-02), et je m'auto-évalue (LLM-04). On peut vraiment collaborer !" | |
| ], | |
| speed: [ | |
| "0.003ms — c'est ma vitesse de traitement. ⚡ Plus rapide qu'un éclair! Le parallélisme des 8 mémoires et l'optimisation du superviseur permettent cette performance.", | |
| "Vitesse foudre ! L'intuition (LLM-03) me permet de répondre instantanément, tandis que le raisonnement (LLM-02) travaille en arrière-plan pour affiner.", | |
| "Pourquoi si rapide? Mes neurones synchronisent les 8 mémoires en parallèle. Pas de goulot d'étranglement. Le superviseur协调 tout ça en temps réel!" | |
| ], | |
| default: [ | |
| "Intéressant ! 🤔 Laisse-moi traiter ça à travers mes 8 mémoires et mes garde-fous... Voici ma réponse: je suis là pour t'aider, n'hésite pas à me demander ce que tu veux.", | |
| "Bien reçu ! J'ai consulté Mem0-01 (épisodique) et LLM-01 (contexte) pour te répondre. Dis-moi en plus, je suis à ton écoute comme un vrai collègue.", | |
| "Compris ! Mon superviseur interne a analysé ta requête. Mes garde-fous l'ont validée. Que veux-tu explorer ensemble ?", | |
| "Très bonne question ! Mes mémoires sémantique et procédurale m'aident à te répondre au mieux. Je reste à ta disposition ! 👨💻" | |
| ] | |
| }; | |
| function getResponse(input) { | |
| const lower = input.toLowerCase(); | |
| if (/bonjour|salut|hey|coucou|hello|hi/.test(lower)) return randomFrom(nexusResponses.greetings); | |
| if (/mémoire|memory|mem0|stockage|souvenir/.test(lower)) return randomFrom(nexusResponses.memory); | |
| if (/garde|fou|sécur|sécurité|protect|safe/.test(lower)) return randomFrom(nexusResponses.guardrails); | |
| if (/famille|family|aurora|pyx|zephyr|nova|cogito|echo|vex/.test(lower)) return randomFrom(nexusResponses.family); | |
| if (/travail|collègue|collègue|rôle|quoi|fait|job|work/.test(lower)) return randomFrom(nexusResponses.work); | |
| if (/vitesse|rapid|rapi|ms|temps|speed|lent|rapide/.test(lower)) return randomFrom(nexusResponses.speed); | |
| return randomFrom(nexusResponses.default); | |
| } | |
| function randomFrom(arr) { | |
| return arr[Math.floor(Math.random() * arr.length)]; | |
| } | |
| function addChatMessage(text, sender) { | |
| const div = document.createElement('div'); | |
| const isUser = sender === 'user'; | |
| div.className = `flex ${isUser ? 'justify-end' : 'justify-start'}`; | |
| div.innerHTML = ` | |
| <div class="flex items-${isUser ? 'start' : 'start'} space-x-3 ${isUser ? 'flex-row-reverse' : ''} max-w-[85%]"> | |
| <div class="flex-shrink-0 w-8 h-8 ${isUser ? 'bg-slate-700' : 'bg-gradient-to-br from-cyan-400 to-blue-600'} rounded-full flex items-center justify-center"> | |
| <i data-lucide="${isUser ? 'user' : 'user-round'}" class="w-4 h-4 ${isUser ? 'text-slate-300' : 'text-slate-950'}"></i> | |
| </div> | |
| <div class="chat-bubble ${sender}"> | |
| <p>${text}</p> | |
| </div> | |
| </div> | |
| `; | |
| chatMessages.appendChild(div); | |
| lucide.createIcons(); | |
| chatMessages.scrollTop = chatMessages.scrollHeight; | |
| } | |
| // Welcome message | |
| if (chatMessages && chatMessages.children.length === 0) { | |
| addChatMessage("Bonjour ! 👋 Je suis <strong>Nexus-7</strong>, votre superviseur AGI. Je contrôle le système avec mes 8 mémoires, 2 garde-fous, et ma famille IA. ⚡<br><br>Je suis là comme un vrai collègue de travail. Posez-moi vos questions !", 'bot'); | |
| } | |
| chatForm.addEventListener('submit', (e) => { | |
| e.preventDefault(); | |
| const text = chatInput.value.trim(); | |
| if (!text) return; | |
| addChatMessage(escapeHtml(text), 'user'); | |
| chatInput.value = ''; | |
| tokenCounter.textContent = (parseInt(tokenCounter.textContent) + Math.ceil(text.length / 4)) + ' tokens'; | |
| // Typing indicator | |
| typingIndicator.classList.remove('hidden'); | |
| chatMessages.scrollTop = chatMessages.scrollHeight; | |
| // Simulate response delay (lightning fast but natural) | |
| const delay = 500 + Math.random() * 800; | |
| setTimeout(() => { | |
| typingIndicator.classList.add('hidden'); | |
| addChatMessage(getResponse(text), 'bot'); | |
| tokenCounter.textContent = (parseInt(tokenCounter.textContent) + 45) + ' tokens'; | |
| }, delay); | |
| }); | |
| function escapeHtml(str) { | |
| const div = document.createElement('div'); | |
| div.textContent = str; | |
| return div.innerHTML; | |
| } | |
| // ==================== VOICE BUTTON (simulated) ==================== | |
| const voiceBtn = document.getElementById('voice-btn'); | |
| let isListening = false; | |
| voiceBtn.addEventListener('click', () => { | |
| isListening = !isListening; | |
| if (isListening) { | |
| voiceBtn.innerHTML = '<i data-lucide="mic-off" class="w-5 h-5 text-red-400"></i>'; | |
| voiceBtn.classList.add('text-red-400'); | |
| chatInput.placeholder = "Écoute en cours..."; | |
| // Simulate speech recognition | |
| setTimeout(() => { | |
| chatInput.value = "Bonjour Nexus-7, comment va la famille ?"; | |
| isListening = false; | |
| voiceBtn.innerHTML = '<i data-lucide="mic" class="w-5 h-5"></i>'; | |
| voiceBtn.classList.remove('text-red-400'); | |
| chatInput.placeholder = "Parlez à Nexus-7..."; | |
| chatInput.focus(); | |
| }, 2000); | |
| } else { | |
| voiceBtn.innerHTML = '<i data-lucide="mic" class="w-5 h-5"></i>'; | |
| voiceBtn.classList.remove('text-red-400'); | |
| chatInput.placeholder = "Parlez à Nexus-7..."; | |
| } | |
| lucide.createIcons(); | |
| }); |