feat: enhance agent orchestration, knowledge flow and UI refinements
This commit is contained in:
866
frontend/prototypes/holographic-brain-chat.html
Normal file
866
frontend/prototypes/holographic-brain-chat.html
Normal file
@@ -0,0 +1,866 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>J.A.R.V.I.S. - Neural Command Center</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;700;900&family=Share+Tech+Mono&display=swap" rel="stylesheet">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
||||
<style>
|
||||
:root {
|
||||
--jarvis-blue: #00f3ff;
|
||||
--jarvis-blue-dim: rgba(0, 243, 255, 0.1);
|
||||
--bg-color: #020408;
|
||||
}
|
||||
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--jarvis-blue);
|
||||
font-family: 'Orbitron', 'Share Tech Mono', monospace;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.scanlines {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
z-index: 100;
|
||||
background: linear-gradient(to bottom,
|
||||
rgba(255, 255, 255, 0),
|
||||
rgba(255, 255, 255, 0) 50%,
|
||||
rgba(0, 0, 0, 0.08) 50%,
|
||||
rgba(0, 0, 0, 0.08));
|
||||
background-size: 100% 3px;
|
||||
}
|
||||
|
||||
.vignette {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: radial-gradient(circle, transparent 40%, rgba(0, 0, 0, 0.8) 100%);
|
||||
pointer-events: none;
|
||||
z-index: 90;
|
||||
}
|
||||
|
||||
.text-glow {
|
||||
text-shadow: 0 0 8px var(--jarvis-blue), 0 0 15px rgba(0, 243, 255, 0.5);
|
||||
}
|
||||
|
||||
.tech-panel {
|
||||
background: rgba(0, 12, 28, 0.75);
|
||||
border: 1px solid rgba(0, 243, 255, 0.2);
|
||||
position: relative;
|
||||
backdrop-filter: blur(8px);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.tech-panel::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-top: 1px solid var(--jarvis-blue);
|
||||
border-left: 1px solid var(--jarvis-blue);
|
||||
}
|
||||
|
||||
.tech-panel::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-bottom: 1px solid var(--jarvis-blue);
|
||||
border-right: 1px solid var(--jarvis-blue);
|
||||
}
|
||||
|
||||
@keyframes pulse-status {
|
||||
0%, 100% { opacity: 0.7; text-shadow: 0 0 5px var(--jarvis-blue); }
|
||||
50% { opacity: 1; text-shadow: 0 0 15px var(--jarvis-blue), 0 0 30px rgba(0, 243, 255, 0.3); }
|
||||
}
|
||||
|
||||
.status-pulse {
|
||||
animation: pulse-status 2s infinite ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes typing-bounce {
|
||||
0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }
|
||||
30% { transform: translateY(-4px); opacity: 1; }
|
||||
}
|
||||
|
||||
.typing-dot {
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--jarvis-blue);
|
||||
margin: 0 3px;
|
||||
animation: typing-bounce 1.2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.typing-dot:nth-child(2) { animation-delay: 0.2s; }
|
||||
.typing-dot:nth-child(3) { animation-delay: 0.4s; }
|
||||
|
||||
@keyframes msg-in {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.msg-animate {
|
||||
animation: msg-in 0.3s ease both;
|
||||
}
|
||||
|
||||
#brain-canvas {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 1;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.chat-panel {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
background: rgba(0, 8, 20, 0.85);
|
||||
}
|
||||
|
||||
.msg-bubble {
|
||||
background: rgba(0, 30, 50, 0.6);
|
||||
border: 1px solid rgba(0, 243, 255, 0.15);
|
||||
padding: 10px 14px;
|
||||
border-radius: 4px 12px 12px 12px;
|
||||
max-width: 85%;
|
||||
line-height: 1.6;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.msg-bubble.user {
|
||||
background: rgba(0, 243, 255, 0.08);
|
||||
border-color: rgba(0, 243, 255, 0.25);
|
||||
border-radius: 12px 4px 12px 12px;
|
||||
}
|
||||
|
||||
.input-holo {
|
||||
background: rgba(0, 15, 30, 0.7);
|
||||
border: 1px solid rgba(0, 243, 255, 0.3);
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
width: 100%;
|
||||
color: #e0f7ff;
|
||||
font-family: 'Share Tech Mono', monospace;
|
||||
font-size: 13px;
|
||||
outline: none;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.input-holo:focus {
|
||||
border-color: rgba(0, 243, 255, 0.6);
|
||||
box-shadow: 0 0 20px rgba(0, 243, 255, 0.15);
|
||||
}
|
||||
|
||||
.input-holo::placeholder {
|
||||
color: rgba(0, 243, 255, 0.4);
|
||||
}
|
||||
|
||||
.send-btn {
|
||||
background: rgba(0, 243, 255, 0.1);
|
||||
border: 1px solid rgba(0, 243, 255, 0.3);
|
||||
color: var(--jarvis-blue);
|
||||
padding: 10px 20px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-family: 'Orbitron', monospace;
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.1em;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.send-btn:hover {
|
||||
background: rgba(0, 243, 255, 0.2);
|
||||
box-shadow: 0 0 20px rgba(0, 243, 255, 0.3);
|
||||
}
|
||||
|
||||
.send-btn:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.legend-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 8px currentColor;
|
||||
}
|
||||
|
||||
.node-item {
|
||||
padding: 6px 10px;
|
||||
border-bottom: 1px solid rgba(0, 243, 255, 0.08);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.node-item:hover {
|
||||
background: rgba(0, 243, 255, 0.05);
|
||||
}
|
||||
|
||||
.node-item.selected {
|
||||
background: rgba(0, 243, 255, 0.1);
|
||||
border-left: 2px solid var(--jarvis-blue);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar { width: 4px; }
|
||||
::-webkit-scrollbar-track { background: rgba(0, 0, 0, 0.3); }
|
||||
::-webkit-scrollbar-thumb { background: rgba(0, 243, 255, 0.4); border-radius: 2px; }
|
||||
|
||||
.metric-bar {
|
||||
height: 3px;
|
||||
background: rgba(0, 243, 255, 0.2);
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.metric-fill {
|
||||
height: 100%;
|
||||
background: var(--jarvis-blue);
|
||||
box-shadow: 0 0 8px var(--jarvis-blue);
|
||||
transition: width 0.5s ease-out;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="scanlines"></div>
|
||||
<div class="vignette"></div>
|
||||
|
||||
<div class="relative z-30 h-full w-full flex flex-col">
|
||||
|
||||
<!-- HEADER -->
|
||||
<header class="flex justify-between items-center px-6 py-3 border-b border-cyan-500/20 bg-black/40 backdrop-blur-md">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="w-2 h-2 bg-cyan-400 rounded-full shadow-[0_0_10px_#00f3ff]"></div>
|
||||
<div class="w-px h-6 bg-cyan-500/50 my-1"></div>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold tracking-[0.2em] text-glow">J.A.R.V.I.S.</h1>
|
||||
<div class="flex items-center gap-2 mt-1">
|
||||
<div class="h-1 w-16 bg-cyan-500/30 overflow-hidden rounded">
|
||||
<div class="h-full bg-cyan-400 w-1/3 animate-pulse"></div>
|
||||
</div>
|
||||
<span class="text-[9px] tracking-[0.3em] opacity-60">NEURAL COMMAND v2.0</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-6">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-[10px] tracking-widest opacity-60">BRAIN MATRIX</span>
|
||||
<span id="brain-status" class="text-xs font-bold px-3 py-1 rounded status-pulse bg-cyan-900/40 border border-cyan-500/30">ONLINE</span>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div class="text-[10px] opacity-50">SYS_TIME</div>
|
||||
<div id="time-display" class="font-mono text-lg text-cyan-200 tracking-wider">00:00:00</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- MAIN CONTENT -->
|
||||
<div class="flex-grow flex relative">
|
||||
|
||||
<!-- BRAIN CANVAS BACKGROUND -->
|
||||
<div id="brain-canvas" class="absolute inset-0 pointer-events-none"></div>
|
||||
|
||||
<!-- LEFT PANEL: Brain Metrics -->
|
||||
<aside class="w-64 p-4 flex flex-col gap-4 relative z-20 bg-gradient-to-b from-black/60 to-transparent">
|
||||
|
||||
<div class="tech-panel p-4">
|
||||
<h3 class="text-xs font-bold tracking-widest text-cyan-300 mb-3 border-b border-cyan-500/20 pb-2">NODE REGISTRY</h3>
|
||||
<div id="node-list" class="max-h-40 overflow-y-auto space-y-1">
|
||||
<!-- nodes injected -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tech-panel p-4">
|
||||
<h3 class="text-xs font-bold tracking-widest text-cyan-300 mb-3 border-b border-cyan-500/20 pb-2">BRAIN METRICS</h3>
|
||||
<div class="space-y-3 text-[10px]">
|
||||
<div>
|
||||
<div class="flex justify-between mb-1"><span class="opacity-60">NODES</span><span id="m-nodes" class="text-cyan-300">14</span></div>
|
||||
<div class="metric-bar"><div id="bar-nodes" class="metric-fill" style="width: 70%"></div></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex justify-between mb-1"><span class="opacity-60">LINKS</span><span id="m-links" class="text-cyan-300">20</span></div>
|
||||
<div class="metric-bar"><div id="bar-links" class="metric-fill" style="width: 66%"></div></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex justify-between mb-1"><span class="opacity-60">CLUSTERS</span><span id="m-clusters" class="text-cyan-300">3</span></div>
|
||||
<div class="metric-bar"><div id="bar-clusters" class="metric-fill" style="width: 60%"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tech-panel p-4 flex-grow">
|
||||
<h3 class="text-xs font-bold tracking-widest text-cyan-300 mb-3 border-b border-cyan-500/20 pb-2">SIGNAL LEGEND</h3>
|
||||
<div class="space-y-2 text-[11px]">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="legend-dot" style="background: #00f3ff; color: #00f3ff;"></div>
|
||||
<span class="text-cyan-300">KNOWLEDGE</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="legend-dot" style="background: #ff6b9d; color: #ff6b9d;"></div>
|
||||
<span class="text-cyan-300">CHAT</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="legend-dot" style="background: #a855f7; color: #a855f7;"></div>
|
||||
<span class="text-cyan-300">FORUM</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="legend-dot" style="background: #fbbf24; color: #fbbf24;"></div>
|
||||
<span class="text-cyan-300">SCHEDULE</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- CENTER: Brain Activity (clickable area) -->
|
||||
<div class="flex-grow relative z-10 flex flex-col justify-end pointer-events-none">
|
||||
<div class="absolute inset-0 flex items-center justify-center pointer-events-auto" id="brain-click-zone">
|
||||
<!-- Brain visualization is rendered here by Three.js -->
|
||||
</div>
|
||||
|
||||
<!-- Terminal Log at bottom -->
|
||||
<div class="tech-panel m-4 p-3 pointer-events-auto max-h-40 overflow-y-auto">
|
||||
<div class="text-[10px] text-cyan-500/70 tracking-widest mb-2">// SYSTEM LOG</div>
|
||||
<div id="terminal-log" class="space-y-1 text-[10px] font-mono">
|
||||
<div class="text-cyan-400/60">>> Neural matrix initialized.</div>
|
||||
<div class="text-cyan-400/60">>> 14 nodes, 20 connections active.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RIGHT PANEL: Chat -->
|
||||
<aside class="w-96 p-4 flex flex-col gap-4 relative z-20 bg-gradient-to-l from-black/60 to-transparent">
|
||||
|
||||
<div class="tech-panel p-4 flex-grow flex flex-col overflow-hidden">
|
||||
<div class="flex justify-between items-center mb-3 border-b border-cyan-500/20 pb-2">
|
||||
<h3 class="text-xs font-bold tracking-widest text-cyan-300">NEURAL CHAT</h3>
|
||||
<span class="text-[9px] bg-cyan-500/20 px-2 py-1 rounded status-pulse">LIVE</span>
|
||||
</div>
|
||||
|
||||
<div id="chat-messages" class="flex-grow overflow-y-auto space-y-3 mb-4 pr-1">
|
||||
<!-- Welcome -->
|
||||
<div class="text-center py-8">
|
||||
<div class="text-3xl font-bold tracking-[0.3em] text-glow mb-2">JARVIS</div>
|
||||
<div class="text-[10px] tracking-[0.2em] opacity-50">STRATEGIC THINKING PARTNER</div>
|
||||
<div class="text-xs opacity-40 mt-4">有什么我可以帮您分析的?</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chat Input -->
|
||||
<div class="flex gap-2">
|
||||
<input type="text" id="chat-input" class="input-holo flex-grow" placeholder="输入指令..." onkeydown="if(event.key==='Enter')sendMsg()">
|
||||
<button id="send-btn" class="send-btn" onclick="sendMsg()">SEND</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Selected Node Detail -->
|
||||
<div id="node-detail" class="tech-panel p-4 hidden">
|
||||
<div class="flex justify-between items-center mb-2 border-b border-cyan-500/20 pb-2">
|
||||
<h3 class="text-xs font-bold tracking-widest text-cyan-300">SELECTED NODE</h3>
|
||||
<button onclick="clearSelection()" class="text-cyan-500/60 hover:text-cyan-300 text-lg">×</button>
|
||||
</div>
|
||||
<div id="detail-content" class="text-[11px] space-y-2">
|
||||
<!-- detail injected -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const NODES = [
|
||||
{ id: 'core-1', name: 'Orchestrator Prime', type: 'knowledge', importance: 0.98, description: '中枢编排核心' },
|
||||
{ id: 'core-2', name: 'Memory Vault', type: 'knowledge', importance: 0.91, description: '知识存储层' },
|
||||
{ id: 'chat-1', name: 'Session Cluster', type: 'chat', importance: 0.89, description: '实时会话汇聚' },
|
||||
{ id: 'chat-2', name: 'Operator Feed', type: 'chat', importance: 0.76, description: '操控台输入流' },
|
||||
{ id: 'forum-1', name: 'Research Swarm', type: 'forum', importance: 0.81, description: '外部案例趋势池' },
|
||||
{ id: 'forum-2', name: 'Threat Monitor', type: 'forum', importance: 0.72, description: '异常风险观察哨' },
|
||||
{ id: 'sched-1', name: 'Sprint Grid', type: 'schedule', importance: 0.87, description: '执行排期矩阵' },
|
||||
{ id: 'sched-2', name: 'Review Window', type: 'schedule', importance: 0.74, description: '评审验收窗口' },
|
||||
{ id: 'bridge-1', name: 'Signal Bridge', type: 'knowledge', importance: 0.84, description: '跨域桥接节点' },
|
||||
{ id: 'arch-1', name: 'Knowledge Archive', type: 'knowledge', importance: 0.66, description: '知识存档' },
|
||||
{ id: 'thread-1', name: 'Conversation Thread', type: 'chat', importance: 0.62, description: '对话线程' },
|
||||
{ id: 'signal-1', name: 'Forum Signal', type: 'forum', importance: 0.58, description: '外部论坛信号' },
|
||||
{ id: 'lane-1', name: 'Schedule Lane', type: 'schedule', importance: 0.57, description: '排程支线' },
|
||||
{ id: 'dormant-1', name: 'Dormant Trace', type: 'forum', importance: 0.45, description: '低优先级线索' },
|
||||
];
|
||||
|
||||
const EDGES = [
|
||||
{ id: 'e1', source: 'core-1', target: 'core-2', relation: '读取知识' },
|
||||
{ id: 'e2', source: 'core-1', target: 'chat-1', relation: '接收会话' },
|
||||
{ id: 'e3', source: 'core-1', target: 'sched-1', relation: '下发编排' },
|
||||
{ id: 'e4', source: 'core-1', target: 'bridge-1', relation: '稳定桥接' },
|
||||
{ id: 'e5', source: 'core-2', target: 'bridge-1', relation: '提供事实' },
|
||||
{ id: 'e6', source: 'chat-1', target: 'chat-2', relation: '扩展输入' },
|
||||
{ id: 'e7', source: 'chat-1', target: 'bridge-1', relation: '触发同步' },
|
||||
{ id: 'e8', source: 'forum-1', target: 'bridge-1', relation: '补充趋势' },
|
||||
{ id: 'e9', source: 'forum-2', target: 'bridge-1', relation: '反馈风险' },
|
||||
{ id: 'e10', source: 'bridge-1', target: 'sched-1', relation: '投递任务' },
|
||||
{ id: 'e11', source: 'sched-1', target: 'sched-2', relation: '进入评审' },
|
||||
{ id: 'e12', source: 'forum-1', target: 'sched-2', relation: '支撑验证' },
|
||||
{ id: 'e13', source: 'core-2', target: 'arch-1', relation: '归档事实' },
|
||||
{ id: 'e14', source: 'bridge-1', target: 'arch-1', relation: '桥接引用' },
|
||||
{ id: 'e15', source: 'chat-1', target: 'thread-1', relation: '拆分会话' },
|
||||
{ id: 'e16', source: 'chat-2', target: 'thread-1', relation: '补充回执' },
|
||||
{ id: 'e17', source: 'forum-1', target: 'signal-1', relation: '吸收案例' },
|
||||
{ id: 'e18', source: 'sched-1', target: 'lane-1', relation: '拆分排期' },
|
||||
{ id: 'e19', source: 'sched-2', target: 'lane-1', relation: '投递评审' },
|
||||
{ id: 'e20', source: 'forum-2', target: 'dormant-1', relation: '弱风险' },
|
||||
];
|
||||
|
||||
const TYPE_COLORS = { knowledge: 0x00f3ff, chat: 0xff6b9d, forum: 0xa855f7, schedule: 0xfbbf24 };
|
||||
const TYPE_LABELS = { knowledge: '知识库', chat: '对话', forum: '论坛', schedule: '日程' };
|
||||
|
||||
let scene, camera, renderer;
|
||||
let nodeObjects = new Map();
|
||||
let particleSystem, coreMesh, innerMesh;
|
||||
let hoveredNodeId = null;
|
||||
let selectedNodeId = null;
|
||||
let mouseX = 0, mouseY = 0;
|
||||
let targetRotX = 0, targetRotY = 0;
|
||||
let windowHalfX = window.innerWidth / 2;
|
||||
let windowHalfY = window.innerHeight / 2;
|
||||
const nodeMeshes = [];
|
||||
|
||||
function updateTime() {
|
||||
const now = new Date();
|
||||
const el = document.getElementById('time-display');
|
||||
if (el) el.textContent = now.toLocaleTimeString('en-US', { hour12: false });
|
||||
}
|
||||
setInterval(updateTime, 1000);
|
||||
updateTime();
|
||||
|
||||
function init() {
|
||||
const container = document.getElementById('brain-canvas');
|
||||
const w = window.innerWidth;
|
||||
const h = window.innerHeight;
|
||||
|
||||
scene = new THREE.Scene();
|
||||
scene.fog = new THREE.FogExp2(0x020408, 0.003);
|
||||
|
||||
camera = new THREE.PerspectiveCamera(55, w / h, 0.1, 2000);
|
||||
camera.position.z = 70;
|
||||
|
||||
renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
|
||||
renderer.setSize(w, h);
|
||||
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
||||
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
||||
renderer.toneMappingExposure = 1.3;
|
||||
container.style.width = w + 'px';
|
||||
container.style.height = h + 'px';
|
||||
container.appendChild(renderer.domElement);
|
||||
|
||||
const ambient = new THREE.AmbientLight(0x404060, 0.5);
|
||||
scene.add(ambient);
|
||||
|
||||
const point = new THREE.PointLight(0x00f3ff, 2, 250);
|
||||
point.position.set(0, 30, 50);
|
||||
scene.add(point);
|
||||
|
||||
createBackground();
|
||||
createBrainCore();
|
||||
createNodes();
|
||||
createEdges();
|
||||
createOrbitalRings();
|
||||
|
||||
document.addEventListener('mousemove', onMouseMove);
|
||||
document.addEventListener('wheel', onWheel);
|
||||
window.addEventListener('resize', onResize);
|
||||
|
||||
updateNodeList();
|
||||
animate();
|
||||
}
|
||||
|
||||
function createBackground() {
|
||||
const count = 1200;
|
||||
const positions = new Float32Array(count * 3);
|
||||
const colors = new Float32Array(count * 3);
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
positions[i * 3] = (Math.random() - 0.5) * 500;
|
||||
positions[i * 3 + 1] = (Math.random() - 0.5) * 500;
|
||||
positions[i * 3 + 2] = (Math.random() - 0.5) * 500;
|
||||
|
||||
const c = new THREE.Color();
|
||||
c.setHSL(0.55 + Math.random() * 0.1, 0.8, 0.5 + Math.random() * 0.3);
|
||||
colors[i * 3] = c.r;
|
||||
colors[i * 3 + 1] = c.g;
|
||||
colors[i * 3 + 2] = c.b;
|
||||
}
|
||||
|
||||
const geo = new THREE.BufferGeometry();
|
||||
geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
||||
geo.setAttribute('color', new THREE.BufferAttribute(colors, 3));
|
||||
|
||||
const mat = new THREE.PointsMaterial({
|
||||
size: 0.4, vertexColors: true, transparent: true, opacity: 0.5,
|
||||
blending: THREE.AdditiveBlending,
|
||||
});
|
||||
|
||||
particleSystem = new THREE.Points(geo, mat);
|
||||
scene.add(particleSystem);
|
||||
}
|
||||
|
||||
function createBrainCore() {
|
||||
coreMesh = new THREE.Mesh(
|
||||
new THREE.IcosahedronGeometry(3, 2),
|
||||
new THREE.MeshBasicMaterial({ color: 0x00f3ff, wireframe: true, transparent: true, opacity: 0.1 })
|
||||
);
|
||||
scene.add(coreMesh);
|
||||
|
||||
innerMesh = new THREE.Mesh(
|
||||
new THREE.IcosahedronGeometry(1.8, 1),
|
||||
new THREE.MeshBasicMaterial({ color: 0x00f3ff, transparent: true, opacity: 0.15 })
|
||||
);
|
||||
scene.add(innerMesh);
|
||||
}
|
||||
|
||||
function createHexShape(r) {
|
||||
const shape = new THREE.Shape();
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const a = (i / 6) * Math.PI * 2 - Math.PI / 6;
|
||||
const x = Math.cos(a) * r, y = Math.sin(a) * r;
|
||||
if (i === 0) shape.moveTo(x, y); else shape.lineTo(x, y);
|
||||
}
|
||||
shape.closePath();
|
||||
return shape;
|
||||
}
|
||||
|
||||
function createNodes() {
|
||||
const angleStep = (Math.PI * 2) / NODES.length;
|
||||
const radius = 28;
|
||||
|
||||
NODES.forEach((node, i) => {
|
||||
const angle = i * angleStep;
|
||||
const layer = Math.floor(i / 7);
|
||||
const r = radius + layer * 16;
|
||||
const x = Math.cos(angle) * r;
|
||||
const z = Math.sin(angle) * r;
|
||||
const y = (Math.random() - 0.5) * 6 + layer * 3;
|
||||
|
||||
const color = TYPE_COLORS[node.type];
|
||||
const imp = node.importance;
|
||||
const height = 2.5 + imp * 10;
|
||||
const r2 = 1.2 + imp * 1.2;
|
||||
|
||||
const group = new THREE.Group();
|
||||
group.userData = { nodeId: node.id };
|
||||
group.position.set(x, y, z);
|
||||
|
||||
const geo = new THREE.ExtrudeGeometry(createHexShape(r2), {
|
||||
depth: height, bevelEnabled: true, bevelThickness: 0.15, bevelSize: 0.1, bevelSegments: 1,
|
||||
});
|
||||
geo.center();
|
||||
|
||||
const mat = new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.1, side: THREE.DoubleSide });
|
||||
const mesh = new THREE.Mesh(geo, mat);
|
||||
mesh.rotation.x = -Math.PI / 2;
|
||||
group.add(mesh);
|
||||
|
||||
const edgesGeo = new THREE.EdgesGeometry(geo);
|
||||
const edgesMat = new THREE.LineBasicMaterial({ color, transparent: true, opacity: 0.7 });
|
||||
const edges = new THREE.LineSegments(edgesGeo, edgesMat);
|
||||
edges.rotation.x = -Math.PI / 2;
|
||||
group.add(edges);
|
||||
|
||||
const glowGeo = new THREE.SphereGeometry(r2 * 0.5, 10, 10);
|
||||
const glowMat = new THREE.MeshBasicMaterial({
|
||||
color, transparent: true, opacity: 0.25 + imp * 0.4, blending: THREE.AdditiveBlending,
|
||||
});
|
||||
const glow = new THREE.Mesh(glowGeo, glowMat);
|
||||
group.add(glow);
|
||||
nodeMeshes.push(glow);
|
||||
|
||||
const topGeo = new THREE.CircleGeometry(r2 * 0.35, 6);
|
||||
const topMat = new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.6, side: THREE.DoubleSide });
|
||||
const top = new THREE.Mesh(topGeo, topMat);
|
||||
top.rotation.x = -Math.PI / 2;
|
||||
top.position.y = height / 2;
|
||||
group.add(top);
|
||||
|
||||
group.rotation.y = Math.random() * Math.PI;
|
||||
scene.add(group);
|
||||
nodeObjects.set(node.id, group);
|
||||
});
|
||||
}
|
||||
|
||||
function createEdges() {
|
||||
EDGES.forEach(edge => {
|
||||
const src = nodeObjects.get(edge.source);
|
||||
const tgt = nodeObjects.get(edge.target);
|
||||
if (!src || !tgt) return;
|
||||
|
||||
const srcNode = NODES.find(n => n.id === edge.source);
|
||||
const color = TYPE_COLORS[srcNode?.type || 'knowledge'];
|
||||
|
||||
const points = [src.position.clone(), tgt.position.clone()];
|
||||
const geo = new THREE.BufferGeometry().setFromPoints(points);
|
||||
const mat = new THREE.LineDashedMaterial({
|
||||
color, transparent: true, opacity: 0.35,
|
||||
dashSize: 1.2, gapSize: 0.8, blending: THREE.AdditiveBlending,
|
||||
});
|
||||
const line = new THREE.Line(geo, mat);
|
||||
line.computeLineDistances();
|
||||
scene.add(line);
|
||||
});
|
||||
}
|
||||
|
||||
function createOrbitalRings() {
|
||||
const ring1Geo = new THREE.BufferGeometry();
|
||||
const ring1Pos = new Float32Array(60 * 3);
|
||||
for (let i = 0; i < 60; i++) {
|
||||
const t = (i / 60) * Math.PI * 2;
|
||||
ring1Pos[i * 3] = Math.cos(t) * 40;
|
||||
ring1Pos[i * 3 + 1] = (Math.random() - 0.5) * 0.4;
|
||||
ring1Pos[i * 3 + 2] = Math.sin(t) * 40;
|
||||
}
|
||||
ring1Geo.setAttribute('position', new THREE.BufferAttribute(ring1Pos, 3));
|
||||
const ring1 = new THREE.Points(ring1Geo, new THREE.PointsMaterial({ color: 0x00f3ff, size: 0.25, transparent: true, opacity: 0.35 }));
|
||||
ring1.userData = { sx: 0.0008, sy: 0.0015 };
|
||||
scene.add(ring1);
|
||||
|
||||
const ring2Geo = new THREE.BufferGeometry();
|
||||
const ring2Pos = new Float32Array(50 * 3);
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const t = (i / 50) * Math.PI * 2;
|
||||
ring2Pos[i * 3] = (Math.random() - 0.5) * 0.4;
|
||||
ring2Pos[i * 3 + 1] = Math.cos(t) * 45;
|
||||
ring2Pos[i * 3 + 2] = Math.sin(t) * 45;
|
||||
}
|
||||
ring2Geo.setAttribute('position', new THREE.BufferAttribute(ring2Pos, 3));
|
||||
const ring2 = new THREE.Points(ring2Geo, new THREE.PointsMaterial({ color: 0xa855f7, size: 0.2, transparent: true, opacity: 0.25 }));
|
||||
ring2.userData = { sx: 0.0015, sy: 0.0008 };
|
||||
scene.add(ring2);
|
||||
}
|
||||
|
||||
function onMouseMove(e) {
|
||||
mouseX = e.clientX - windowHalfX;
|
||||
mouseY = e.clientY - windowHalfY;
|
||||
targetRotY = mouseX * 0.0004;
|
||||
targetRotX = -mouseY * 0.0004;
|
||||
}
|
||||
|
||||
function onWheel(e) {
|
||||
camera.position.z += e.deltaY * 0.04;
|
||||
camera.position.z = Math.max(35, Math.min(180, camera.position.z));
|
||||
}
|
||||
|
||||
function onResize() {
|
||||
const w = window.innerWidth;
|
||||
const h = window.innerHeight;
|
||||
camera.aspect = w / h;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(w, h);
|
||||
const container = document.getElementById('brain-canvas');
|
||||
container.style.width = w + 'px';
|
||||
container.style.height = h + 'px';
|
||||
}
|
||||
|
||||
function animate() {
|
||||
requestAnimationFrame(animate);
|
||||
const t = Date.now() * 0.001;
|
||||
|
||||
if (coreMesh) {
|
||||
coreMesh.rotation.y += 0.001;
|
||||
coreMesh.rotation.x -= 0.002;
|
||||
}
|
||||
if (innerMesh) {
|
||||
innerMesh.rotation.y -= 0.002;
|
||||
innerMesh.rotation.z += 0.001;
|
||||
}
|
||||
|
||||
scene.rotation.y += (targetRotY - scene.rotation.y) * 0.03;
|
||||
scene.rotation.x += (targetRotX - scene.rotation.x) * 0.03;
|
||||
|
||||
if (particleSystem) {
|
||||
particleSystem.rotation.y += 0.00008;
|
||||
particleSystem.rotation.x += 0.00004;
|
||||
}
|
||||
|
||||
nodeMeshes.forEach((m, i) => {
|
||||
const p = Math.sin(t * 1.8 + i * 0.4) * 0.12;
|
||||
m.scale.setScalar(1 + p);
|
||||
});
|
||||
|
||||
scene.children.forEach(c => {
|
||||
if (c instanceof THREE.Points && c.userData.sx) {
|
||||
c.rotation.x += c.userData.sx;
|
||||
c.rotation.y += c.userData.sy;
|
||||
}
|
||||
});
|
||||
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
|
||||
function updateNodeList() {
|
||||
const list = document.getElementById('node-list');
|
||||
list.innerHTML = '';
|
||||
NODES.forEach(node => {
|
||||
const color = '#' + TYPE_COLORS[node.type].toString(16).padStart(6, '0');
|
||||
const div = document.createElement('div');
|
||||
div.className = 'node-item flex items-center gap-2';
|
||||
div.innerHTML = `
|
||||
<div class="w-2 h-2 rounded-full flex-shrink-0" style="background: ${color}; box-shadow: 0 0 6px ${color};"></div>
|
||||
<span class="text-cyan-300 truncate flex-grow">${node.name}</span>
|
||||
<span class="opacity-50">${Math.round(node.importance * 100)}%</span>
|
||||
`;
|
||||
div.onclick = () => selectNode(node.id);
|
||||
list.appendChild(div);
|
||||
});
|
||||
}
|
||||
|
||||
function selectNode(nodeId) {
|
||||
selectedNodeId = nodeId;
|
||||
const node = NODES.find(n => n.id === nodeId);
|
||||
if (!node) return;
|
||||
|
||||
document.querySelectorAll('.node-item').forEach(el => el.classList.remove('selected'));
|
||||
const items = document.querySelectorAll('.node-item');
|
||||
const idx = NODES.findIndex(n => n.id === nodeId);
|
||||
if (items[idx]) items[idx].classList.add('selected');
|
||||
|
||||
const color = '#' + TYPE_COLORS[node.type].toString(16).padStart(6, '0');
|
||||
const detail = document.getElementById('detail-content');
|
||||
const labels = { knowledge: '知识库', chat: '对话', forum: '论坛', schedule: '日程' };
|
||||
detail.innerHTML = `
|
||||
<div class="text-center mb-2">
|
||||
<div class="text-[9px] opacity-50 uppercase tracking-widest" style="color: ${color}">${labels[node.type]}</div>
|
||||
<div class="text-sm font-bold text-cyan-100 tracking-wider">${node.name}</div>
|
||||
</div>
|
||||
<div class="flex justify-between"><span class="opacity-60">重要性</span><span class="text-cyan-300">${Math.round(node.importance * 100)}%</span></div>
|
||||
<div class="opacity-60">描述</div>
|
||||
<div class="text-cyan-300/80 pl-2 border-l border-cyan-500/30">${node.description}</div>
|
||||
<div class="mt-2">
|
||||
<div class="opacity-60 mb-1">关联链路</div>
|
||||
${EDGES.filter(e => e.source === nodeId || e.target === nodeId).map(e => {
|
||||
const otherId = e.source === nodeId ? e.target : e.source;
|
||||
const other = NODES.find(n => n.id === otherId);
|
||||
return `<div class="text-[9px] text-cyan-500/70">${e.relation} → ${other?.name || ''}</div>`;
|
||||
}).join('')}
|
||||
</div>
|
||||
`;
|
||||
document.getElementById('node-detail').classList.remove('hidden');
|
||||
addLog('>> Node selected: ' + node.name);
|
||||
}
|
||||
|
||||
window.clearSelection = function() {
|
||||
selectedNodeId = null;
|
||||
document.querySelectorAll('.node-item').forEach(el => el.classList.remove('selected'));
|
||||
document.getElementById('node-detail').classList.add('hidden');
|
||||
};
|
||||
|
||||
function addLog(msg) {
|
||||
const log = document.getElementById('terminal-log');
|
||||
if (!log) return;
|
||||
const div = document.createElement('div');
|
||||
div.className = 'text-cyan-400/60';
|
||||
div.textContent = '>> ' + msg;
|
||||
log.appendChild(div);
|
||||
log.scrollTop = log.scrollHeight;
|
||||
while (log.children.length > 20) log.removeChild(log.firstChild);
|
||||
}
|
||||
|
||||
window.sendMsg = function() {
|
||||
const input = document.getElementById('chat-input');
|
||||
const val = input.value.trim();
|
||||
if (!val) return;
|
||||
|
||||
const messages = document.getElementById('chat-messages');
|
||||
|
||||
// User msg
|
||||
const userDiv = document.createElement('div');
|
||||
userDiv.className = 'flex justify-end msg-animate';
|
||||
userDiv.innerHTML = `<div class="msg-bubble user">${escapeHtml(val)}</div>`;
|
||||
messages.appendChild(userDiv);
|
||||
|
||||
input.value = '';
|
||||
|
||||
// Simulate Jarvis response
|
||||
setTimeout(() => {
|
||||
const respDiv = document.createElement('div');
|
||||
respDiv.className = 'flex justify-start msg-animate';
|
||||
respDiv.innerHTML = `<div class="msg-bubble">正在分析您的请求...</div>`;
|
||||
messages.appendChild(respDiv);
|
||||
messages.scrollTop = messages.scrollHeight;
|
||||
|
||||
setTimeout(() => {
|
||||
respDiv.querySelector('.msg-bubble').innerHTML = `我已经分析了这个问题。基于当前知识图谱的相关节点:<br><br>
|
||||
<span class="text-cyan-400">• Signal Bridge</span> 跨域桥接节点正在协调<br>
|
||||
<span class="text-cyan-400">• Sprint Grid</span> 执行排期矩阵已就绪<br><br>
|
||||
需要我展开某个节点的详细信息吗?`;
|
||||
addLog('Response generated for: ' + val.substring(0, 30) + '...');
|
||||
}, 1200);
|
||||
}, 500);
|
||||
|
||||
messages.scrollTop = messages.scrollHeight;
|
||||
addLog('User input: ' + val.substring(0, 40));
|
||||
};
|
||||
|
||||
function escapeHtml(str) {
|
||||
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
|
||||
// Raycasting for node hover
|
||||
document.addEventListener('mousemove', (e) => {
|
||||
const canvas = renderer.domElement;
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
if (e.clientX < rect.left || e.clientX > rect.right || e.clientY < rect.top || e.clientY > rect.bottom) return;
|
||||
|
||||
const x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
|
||||
const y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
|
||||
|
||||
const raycaster = new THREE.Raycaster();
|
||||
raycaster.setFromCamera({ x, y }, camera);
|
||||
const hits = raycaster.intersectObjects(scene.children, true);
|
||||
|
||||
let found = null;
|
||||
for (const hit of hits) {
|
||||
let obj = hit.object;
|
||||
while (obj) {
|
||||
if (obj.userData?.nodeId) { found = obj.userData.nodeId; break; }
|
||||
obj = obj.parent;
|
||||
}
|
||||
if (found) break;
|
||||
}
|
||||
hoveredNodeId = found;
|
||||
canvas.style.cursor = found ? 'pointer' : 'grab';
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
const canvas = renderer.domElement;
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
if (e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom) {
|
||||
if (hoveredNodeId) selectNode(hoveredNodeId);
|
||||
}
|
||||
});
|
||||
|
||||
init();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
831
frontend/prototypes/holographic-brain-demo.html
Normal file
831
frontend/prototypes/holographic-brain-demo.html
Normal file
@@ -0,0 +1,831 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>J.A.R.V.I.S. - 知识脑图</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;700;900&family=Share+Tech+Mono&display=swap" rel="stylesheet">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
||||
<style>
|
||||
:root {
|
||||
--jarvis-blue: #00f3ff;
|
||||
--jarvis-blue-dim: rgba(0, 243, 255, 0.1);
|
||||
--jarvis-alert: #ff3333;
|
||||
--bg-color: #020408;
|
||||
}
|
||||
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--jarvis-blue);
|
||||
font-family: 'Orbitron', sans-serif;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
body::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: linear-gradient(to bottom,
|
||||
rgba(255, 255, 255, 0),
|
||||
rgba(255, 255, 255, 0) 50%,
|
||||
rgba(0, 0, 0, 0.1) 50%,
|
||||
rgba(0, 0, 0, 0.1));
|
||||
background-size: 100% 3px;
|
||||
pointer-events: none;
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.vignette {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: radial-gradient(circle, transparent 50%, rgba(0, 0, 0, 0.95) 100%);
|
||||
pointer-events: none;
|
||||
z-index: 40;
|
||||
}
|
||||
|
||||
.text-glow {
|
||||
text-shadow: 0 0 5px var(--jarvis-blue), 0 0 10px var(--jarvis-blue);
|
||||
}
|
||||
|
||||
.tech-panel {
|
||||
background: rgba(0, 15, 30, 0.7);
|
||||
border-left: 1px solid rgba(0, 243, 255, 0.3);
|
||||
border-right: 1px solid rgba(0, 243, 255, 0.3);
|
||||
position: relative;
|
||||
backdrop-filter: blur(4px);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.tech-panel:hover {
|
||||
border-color: rgba(0, 243, 255, 0.8);
|
||||
background: rgba(0, 20, 40, 0.8);
|
||||
}
|
||||
|
||||
.tech-panel::before,
|
||||
.tech-panel::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border: 1px solid var(--jarvis-blue);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.tech-panel::before {
|
||||
top: -1px;
|
||||
left: -1px;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.tech-panel::after {
|
||||
bottom: -1px;
|
||||
right: -1px;
|
||||
border-left: none;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.tech-border {
|
||||
clip-path: polygon(10px 0,
|
||||
100% 0,
|
||||
100% calc(100% - 10px),
|
||||
calc(100% - 10px) 100%,
|
||||
0 100%,
|
||||
0 10px);
|
||||
border-top: 1px solid var(--jarvis-blue-dim);
|
||||
}
|
||||
|
||||
@keyframes pulse-status {
|
||||
0%, 100% { opacity: 0.6; text-shadow: 0 0 2px var(--jarvis-blue); box-shadow: 0 0 5px var(--jarvis-blue-dim); }
|
||||
50% { opacity: 1; text-shadow: 0 0 15px var(--jarvis-blue); box-shadow: 0 0 15px var(--jarvis-blue-dim); }
|
||||
}
|
||||
|
||||
.status-pulse {
|
||||
animation: pulse-status 3s infinite ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes flash-row {
|
||||
0% { background-color: transparent; }
|
||||
50% { background-color: rgba(0, 243, 255, 0.15); }
|
||||
100% { background-color: transparent; }
|
||||
}
|
||||
|
||||
.flash-row:hover {
|
||||
animation: flash-row 0.8s infinite;
|
||||
cursor: pointer;
|
||||
background: rgba(0, 243, 255, 0.05);
|
||||
}
|
||||
|
||||
#brain-canvas-container {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
#brain-canvas-container canvas {
|
||||
pointer-events: auto;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
#brain-canvas-container canvas:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.legend-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 6px currentColor;
|
||||
}
|
||||
|
||||
.detail-panel {
|
||||
transform: translateX(100%);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.detail-panel.active {
|
||||
transform: translateX(0);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="vignette"></div>
|
||||
|
||||
<div class="relative z-30 h-full w-full flex flex-col p-4 md:p-6 pointer-events-none">
|
||||
|
||||
<header class="flex justify-between items-start pointer-events-auto mb-2 border-b border-cyan-500/20 pb-2">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<div class="w-2 h-2 bg-cyan-400 rounded-full mb-1"></div>
|
||||
<div class="h-8 w-px bg-cyan-500/50"></div>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-4xl font-bold tracking-[0.15em] text-glow font-orbitron">J.A.R.V.I.S.</h1>
|
||||
<div class="flex items-center gap-2 mt-1">
|
||||
<div class="h-1 w-20 bg-cyan-500/30 overflow-hidden">
|
||||
<div class="h-full bg-cyan-400 w-1/2 animate-pulse"></div>
|
||||
</div>
|
||||
<div class="text-[9px] tracking-[0.3em] opacity-70">KNOWLEDGE BRAIN V2.0</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-right">
|
||||
<div class="flex items-center justify-end gap-3 mb-1">
|
||||
<span class="text-[10px] tracking-widest opacity-60">BRAIN STATUS</span>
|
||||
<span id="brain-status" class="font-bold text-xs status-pulse bg-cyan-900/40 px-3 py-1 rounded-sm border border-cyan-500/30">INITIALIZING</span>
|
||||
</div>
|
||||
<div class="flex justify-end items-baseline gap-2">
|
||||
<span class="text-[10px] opacity-50">SYS_TIME</span>
|
||||
<div id="time-display" class="font-mono text-xl tracking-wider text-cyan-200">00:00:00</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="flex-grow grid grid-cols-1 md:grid-cols-4 gap-6 pointer-events-auto h-0 min-h-0 mt-2">
|
||||
|
||||
<div class="flex flex-col gap-4 h-full overflow-hidden">
|
||||
|
||||
<div class="tech-panel p-4 tech-border">
|
||||
<h3 class="text-xs font-bold tracking-widest border-b border-cyan-500/30 pb-2 mb-3 text-cyan-300">
|
||||
NODE REGISTRY</h3>
|
||||
<div class="space-y-2 font-mono text-[10px] max-h-48 overflow-y-auto pr-1">
|
||||
<div id="node-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tech-panel p-4 tech-border">
|
||||
<h3 class="text-xs font-bold tracking-widest border-b border-cyan-500/30 pb-2 mb-3 text-cyan-300">
|
||||
BRAIN METRICS</h3>
|
||||
<div class="space-y-4 font-mono text-[10px]">
|
||||
<div>
|
||||
<div class="flex justify-between mb-1"><span class="opacity-70">NODE_COUNT</span><span class="text-cyan-300" id="metric-nodes">0</span></div>
|
||||
<div class="w-full bg-cyan-900/50 h-1">
|
||||
<div id="nodes-bar" class="sys-bar bg-cyan-400 h-full w-[0%] shadow-[0_0_8px_#00f3ff]"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex justify-between mb-1"><span class="opacity-70">LINK_DENSITY</span><span class="text-cyan-300" id="metric-links">0</span></div>
|
||||
<div class="w-full bg-cyan-900/50 h-1">
|
||||
<div id="links-bar" class="sys-bar bg-cyan-400 h-full w-[0%] shadow-[0_0_8px_#00f3ff]"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex justify-between mb-1"><span class="opacity-70">CLUSTER_COUNT</span><span class="text-cyan-300" id="metric-clusters">0</span></div>
|
||||
<div class="w-full bg-cyan-900/50 h-1">
|
||||
<div id="clusters-bar" class="sys-bar bg-cyan-400 h-full w-[0%] shadow-[0_0_8px_#00f3ff]"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2 pt-2">
|
||||
<div class="bg-cyan-900/20 p-2 border border-cyan-500/20 text-center">
|
||||
<div class="opacity-50 text-[9px]">CORE_NODES</div>
|
||||
<div id="core-nodes" class="text-sm font-bold text-cyan-300">0</div>
|
||||
</div>
|
||||
<div class="bg-cyan-900/20 p-2 border border-cyan-500/20 text-center">
|
||||
<div class="opacity-50 text-[9px]">BRIDGE_NODES</div>
|
||||
<div id="bridge-nodes" class="text-sm font-bold text-cyan-300">0</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tech-panel p-4 flex-grow overflow-hidden flex flex-col">
|
||||
<div class="flex justify-between items-center border-b border-cyan-500/30 pb-2 mb-2">
|
||||
<h3 class="text-xs font-bold tracking-widest text-cyan-300">SIGNAL LEGEND</h3>
|
||||
</div>
|
||||
<div class="space-y-3 font-mono text-[11px]">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="legend-dot" style="background: #00f3ff; color: #00f3ff;"></div>
|
||||
<span class="text-cyan-300">KNOWLEDGE</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="legend-dot" style="background: #ff6b9d; color: #ff6b9d;"></div>
|
||||
<span class="text-cyan-300">CHAT</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="legend-dot" style="background: #a855f7; color: #a855f7;"></div>
|
||||
<span class="text-cyan-300">FORUM</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="legend-dot" style="background: #fbbf24; color: #fbbf24;"></div>
|
||||
<span class="text-cyan-300">SCHEDULE</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="md:col-span-2 relative flex flex-col justify-end items-center">
|
||||
|
||||
<div class="absolute top-10 w-full text-center pointer-events-none">
|
||||
<div id="brain-title" class="text-xl md:text-2xl font-bold tracking-[0.3em] text-glow uppercase text-cyan-100">
|
||||
NEURAL BRAIN MATRIX
|
||||
</div>
|
||||
<div class="text-[10px] text-cyan-400/70 tracking-[0.5em] mt-2">INTERACTIVE 3D GRAPH</div>
|
||||
<div class="text-[10px] text-cyan-400/50 tracking-[0.2em] mt-1">DRAG TO ROTATE / SCROLL TO ZOOM</div>
|
||||
</div>
|
||||
|
||||
<div id="brain-canvas-container" class="w-full h-full"></div>
|
||||
|
||||
<div class="terminal-window w-full h-48 bg-black/90 border-t border-cyan-500/50 rounded-tl-lg rounded-tr-lg overflow-hidden flex flex-col backdrop-blur-md shadow-[0_-5px_20px_rgba(0,243,255,0.1)]">
|
||||
<div class="bg-cyan-900/20 px-3 py-1.5 text-[10px] flex justify-between border-b border-cyan-500/30 items-center">
|
||||
<span class="tracking-widest text-cyan-400">BRAIN_CONSOLE // SYSTEM LOG</span>
|
||||
<div class="flex gap-2 font-mono text-cyan-600">
|
||||
<span id="active-nodes-val">ACTIVE: 0</span>
|
||||
<span id="selected-val">SELECTED: NONE</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="brain-terminal" class="p-4 font-mono text-xs text-cyan-300/80 overflow-y-auto flex-grow space-y-1.5">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-4 h-full overflow-hidden">
|
||||
|
||||
<div id="detail-panel" class="tech-panel p-4 tech-border detail-panel">
|
||||
<h3 class="text-xs font-bold tracking-widest border-b border-cyan-500/30 pb-2 mb-3 text-cyan-300">
|
||||
SELECTED NODE</h3>
|
||||
<div id="node-detail" class="font-mono text-[11px] space-y-3">
|
||||
<div class="text-center text-cyan-500/50 py-8">NO NODE SELECTED</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tech-panel p-4 flex-grow flex flex-col overflow-hidden">
|
||||
<h3 class="text-xs font-bold tracking-widest border-b border-cyan-500/30 pb-2 mb-3 text-cyan-300">
|
||||
OUTGOING CONNECTIONS</h3>
|
||||
<div id="connections-list" class="font-mono text-[10px] overflow-y-auto flex-grow space-y-2">
|
||||
<div class="text-center text-cyan-500/50 py-4">SELECT A NODE</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tech-panel h-32 p-4 relative overflow-hidden flex flex-col justify-between">
|
||||
<div class="text-[10px] opacity-60 tracking-widest mb-1">BRAIN ACTIVITY</div>
|
||||
<div class="flex items-end justify-between h-full gap-1 pt-2" id="activity-graph">
|
||||
<div class="sys-bar w-1 bg-cyan-600 h-[30%]"></div>
|
||||
<div class="sys-bar w-1 bg-cyan-500 h-[50%]"></div>
|
||||
<div class="sys-bar w-1 bg-cyan-400 h-[80%]"></div>
|
||||
<div class="sys-bar w-1 bg-cyan-300 h-[40%]"></div>
|
||||
<div class="sys-bar w-1 bg-cyan-400 h-[60%]"></div>
|
||||
<div class="sys-bar w-1 bg-cyan-500 h-[90%]"></div>
|
||||
<div class="sys-bar w-1 bg-cyan-600 h-[45%]"></div>
|
||||
<div class="sys-bar w-1 bg-cyan-500 h-[70%]"></div>
|
||||
<div class="sys-bar w-1 bg-cyan-400 h-[30%]"></div>
|
||||
<div class="sys-bar w-1 bg-cyan-300 h-[60%]"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const NODES = [
|
||||
{ id: 'core-1', name: 'Orchestrator Prime', type: 'knowledge', importance: 0.98, description: '中枢编排核心' },
|
||||
{ id: 'core-2', name: 'Memory Vault', type: 'knowledge', importance: 0.91, description: '知识存储层' },
|
||||
{ id: 'chat-1', name: 'Session Cluster', type: 'chat', importance: 0.89, description: '实时会话汇聚' },
|
||||
{ id: 'chat-2', name: 'Operator Feed', type: 'chat', importance: 0.76, description: '操控台输入流' },
|
||||
{ id: 'forum-1', name: 'Research Swarm', type: 'forum', importance: 0.81, description: '外部案例趋势池' },
|
||||
{ id: 'forum-2', name: 'Threat Monitor', type: 'forum', importance: 0.72, description: '异常风险观察哨' },
|
||||
{ id: 'sched-1', name: 'Sprint Grid', type: 'schedule', importance: 0.87, description: '执行排期矩阵' },
|
||||
{ id: 'sched-2', name: 'Review Window', type: 'schedule', importance: 0.74, description: '评审验收窗口' },
|
||||
{ id: 'bridge-1', name: 'Signal Bridge', type: 'knowledge', importance: 0.84, description: '跨域桥接节点' },
|
||||
{ id: 'arch-1', name: 'Knowledge Archive', type: 'knowledge', importance: 0.66, description: '知识存档' },
|
||||
{ id: 'thread-1', name: 'Conversation Thread', type: 'chat', importance: 0.62, description: '对话线程' },
|
||||
{ id: 'signal-1', name: 'Forum Signal', type: 'forum', importance: 0.58, description: '外部论坛信号' },
|
||||
{ id: 'lane-1', name: 'Schedule Lane', type: 'schedule', importance: 0.57, description: '排程支线' },
|
||||
{ id: 'dormant-1', name: 'Dormant Trace', type: 'forum', importance: 0.45, description: '低优先级线索' },
|
||||
];
|
||||
|
||||
const EDGES = [
|
||||
{ id: 'e1', source: 'core-1', target: 'core-2', relation: '读取知识' },
|
||||
{ id: 'e2', source: 'core-1', target: 'chat-1', relation: '接收会话' },
|
||||
{ id: 'e3', source: 'core-1', target: 'sched-1', relation: '下发编排' },
|
||||
{ id: 'e4', source: 'core-1', target: 'bridge-1', relation: '稳定桥接' },
|
||||
{ id: 'e5', source: 'core-2', target: 'bridge-1', relation: '提供事实' },
|
||||
{ id: 'e6', source: 'chat-1', target: 'chat-2', relation: '扩展输入' },
|
||||
{ id: 'e7', source: 'chat-1', target: 'bridge-1', relation: '触发同步' },
|
||||
{ id: 'e8', source: 'forum-1', target: 'bridge-1', relation: '补充趋势' },
|
||||
{ id: 'e9', source: 'forum-2', target: 'bridge-1', relation: '反馈风险' },
|
||||
{ id: 'e10', source: 'bridge-1', target: 'sched-1', relation: '投递任务' },
|
||||
{ id: 'e11', source: 'sched-1', target: 'sched-2', relation: '进入评审' },
|
||||
{ id: 'e12', source: 'forum-1', target: 'sched-2', relation: '支撑验证' },
|
||||
{ id: 'e13', source: 'core-2', target: 'arch-1', relation: '归档事实' },
|
||||
{ id: 'e14', source: 'bridge-1', target: 'arch-1', relation: '桥接引用' },
|
||||
{ id: 'e15', source: 'chat-1', target: 'thread-1', relation: '拆分会话' },
|
||||
{ id: 'e16', source: 'chat-2', target: 'thread-1', relation: '补充回执' },
|
||||
{ id: 'e17', source: 'forum-1', target: 'signal-1', relation: '吸收案例' },
|
||||
{ id: 'e18', source: 'sched-1', target: 'lane-1', relation: '拆分排期' },
|
||||
{ id: 'e19', source: 'sched-2', target: 'lane-1', relation: '投递评审' },
|
||||
{ id: 'e20', source: 'forum-2', target: 'dormant-1', relation: '弱风险' },
|
||||
];
|
||||
|
||||
const TYPE_COLORS = {
|
||||
knowledge: 0x00f3ff,
|
||||
chat: 0xff6b9d,
|
||||
forum: 0xa855f7,
|
||||
schedule: 0xfbbf24,
|
||||
};
|
||||
|
||||
const TYPE_LABELS = {
|
||||
knowledge: '知识库',
|
||||
chat: '对话',
|
||||
forum: '论坛',
|
||||
schedule: '日程',
|
||||
};
|
||||
|
||||
let scene, camera, renderer;
|
||||
let nodeObjects = new Map();
|
||||
let edgeObjects = [];
|
||||
let particleSystem;
|
||||
let hoveredNodeId = null;
|
||||
let selectedNodeId = null;
|
||||
let mouseX = 0, mouseY = 0;
|
||||
let targetX = 0, targetY = 0;
|
||||
let windowHalfX = window.innerWidth / 2;
|
||||
let windowHalfY = window.innerHeight / 2;
|
||||
const nodeMeshes = [];
|
||||
|
||||
function updateTime() {
|
||||
const now = new Date();
|
||||
const el = document.getElementById('time-display');
|
||||
if (el) el.innerText = now.toLocaleTimeString('en-US', { hour12: false });
|
||||
}
|
||||
setInterval(updateTime, 1000);
|
||||
updateTime();
|
||||
|
||||
function init() {
|
||||
const container = document.getElementById('brain-canvas-container');
|
||||
const w = container.parentElement.clientWidth || 600;
|
||||
const h = container.parentElement.clientHeight || 600;
|
||||
|
||||
scene = new THREE.Scene();
|
||||
scene.fog = new THREE.FogExp2(0x020408, 0.0025);
|
||||
|
||||
camera = new THREE.PerspectiveCamera(60, w / h, 0.1, 2000);
|
||||
camera.position.z = 80;
|
||||
|
||||
renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
|
||||
renderer.setSize(w, h);
|
||||
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
||||
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
||||
renderer.toneMappingExposure = 1.2;
|
||||
container.appendChild(renderer.domElement);
|
||||
|
||||
const ambientLight = new THREE.AmbientLight(0x404060, 0.4);
|
||||
scene.add(ambientLight);
|
||||
|
||||
const pointLight = new THREE.PointLight(0x00f3ff, 1.5, 300);
|
||||
pointLight.position.set(0, 50, 50);
|
||||
scene.add(pointLight);
|
||||
|
||||
createBackground();
|
||||
createBrainCore();
|
||||
createNodes();
|
||||
createEdges();
|
||||
createOrbitalRings();
|
||||
|
||||
document.addEventListener('mousemove', onDocumentMouseMove);
|
||||
document.addEventListener('mousedown', onDocumentMouseDown);
|
||||
document.addEventListener('mouseup', onDocumentMouseUp);
|
||||
document.addEventListener('wheel', onDocumentWheel);
|
||||
window.addEventListener('resize', onWindowResize);
|
||||
|
||||
updateMetrics();
|
||||
updateNodeList();
|
||||
|
||||
setTimeout(() => {
|
||||
const statusEl = document.getElementById('brain-status');
|
||||
if (statusEl) {
|
||||
statusEl.innerText = "ONLINE";
|
||||
statusEl.classList.remove('text-red-500');
|
||||
statusEl.classList.add('text-cyan-300', 'bg-cyan-500/20', 'border-cyan-400');
|
||||
}
|
||||
addLog(">> Brain matrix initialized.");
|
||||
addLog(">> Neural pathways loaded.");
|
||||
addLog(">> 14 nodes, 20 connections active.");
|
||||
}, 1500);
|
||||
|
||||
animate();
|
||||
}
|
||||
|
||||
function createBackground() {
|
||||
const count = 1500;
|
||||
const positions = new Float32Array(count * 3);
|
||||
const colors = new Float32Array(count * 3);
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
positions[i * 3] = (Math.random() - 0.5) * 600;
|
||||
positions[i * 3 + 1] = (Math.random() - 0.5) * 600;
|
||||
positions[i * 3 + 2] = (Math.random() - 0.5) * 600;
|
||||
|
||||
const c = new THREE.Color();
|
||||
c.setHSL(0.55 + Math.random() * 0.1, 0.7, 0.5 + Math.random() * 0.4);
|
||||
colors[i * 3] = c.r;
|
||||
colors[i * 3 + 1] = c.g;
|
||||
colors[i * 3 + 2] = c.b;
|
||||
}
|
||||
|
||||
const geo = new THREE.BufferGeometry();
|
||||
geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
||||
geo.setAttribute('color', new THREE.BufferAttribute(colors, 3));
|
||||
|
||||
const mat = new THREE.PointsMaterial({
|
||||
size: 0.5, vertexColors: true, transparent: true, opacity: 0.6,
|
||||
blending: THREE.AdditiveBlending,
|
||||
});
|
||||
|
||||
particleSystem = new THREE.Points(geo, mat);
|
||||
scene.add(particleSystem);
|
||||
}
|
||||
|
||||
function createBrainCore() {
|
||||
const coreGeo = new THREE.IcosahedronGeometry(3, 2);
|
||||
const coreMat = new THREE.MeshBasicMaterial({
|
||||
color: 0x00f3ff,
|
||||
wireframe: true,
|
||||
transparent: true,
|
||||
opacity: 0.12
|
||||
});
|
||||
const coreMesh = new THREE.Mesh(coreGeo, coreMat);
|
||||
scene.add(coreMesh);
|
||||
|
||||
const innerGeo = new THREE.IcosahedronGeometry(2, 1);
|
||||
const innerMat = new THREE.MeshBasicMaterial({
|
||||
color: 0x00f3ff,
|
||||
transparent: true,
|
||||
opacity: 0.2
|
||||
});
|
||||
const innerMesh = new THREE.Mesh(innerGeo, innerMat);
|
||||
scene.add(innerMesh);
|
||||
}
|
||||
|
||||
function createHexShape(r) {
|
||||
const shape = new THREE.Shape();
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const a = (i / 6) * Math.PI * 2 - Math.PI / 6;
|
||||
const x = Math.cos(a) * r, y = Math.sin(a) * r;
|
||||
if (i === 0) shape.moveTo(x, y); else shape.lineTo(x, y);
|
||||
}
|
||||
shape.closePath();
|
||||
return shape;
|
||||
}
|
||||
|
||||
function createNodes() {
|
||||
const angleStep = (Math.PI * 2) / NODES.length;
|
||||
const radius = 30;
|
||||
|
||||
NODES.forEach((node, i) => {
|
||||
const angle = i * angleStep;
|
||||
const layer = Math.floor(i / 7);
|
||||
const r = radius + layer * 18;
|
||||
const x = Math.cos(angle) * r;
|
||||
const z = Math.sin(angle) * r;
|
||||
const y = (Math.random() - 0.5) * 8 + layer * 4;
|
||||
|
||||
const color = TYPE_COLORS[node.type];
|
||||
const imp = node.importance;
|
||||
const height = 3 + imp * 12;
|
||||
const radius = 1.5 + imp * 1.5;
|
||||
|
||||
const group = new THREE.Group();
|
||||
group.userData = { nodeId: node.id };
|
||||
group.position.set(x, y, z);
|
||||
|
||||
const geo = new THREE.ExtrudeGeometry(createHexShape(radius), {
|
||||
depth: height, bevelEnabled: true, bevelThickness: 0.2, bevelSize: 0.15, bevelSegments: 2,
|
||||
});
|
||||
geo.center();
|
||||
|
||||
const mat = new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.12, side: THREE.DoubleSide });
|
||||
const mesh = new THREE.Mesh(geo, mat);
|
||||
mesh.rotation.x = -Math.PI / 2;
|
||||
group.add(mesh);
|
||||
|
||||
const edgesGeo = new THREE.EdgesGeometry(geo);
|
||||
const edgesMat = new THREE.LineBasicMaterial({ color, transparent: true, opacity: 0.8 });
|
||||
const edges = new THREE.LineSegments(edgesGeo, edgesMat);
|
||||
edges.rotation.x = -Math.PI / 2;
|
||||
group.add(edges);
|
||||
|
||||
const glowGeo = new THREE.SphereGeometry(radius * 0.6, 12, 12);
|
||||
const glowMat = new THREE.MeshBasicMaterial({
|
||||
color, transparent: true, opacity: 0.3 + imp * 0.4, blending: THREE.AdditiveBlending,
|
||||
});
|
||||
const glow = new THREE.Mesh(glowGeo, glowMat);
|
||||
group.add(glow);
|
||||
|
||||
const topGeo = new THREE.CircleGeometry(radius * 0.4, 6);
|
||||
const topMat = new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.7, side: THREE.DoubleSide });
|
||||
const top = new THREE.Mesh(topGeo, topMat);
|
||||
top.rotation.x = -Math.PI / 2;
|
||||
top.position.y = height / 2;
|
||||
group.add(top);
|
||||
|
||||
group.rotation.y = Math.random() * Math.PI;
|
||||
scene.add(group);
|
||||
nodeObjects.set(node.id, group);
|
||||
nodeMeshes.push(glow);
|
||||
});
|
||||
}
|
||||
|
||||
function createEdges() {
|
||||
EDGES.forEach(edge => {
|
||||
const src = nodeObjects.get(edge.source);
|
||||
const tgt = nodeObjects.get(edge.target);
|
||||
if (!src || !tgt) return;
|
||||
|
||||
const srcNode = NODES.find(n => n.id === edge.source);
|
||||
const color = TYPE_COLORS[srcNode?.type || 'knowledge'];
|
||||
|
||||
const points = [src.position.clone(), tgt.position.clone()];
|
||||
const geo = new THREE.BufferGeometry().setFromPoints(points);
|
||||
const mat = new THREE.LineDashedMaterial({
|
||||
color, transparent: true, opacity: 0.4,
|
||||
dashSize: 1.5, gapSize: 1, blending: THREE.AdditiveBlending,
|
||||
});
|
||||
const line = new THREE.Line(geo, mat);
|
||||
line.computeLineDistances();
|
||||
scene.add(line);
|
||||
edgeObjects.push(line);
|
||||
});
|
||||
}
|
||||
|
||||
function createOrbitalRings() {
|
||||
const ring1Geo = new THREE.BufferGeometry();
|
||||
const ring1Pos = new Float32Array(80 * 3);
|
||||
for (let i = 0; i < 80; i++) {
|
||||
const theta = (i / 80) * Math.PI * 2;
|
||||
ring1Pos[i * 3] = Math.cos(theta) * 45;
|
||||
ring1Pos[i * 3 + 1] = (Math.random() - 0.5) * 0.5;
|
||||
ring1Pos[i * 3 + 2] = Math.sin(theta) * 45;
|
||||
}
|
||||
ring1Geo.setAttribute('position', new THREE.BufferAttribute(ring1Pos, 3));
|
||||
const ring1Mat = new THREE.PointsMaterial({ color: 0x00f3ff, size: 0.3, transparent: true, opacity: 0.4 });
|
||||
const ring1 = new THREE.Points(ring1Geo, ring1Mat);
|
||||
ring1.userData = { speedX: 0.001, speedY: 0.002 };
|
||||
scene.add(ring1);
|
||||
|
||||
const ring2Geo = new THREE.BufferGeometry();
|
||||
const ring2Pos = new Float32Array(60 * 3);
|
||||
for (let i = 0; i < 60; i++) {
|
||||
const theta = (i / 60) * Math.PI * 2;
|
||||
ring2Pos[i * 3] = (Math.random() - 0.5) * 0.5;
|
||||
ring2Pos[i * 3 + 1] = Math.cos(theta) * 50;
|
||||
ring2Pos[i * 3 + 2] = Math.sin(theta) * 50;
|
||||
}
|
||||
ring2Geo.setAttribute('position', new THREE.BufferAttribute(ring2Pos, 3));
|
||||
const ring2Mat = new THREE.PointsMaterial({ color: 0xa855f7, size: 0.25, transparent: true, opacity: 0.3 });
|
||||
const ring2 = new THREE.Points(ring2Geo, ring2Mat);
|
||||
ring2.userData = { speedX: 0.002, speedY: 0.001 };
|
||||
scene.add(ring2);
|
||||
}
|
||||
|
||||
function onDocumentMouseMove(e) {
|
||||
mouseX = e.clientX - windowHalfX;
|
||||
mouseY = e.clientY - windowHalfY;
|
||||
}
|
||||
|
||||
function onDocumentMouseDown(e) {}
|
||||
function onDocumentMouseUp(e) {
|
||||
if (hoveredNodeId) selectNode(hoveredNodeId);
|
||||
}
|
||||
|
||||
function onDocumentWheel(e) {
|
||||
camera.position.z += e.deltaY * 0.05;
|
||||
camera.position.z = Math.max(30, Math.min(200, camera.position.z));
|
||||
}
|
||||
|
||||
function onWindowResize() {
|
||||
const container = document.getElementById('brain-canvas-container');
|
||||
const w = container.parentElement.clientWidth || 600;
|
||||
const h = container.parentElement.clientHeight || 600;
|
||||
camera.aspect = w / h;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(w, h);
|
||||
}
|
||||
|
||||
function animate() {
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
const t = Date.now() * 0.001;
|
||||
|
||||
targetX = mouseX * 0.0005;
|
||||
targetY = mouseY * 0.0005;
|
||||
|
||||
camera.rotation.y += (targetX - camera.rotation.y) * 0.05;
|
||||
camera.rotation.x += (-targetY - camera.rotation.x) * 0.05;
|
||||
|
||||
particleSystem.rotation.y += 0.0001;
|
||||
particleSystem.rotation.x += 0.00005;
|
||||
|
||||
nodeMeshes.forEach((mesh, i) => {
|
||||
const pulse = Math.sin(t * 2 + i * 0.5) * 0.15;
|
||||
mesh.scale.setScalar(1 + pulse);
|
||||
});
|
||||
|
||||
edgeObjects.forEach(line => {
|
||||
const mat = line.material;
|
||||
mat.dashSize = 1.5 + Math.sin(t) * 0.3;
|
||||
});
|
||||
|
||||
scene.children.forEach(child => {
|
||||
if (child instanceof THREE.Points && child.userData.speedX) {
|
||||
child.rotation.x += child.userData.speedX;
|
||||
child.rotation.y += child.userData.speedY;
|
||||
}
|
||||
});
|
||||
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
|
||||
function updateMetrics() {
|
||||
document.getElementById('metric-nodes').textContent = NODES.length;
|
||||
document.getElementById('metric-links').textContent = EDGES.length;
|
||||
document.getElementById('nodes-bar').style.width = (NODES.length / 20 * 100) + '%';
|
||||
document.getElementById('links-bar').style.width = (EDGES.length / 30 * 100) + '%';
|
||||
|
||||
const clusters = 3;
|
||||
document.getElementById('metric-clusters').textContent = clusters;
|
||||
document.getElementById('clusters-bar').style.width = (clusters / 5 * 100) + '%';
|
||||
|
||||
const coreCount = NODES.filter(n => n.importance >= 0.86).length;
|
||||
const bridgeCount = NODES.filter(n => n.importance >= 0.7 && n.importance < 0.86).length;
|
||||
document.getElementById('core-nodes').textContent = coreCount;
|
||||
document.getElementById('bridge-nodes').textContent = bridgeCount;
|
||||
document.getElementById('active-nodes-val').textContent = 'ACTIVE: ' + NODES.length;
|
||||
}
|
||||
|
||||
function updateNodeList() {
|
||||
const list = document.getElementById('node-list');
|
||||
list.innerHTML = '';
|
||||
NODES.forEach(node => {
|
||||
const color = '#' + TYPE_COLORS[node.type].toString(16).padStart(6, '0');
|
||||
const div = document.createElement('div');
|
||||
div.className = 'flash-row p-2 flex justify-between items-center border-b border-cyan-500/10';
|
||||
div.innerHTML = `
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-1.5 h-1.5 rounded-full" style="background: ${color}; box-shadow: 0 0 4px ${color};"></div>
|
||||
<span class="text-cyan-300 truncate max-w-[100px]">${node.name}</span>
|
||||
</div>
|
||||
<span class="opacity-50 text-[9px]">${Math.round(node.importance * 100)}%</span>
|
||||
`;
|
||||
div.onclick = () => selectNode(node.id);
|
||||
list.appendChild(div);
|
||||
});
|
||||
}
|
||||
|
||||
function selectNode(nodeId) {
|
||||
selectedNodeId = nodeId;
|
||||
const node = NODES.find(n => n.id === nodeId);
|
||||
if (!node) return;
|
||||
|
||||
document.getElementById('selected-val').textContent = 'SELECTED: ' + node.name;
|
||||
|
||||
const color = '#' + TYPE_COLORS[node.type].toString(16).padStart(6, '0');
|
||||
const detail = document.getElementById('node-detail');
|
||||
detail.innerHTML = `
|
||||
<div class="mb-3 text-center">
|
||||
<div class="text-[9px] opacity-50 uppercase tracking-widest">${TYPE_LABELS[node.type]}</div>
|
||||
<div class="text-lg font-bold text-cyan-100 tracking-wider">${node.name}</div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<div class="flex justify-between"><span class="opacity-60">IMPORTANCE</span><span class="text-cyan-300">${Math.round(node.importance * 100)}%</span></div>
|
||||
<div class="flex justify-between"><span class="opacity-60">DESCRIPTION</span></div>
|
||||
<div class="text-cyan-300/80 text-[10px] pl-2 border-l border-cyan-500/30">${node.description}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const conns = EDGES.filter(e => e.source === nodeId || e.target === nodeId);
|
||||
const list = document.getElementById('connections-list');
|
||||
list.innerHTML = '';
|
||||
if (conns.length === 0) {
|
||||
list.innerHTML = '<div class="text-center text-cyan-500/50 py-4">NO CONNECTIONS</div>';
|
||||
} else {
|
||||
conns.forEach(conn => {
|
||||
const otherId = conn.source === nodeId ? conn.target : conn.source;
|
||||
const other = NODES.find(n => n.id === otherId);
|
||||
const otherColor = '#' + TYPE_COLORS[other?.type || 'knowledge'].toString(16).padStart(6, '0');
|
||||
const div = document.createElement('div');
|
||||
div.className = 'border-b border-cyan-500/10 pb-1 mb-1';
|
||||
div.innerHTML = `
|
||||
<div class="flex items-center gap-1 text-[9px]">
|
||||
<span class="text-cyan-500">${conn.relation}</span>
|
||||
<span class="opacity-30">→</span>
|
||||
<span class="w-2 h-2 rounded-full inline-block" style="background: ${otherColor}; box-shadow: 0 0 4px ${otherColor};"></span>
|
||||
<span class="text-cyan-300">${other?.name || ''}</span>
|
||||
</div>
|
||||
`;
|
||||
list.appendChild(div);
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('detail-panel').classList.add('active');
|
||||
addLog('>> Node selected: ' + node.name);
|
||||
}
|
||||
|
||||
function addLog(msg) {
|
||||
const terminal = document.getElementById('brain-terminal');
|
||||
if (!terminal) return;
|
||||
const div = document.createElement('div');
|
||||
div.className = 'border-l-2 border-cyan-800 pl-2 py-0.5';
|
||||
div.innerHTML = `<span class="text-[10px] text-cyan-600 mr-2 font-bold">[${new Date().toLocaleTimeString('en-US', { hour12: false })}]</span><span class="text-xs font-mono text-cyan-300/80">${msg}</span>`;
|
||||
terminal.appendChild(div);
|
||||
if (terminal.children.length > 15) terminal.removeChild(terminal.firstChild);
|
||||
terminal.scrollTop = terminal.scrollHeight;
|
||||
}
|
||||
|
||||
function updateActivityGraph() {
|
||||
const bars = document.querySelectorAll('#activity-graph .sys-bar');
|
||||
bars.forEach(bar => {
|
||||
bar.style.height = Math.floor(Math.random() * 80 + 10) + '%';
|
||||
});
|
||||
}
|
||||
|
||||
setInterval(updateActivityGraph, 500);
|
||||
|
||||
document.addEventListener('mousemove', (e) => {
|
||||
const container = document.getElementById('brain-canvas-container');
|
||||
if (!container) return;
|
||||
const rect = container.getBoundingClientRect();
|
||||
if (e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom) {
|
||||
const x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
|
||||
const y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
|
||||
|
||||
const raycaster = new THREE.Raycaster();
|
||||
raycaster.setFromCamera({ x, y }, camera);
|
||||
const intersects = raycaster.intersectObjects(scene.children, true);
|
||||
|
||||
let found = null;
|
||||
for (const hit of intersects) {
|
||||
let obj = hit.object;
|
||||
while (obj) {
|
||||
if (obj.userData?.nodeId) { found = obj.userData.nodeId; break; }
|
||||
obj = obj.parent;
|
||||
}
|
||||
if (found) break;
|
||||
}
|
||||
hoveredNodeId = found;
|
||||
container.style.cursor = found ? 'pointer' : 'grab';
|
||||
}
|
||||
});
|
||||
|
||||
init();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
752
frontend/prototypes/index_backup.html
Normal file
752
frontend/prototypes/index_backup.html
Normal file
@@ -0,0 +1,752 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>J.A.R.V.I.S. Interface</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;700;900&family=Share+Tech+Mono&display=swap"
|
||||
rel="stylesheet">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
||||
<style>
|
||||
:root {
|
||||
--jarvis-blue: #00f3ff;
|
||||
--jarvis-blue-dim: rgba(0, 243, 255, 0.1);
|
||||
--jarvis-alert: #ff3333;
|
||||
--bg-color: #020408;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--jarvis-blue);
|
||||
font-family: 'Orbitron', sans-serif;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
/* Scanline effect */
|
||||
body::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: linear-gradient(to bottom,
|
||||
rgba(255, 255, 255, 0),
|
||||
rgba(255, 255, 255, 0) 50%,
|
||||
rgba(0, 0, 0, 0.1) 50%,
|
||||
rgba(0, 0, 0, 0.1));
|
||||
background-size: 100% 3px;
|
||||
pointer-events: none;
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
/* Vignette */
|
||||
.vignette {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: radial-gradient(circle, transparent 50%, rgba(0, 0, 0, 0.95) 100%);
|
||||
pointer-events: none;
|
||||
z-index: 40;
|
||||
}
|
||||
|
||||
/* Text & Border Utilities */
|
||||
.text-glow {
|
||||
text-shadow: 0 0 5px var(--jarvis-blue), 0 0 10px var(--jarvis-blue);
|
||||
}
|
||||
|
||||
.text-dim {
|
||||
color: rgba(0, 243, 255, 0.6);
|
||||
}
|
||||
|
||||
/* Tech Panels */
|
||||
.tech-panel {
|
||||
background: rgba(0, 15, 30, 0.7);
|
||||
border-left: 1px solid rgba(0, 243, 255, 0.3);
|
||||
border-right: 1px solid rgba(0, 243, 255, 0.3);
|
||||
position: relative;
|
||||
backdrop-filter: blur(4px);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.tech-panel:hover {
|
||||
border-color: rgba(0, 243, 255, 0.8);
|
||||
background: rgba(0, 20, 40, 0.8);
|
||||
}
|
||||
|
||||
/* Corner accents */
|
||||
.tech-panel::before,
|
||||
.tech-panel::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border: 1px solid var(--jarvis-blue);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.tech-panel::before {
|
||||
top: -1px;
|
||||
left: -1px;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.tech-panel::after {
|
||||
bottom: -1px;
|
||||
right: -1px;
|
||||
border-left: none;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
/* Tech Border Box */
|
||||
.tech-border {
|
||||
clip-path: polygon(10px 0,
|
||||
100% 0,
|
||||
100% calc(100% - 10px),
|
||||
calc(100% - 10px) 100%,
|
||||
0 100%,
|
||||
0 10px);
|
||||
border-top: 1px solid var(--jarvis-blue-dim);
|
||||
}
|
||||
|
||||
/* Scrollbars */
|
||||
::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--jarvis-blue);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
@keyframes pulse-status {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.6;
|
||||
text-shadow: 0 0 2px var(--jarvis-blue);
|
||||
box-shadow: 0 0 5px var(--jarvis-blue-dim);
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
text-shadow: 0 0 15px var(--jarvis-blue);
|
||||
box-shadow: 0 0 15px var(--jarvis-blue-dim);
|
||||
}
|
||||
}
|
||||
|
||||
.status-pulse {
|
||||
animation: pulse-status 3s infinite ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes flash-row {
|
||||
0% {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
50% {
|
||||
background-color: rgba(0, 243, 255, 0.15);
|
||||
}
|
||||
|
||||
100% {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.flash-row:hover {
|
||||
animation: flash-row 0.8s infinite;
|
||||
cursor: pointer;
|
||||
background: rgba(0, 243, 255, 0.05);
|
||||
}
|
||||
|
||||
/* Smooth Bar Transition */
|
||||
.sys-bar {
|
||||
transition: width 0.5s ease-out, height 0.3s ease-out;
|
||||
}
|
||||
|
||||
/* Canvas Positioning */
|
||||
#canvas-container {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -55%);
|
||||
z-index: 10;
|
||||
width: 800px;
|
||||
height: 800px;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="vignette"></div>
|
||||
|
||||
<!-- MAIN UI WRAPPER -->
|
||||
<div class="relative z-30 h-full w-full flex flex-col p-4 md:p-6 pointer-events-none">
|
||||
|
||||
<!-- HEADER -->
|
||||
<header class="flex justify-between items-start pointer-events-auto mb-2 border-b border-cyan-500/20 pb-2">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<div class="w-2 h-2 bg-cyan-400 rounded-full mb-1"></div>
|
||||
<div class="h-8 w-px bg-cyan-500/50"></div>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-4xl font-bold tracking-[0.15em] text-glow font-orbitron">J.A.R.V.I.S.</h1>
|
||||
<div class="flex items-center gap-2 mt-1">
|
||||
<div class="h-1 w-20 bg-cyan-500/30 overflow-hidden">
|
||||
<div class="h-full bg-cyan-400 w-1/2 animate-pulse"></div>
|
||||
</div>
|
||||
<div class="text-[9px] tracking-[0.3em] opacity-70">NEURAL INTERFACE V5.1</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-right">
|
||||
<div class="flex items-center justify-end gap-3 mb-1">
|
||||
<span class="text-[10px] tracking-widest opacity-60">LINK STATUS</span>
|
||||
<span id="connection-status"
|
||||
class="font-bold text-xs status-pulse bg-cyan-900/40 px-3 py-1 rounded-sm border border-cyan-500/30">DISCONNECTED</span>
|
||||
</div>
|
||||
<div class="flex justify-end items-baseline gap-2">
|
||||
<span class="text-[10px] opacity-50">SYS_TIME</span>
|
||||
<div id="time-display" class="font-mono text-xl tracking-wider text-cyan-200">00:00:00</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- MAIN GRID LAYOUT -->
|
||||
<div class="flex-grow grid grid-cols-1 md:grid-cols-4 gap-6 pointer-events-auto h-0 min-h-0 mt-2">
|
||||
|
||||
<!-- LEFT COLUMN: DEVICES & IOT -->
|
||||
<div class="flex flex-col gap-4 h-full overflow-hidden">
|
||||
|
||||
<!-- Server Status -->
|
||||
<div class="tech-panel p-4 tech-border">
|
||||
<h3 class="text-xs font-bold tracking-widest border-b border-cyan-500/30 pb-2 mb-3 text-cyan-300">
|
||||
SERVER CLUSTER LOAD</h3>
|
||||
<div class="space-y-4 font-mono text-[10px]">
|
||||
|
||||
<div>
|
||||
<div class="flex justify-between mb-1"><span class="opacity-70">MAIN_FRAME_01</span><span
|
||||
class="text-cyan-300" id="server-1-val">ONLINE</span></div>
|
||||
<div class="w-full bg-cyan-900/50 h-1">
|
||||
<div id="server-1-bar"
|
||||
class="sys-bar bg-cyan-400 h-full w-[34%] shadow-[0_0_8px_#00f3ff]"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="flex justify-between mb-1"><span class="opacity-70">DATA_CORE_ZETA</span><span
|
||||
class="text-cyan-300" id="server-2-val">PROCESSING</span></div>
|
||||
<div class="w-full bg-cyan-900/50 h-1">
|
||||
<div id="server-2-bar"
|
||||
class="sys-bar bg-cyan-400 h-full w-[89%] shadow-[0_0_8px_#00f3ff]"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="flex justify-between mb-1"><span class="opacity-70">STORAGE_ARRAY</span><span
|
||||
class="text-cyan-300" id="server-3-val">STABLE</span></div>
|
||||
<div class="w-full bg-cyan-900/50 h-1">
|
||||
<div id="server-3-bar"
|
||||
class="sys-bar bg-cyan-400 h-full w-[12%] shadow-[0_0_8px_#00f3ff]"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-2 pt-2">
|
||||
<div class="bg-cyan-900/20 p-2 border border-cyan-500/20 text-center">
|
||||
<div class="opacity-50 text-[9px]">TEMP</div>
|
||||
<div id="sys-temp" class="text-sm font-bold text-cyan-300">42°C</div>
|
||||
</div>
|
||||
<div class="bg-cyan-900/20 p-2 border border-cyan-500/20 text-center">
|
||||
<div class="opacity-50 text-[9px]">POWER</div>
|
||||
<div id="sys-power" class="text-sm font-bold text-cyan-300">98%</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Connected Devices -->
|
||||
<div class="tech-panel p-4 flex-grow overflow-hidden flex flex-col">
|
||||
<div class="flex justify-between items-center border-b border-cyan-500/30 pb-2 mb-2">
|
||||
<h3 class="text-xs font-bold tracking-widest text-cyan-300">CONNECTED DEVICES</h3>
|
||||
<span class="text-[9px] bg-cyan-500/20 px-1 rounded animate-pulse">SCANNING</span>
|
||||
</div>
|
||||
|
||||
<ul id="device-list"
|
||||
class="font-mono text-[11px] space-y-1 overflow-y-auto pr-1 flex-grow custom-scrollbar">
|
||||
<li
|
||||
class="flash-row p-2 flex justify-between items-center group border-b border-cyan-500/10 hover:border-cyan-500/50 transition-colors">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-1.5 h-1.5 bg-green-500 rounded-full"></div>
|
||||
<span>SMART_HOME_HUB</span>
|
||||
</div>
|
||||
<span class="opacity-50 text-[9px]">192.168.1.10</span>
|
||||
</li>
|
||||
<li
|
||||
class="flash-row p-2 flex justify-between items-center group border-b border-cyan-500/10 hover:border-cyan-500/50 transition-colors">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-1.5 h-1.5 bg-green-500 rounded-full"></div>
|
||||
<span>SECURITY_CAM_GRID</span>
|
||||
</div>
|
||||
<span class="device-status opacity-50 text-[9px]">ACTIVE</span>
|
||||
</li>
|
||||
<li
|
||||
class="flash-row p-2 flex justify-between items-center group border-b border-cyan-500/10 hover:border-cyan-500/50 transition-colors">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-1.5 h-1.5 bg-yellow-500 rounded-full animate-pulse"></div>
|
||||
<span>DRONE_SENTRY_04</span>
|
||||
</div>
|
||||
<span class="device-status opacity-50 text-[9px]">CHARGING</span>
|
||||
</li>
|
||||
<li
|
||||
class="flash-row p-2 flex justify-between items-center group border-b border-cyan-500/10 hover:border-cyan-500/50 transition-colors">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-1.5 h-1.5 bg-green-500 rounded-full"></div>
|
||||
<span>MAIN_GATE_LOCK</span>
|
||||
</div>
|
||||
<span class="opacity-50 text-[9px]">SECURE</span>
|
||||
</li>
|
||||
<li
|
||||
class="flash-row p-2 flex justify-between items-center group border-b border-cyan-500/10 hover:border-cyan-500/50 transition-colors">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-1.5 h-1.5 bg-red-500 rounded-full"></div>
|
||||
<span>EXT_PERIMETER_SENSORS</span>
|
||||
</div>
|
||||
<span class="device-status opacity-50 text-[9px]">ALERT</span>
|
||||
</li>
|
||||
<li
|
||||
class="flash-row p-2 flex justify-between items-center group border-b border-cyan-500/10 hover:border-cyan-500/50 transition-colors">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-1.5 h-1.5 bg-green-500 rounded-full"></div>
|
||||
<span>LAB_3D_PRINTER</span>
|
||||
</div>
|
||||
<span class="device-status opacity-50 text-[9px]">IDLE</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CENTER COLUMN: NEURAL VISUALIZATION & OUTPUT -->
|
||||
<div class="md:col-span-2 relative flex flex-col justify-end items-center">
|
||||
|
||||
<!-- Floating Status -->
|
||||
<div class="absolute top-10 w-full text-center pointer-events-none">
|
||||
<div id="ai-status"
|
||||
class="text-xl md:text-2xl font-bold tracking-[0.3em] text-glow uppercase text-cyan-100">
|
||||
NEURAL ENGINE: STANDBY
|
||||
</div>
|
||||
<div class="text-[10px] text-cyan-400/70 tracking-[0.5em] mt-2">AWAITING INPUT PARAMETERS</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Terminal (Bottom Center) -->
|
||||
<div
|
||||
class="terminal-window w-full h-56 bg-black/90 border-t border-cyan-500/50 rounded-tl-lg rounded-tr-lg overflow-hidden flex flex-col backdrop-blur-md shadow-[0_-5px_20px_rgba(0,243,255,0.1)]">
|
||||
<div
|
||||
class="bg-cyan-900/20 px-3 py-1.5 text-[10px] flex justify-between border-b border-cyan-500/30 items-center">
|
||||
<span class="tracking-widest text-cyan-400">CONSOLE_OUT // ROOT_ACCESS</span>
|
||||
<div class="flex gap-2 font-mono text-cyan-600">
|
||||
<span id="pid-val">PID:4092</span>
|
||||
<span id="mem-val">MEM:24%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="terminal-content"
|
||||
class="p-4 font-mono text-xs text-cyan-300/80 overflow-y-auto flex-grow space-y-1.5">
|
||||
<!-- Logs inject here -->
|
||||
<div class="opacity-50 border-l-2 border-cyan-800 pl-2">>> Initializing neural weights... OK
|
||||
</div>
|
||||
<div class="opacity-50 border-l-2 border-cyan-800 pl-2">>> Mounting device file system... OK
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RIGHT COLUMN: FILES & ALGORITHMS -->
|
||||
<div class="flex flex-col gap-4 h-full overflow-hidden">
|
||||
|
||||
<!-- Algorithm Status -->
|
||||
<div class="tech-panel p-4 flex-grow flex flex-col">
|
||||
<h3 class="text-xs font-bold tracking-widest border-b border-cyan-500/30 pb-2 mb-2 text-cyan-300">
|
||||
ACTIVE THREADS</h3>
|
||||
<div class="font-mono text-[11px] overflow-y-auto pr-1 flex-grow space-y-3">
|
||||
|
||||
<div class="mb-2">
|
||||
<div class="text-cyan-100 opacity-60 mb-1 text-[10px]">/PROCESS/VISION</div>
|
||||
<div class="pl-2 border-l border-cyan-500/30 space-y-1.5">
|
||||
<div class="flash-row flex items-center justify-between"><span
|
||||
class="text-cyan-400">object_recognition.py</span> <span
|
||||
class="text-[9px] bg-cyan-900 px-1 rounded">RUNNING</span></div>
|
||||
<div class="flash-row flex items-center justify-between"><span
|
||||
class="text-cyan-400">facial_scan_v2.exe</span> <span
|
||||
class="text-[9px] bg-cyan-900 px-1 rounded">IDLE</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="text-cyan-100 opacity-60 mb-1 text-[10px]">/PROCESS/LANGUAGE</div>
|
||||
<div class="pl-2 border-l border-cyan-500/30 space-y-1.5">
|
||||
<div class="flash-row flex items-center justify-between"><span
|
||||
class="text-cyan-400">nlp_core_en.lib</span> <span
|
||||
class="text-[9px] bg-cyan-900 px-1 rounded">LOADED</span></div>
|
||||
<div class="flash-row flex items-center justify-between"><span
|
||||
class="text-cyan-400">voice_synth_mod.dll</span> <span
|
||||
class="text-[9px] bg-cyan-900 px-1 rounded">ACTIVE</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="text-cyan-100 opacity-60 mb-1 text-[10px]">/PROCESS/NETWORK</div>
|
||||
<div class="pl-2 border-l border-cyan-500/30 space-y-1.5">
|
||||
<div class="flash-row flex items-center justify-between"><span
|
||||
class="text-cyan-400">packet_sniffer.sh</span> <span
|
||||
class="text-[9px] bg-cyan-900 px-1 rounded">BG</span></div>
|
||||
<div class="flash-row flex items-center justify-between"><span
|
||||
class="text-cyan-400">firewall_daemon</span> <span
|
||||
class="text-[9px] bg-cyan-900 px-1 rounded">ACTIVE</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Network Topology (Active Visualizer) -->
|
||||
<div class="tech-panel h-32 p-4 relative overflow-hidden flex flex-col justify-between">
|
||||
<div class="text-[10px] opacity-60 tracking-widest mb-1">NETWORK_TRAFFIC</div>
|
||||
<!-- Mock Graph Bars -->
|
||||
<div class="flex items-end justify-between h-full gap-1 pt-2" id="network-graph">
|
||||
<div class="sys-bar w-1 bg-cyan-600 h-[30%]"></div>
|
||||
<div class="sys-bar w-1 bg-cyan-500 h-[50%]"></div>
|
||||
<div class="sys-bar w-1 bg-cyan-400 h-[80%]"></div>
|
||||
<div class="sys-bar w-1 bg-cyan-300 h-[40%]"></div>
|
||||
<div class="sys-bar w-1 bg-cyan-400 h-[60%]"></div>
|
||||
<div class="sys-bar w-1 bg-cyan-500 h-[90%]"></div>
|
||||
<div class="sys-bar w-1 bg-cyan-600 h-[45%]"></div>
|
||||
<div class="sys-bar w-1 bg-cyan-500 h-[70%]"></div>
|
||||
<div class="sys-bar w-1 bg-cyan-400 h-[30%]"></div>
|
||||
<div class="sys-bar w-1 bg-cyan-300 h-[60%]"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- THREE.JS CONTAINER -->
|
||||
<div id="canvas-container"></div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
// --- CLOCK UTILITY ---
|
||||
function updateTime() {
|
||||
const now = new Date();
|
||||
const timeDisplay = document.getElementById('time-display');
|
||||
if (timeDisplay) {
|
||||
timeDisplay.innerText = now.toLocaleTimeString('en-US', { hour12: false });
|
||||
}
|
||||
}
|
||||
setInterval(updateTime, 1000);
|
||||
updateTime();
|
||||
|
||||
// --- THREE.JS ADVANCED NEURAL VISUALIZATION ---
|
||||
const container = document.getElementById('canvas-container');
|
||||
|
||||
if (container) {
|
||||
const scene = new THREE.Scene();
|
||||
// Subtler fog for deep space feel
|
||||
scene.fog = new THREE.FogExp2(0x020408, 0.003);
|
||||
|
||||
const camera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000);
|
||||
camera.position.z = 20;
|
||||
|
||||
const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
|
||||
renderer.setSize(800, 800);
|
||||
renderer.setPixelRatio(window.devicePixelRatio);
|
||||
container.appendChild(renderer.domElement);
|
||||
|
||||
// --- GROUPING ---
|
||||
const brainGroup = new THREE.Group();
|
||||
scene.add(brainGroup);
|
||||
|
||||
// 1. INNER CORE (Dense Wireframe Sphere)
|
||||
const coreGeo = new THREE.IcosahedronGeometry(4, 2);
|
||||
const coreMat = new THREE.MeshBasicMaterial({
|
||||
color: 0x00f3ff,
|
||||
wireframe: true,
|
||||
transparent: true,
|
||||
opacity: 0.15
|
||||
});
|
||||
const coreMesh = new THREE.Mesh(coreGeo, coreMat);
|
||||
brainGroup.add(coreMesh);
|
||||
|
||||
// 2. NEURAL CLOUD (Nodes & Lines)
|
||||
const particleCount = 400; // Increased count
|
||||
const cloudRadius = 9;
|
||||
const particlesData = [];
|
||||
let particlePositions = new Float32Array(particleCount * 3);
|
||||
|
||||
const pMaterial = new THREE.PointsMaterial({
|
||||
color: 0x00f3ff,
|
||||
size: 0.15,
|
||||
blending: THREE.AdditiveBlending,
|
||||
transparent: true,
|
||||
opacity: 0.8
|
||||
});
|
||||
|
||||
// Generate random points in a spherical shell
|
||||
for (let i = 0; i < particleCount; i++) {
|
||||
const r = cloudRadius + (Math.random() - 0.5) * 4; // Vary radius for depth
|
||||
const theta = Math.random() * Math.PI * 2;
|
||||
const phi = Math.acos((Math.random() * 2) - 1);
|
||||
|
||||
const x = r * Math.sin(phi) * Math.cos(theta);
|
||||
const y = r * Math.sin(phi) * Math.sin(theta);
|
||||
const z = r * Math.cos(phi);
|
||||
|
||||
particlePositions[i * 3] = x;
|
||||
particlePositions[i * 3 + 1] = y;
|
||||
particlePositions[i * 3 + 2] = z;
|
||||
|
||||
particlesData.push({
|
||||
velocity: new THREE.Vector3(
|
||||
(-1 + Math.random() * 2) * 0.02,
|
||||
(-1 + Math.random() * 2) * 0.02,
|
||||
(-1 + Math.random() * 2) * 0.02
|
||||
),
|
||||
originalPos: new THREE.Vector3(x, y, z)
|
||||
});
|
||||
}
|
||||
|
||||
const particlesGeo = new THREE.BufferGeometry();
|
||||
particlesGeo.setAttribute('position', new THREE.BufferAttribute(particlePositions, 3));
|
||||
const particleSystem = new THREE.Points(particlesGeo, pMaterial);
|
||||
brainGroup.add(particleSystem);
|
||||
|
||||
// Lines setup
|
||||
const maxConnections = particleCount;
|
||||
const linesGeo = new THREE.BufferGeometry();
|
||||
const linePositions = new Float32Array(maxConnections * maxConnections * 3);
|
||||
const lineColors = new Float32Array(maxConnections * maxConnections * 3);
|
||||
|
||||
linesGeo.setAttribute('position', new THREE.BufferAttribute(linePositions, 3));
|
||||
linesGeo.setAttribute('color', new THREE.BufferAttribute(lineColors, 3));
|
||||
|
||||
const lMaterial = new THREE.LineBasicMaterial({
|
||||
vertexColors: true,
|
||||
blending: THREE.AdditiveBlending,
|
||||
transparent: true,
|
||||
opacity: 0.2
|
||||
});
|
||||
|
||||
const linesMesh = new THREE.LineSegments(linesGeo, lMaterial);
|
||||
brainGroup.add(linesMesh);
|
||||
|
||||
// 3. ORBITAL RINGS (Fast moving outer data)
|
||||
const ringGroup = new THREE.Group();
|
||||
brainGroup.add(ringGroup);
|
||||
|
||||
function createOrbitalRing(radius, count, speedX, speedY) {
|
||||
const ringGeo = new THREE.BufferGeometry();
|
||||
const ringPos = new Float32Array(count * 3);
|
||||
for (let i = 0; i < count; i++) {
|
||||
const theta = (i / count) * Math.PI * 2;
|
||||
ringPos[i * 3] = Math.cos(theta) * radius;
|
||||
ringPos[i * 3 + 1] = Math.sin(theta) * radius;
|
||||
ringPos[i * 3 + 2] = (Math.random() - 0.5) * 0.5;
|
||||
}
|
||||
ringGeo.setAttribute('position', new THREE.BufferAttribute(ringPos, 3));
|
||||
const ringMat = new THREE.PointsMaterial({ color: 0x00f3ff, size: 0.1, transparent: true, opacity: 0.6 });
|
||||
const ring = new THREE.Points(ringGeo, ringMat);
|
||||
ring.userData = { speedX, speedY };
|
||||
return ring;
|
||||
}
|
||||
|
||||
const ring1 = createOrbitalRing(11, 100, 0.005, 0.01);
|
||||
const ring2 = createOrbitalRing(13, 80, -0.01, 0.002);
|
||||
ringGroup.add(ring1);
|
||||
ringGroup.add(ring2);
|
||||
|
||||
|
||||
// --- ANIMATION LOOP ---
|
||||
function animate() {
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
// 1. Rotate entire group
|
||||
brainGroup.rotation.y += 0.002;
|
||||
|
||||
// 2. Animate Core
|
||||
coreMesh.rotation.x -= 0.005;
|
||||
coreMesh.rotation.z += 0.002;
|
||||
|
||||
// 3. Animate Particles & Lines
|
||||
let vertexpos = 0;
|
||||
let colorpos = 0;
|
||||
let numConnected = 0;
|
||||
|
||||
// Update particles
|
||||
for (let i = 0; i < particleCount; i++) {
|
||||
// Jitter particles
|
||||
const p = particlesData[i];
|
||||
// Constrain to radius roughly
|
||||
particlePositions[i * 3] += p.velocity.x;
|
||||
particlePositions[i * 3 + 1] += p.velocity.y;
|
||||
particlePositions[i * 3 + 2] += p.velocity.z;
|
||||
|
||||
// Very simple bounce back logic
|
||||
if (Math.abs(particlePositions[i * 3] - p.originalPos.x) > 1) p.velocity.x = -p.velocity.x;
|
||||
if (Math.abs(particlePositions[i * 3 + 1] - p.originalPos.y) > 1) p.velocity.y = -p.velocity.y;
|
||||
if (Math.abs(particlePositions[i * 3 + 2] - p.originalPos.z) > 1) p.velocity.z = -p.velocity.z;
|
||||
}
|
||||
particlesGeo.attributes.position.needsUpdate = true;
|
||||
|
||||
// Connect lines (Optimize: only check subset or smaller distance)
|
||||
const connectionDist = 2.5;
|
||||
|
||||
for (let i = 0; i < particleCount; i++) {
|
||||
for (let j = i + 1; j < particleCount; j++) {
|
||||
const dx = particlePositions[i * 3] - particlePositions[j * 3];
|
||||
const dy = particlePositions[i * 3 + 1] - particlePositions[j * 3 + 1];
|
||||
const dz = particlePositions[i * 3 + 2] - particlePositions[j * 3 + 2];
|
||||
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
|
||||
if (dist < connectionDist) {
|
||||
const alpha = 1.0 - dist / connectionDist;
|
||||
|
||||
linePositions[vertexpos++] = particlePositions[i * 3];
|
||||
linePositions[vertexpos++] = particlePositions[i * 3 + 1];
|
||||
linePositions[vertexpos++] = particlePositions[i * 3 + 2];
|
||||
|
||||
linePositions[vertexpos++] = particlePositions[j * 3];
|
||||
linePositions[vertexpos++] = particlePositions[j * 3 + 1];
|
||||
linePositions[vertexpos++] = particlePositions[j * 3 + 2];
|
||||
|
||||
lineColors[colorpos++] = 0; lineColors[colorpos++] = alpha; lineColors[colorpos++] = 1;
|
||||
lineColors[colorpos++] = 0; lineColors[colorpos++] = alpha; lineColors[colorpos++] = 1;
|
||||
|
||||
numConnected++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
linesMesh.geometry.setDrawRange(0, numConnected * 2);
|
||||
linesMesh.geometry.attributes.position.needsUpdate = true;
|
||||
linesMesh.geometry.attributes.color.needsUpdate = true;
|
||||
|
||||
// 4. Animate Rings
|
||||
ring1.rotation.x += ring1.userData.speedX;
|
||||
ring1.rotation.y += ring1.userData.speedY;
|
||||
ring2.rotation.x += ring2.userData.speedX;
|
||||
ring2.rotation.y += ring2.userData.speedY;
|
||||
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
animate();
|
||||
}
|
||||
|
||||
|
||||
// --- MOCK DATA SIMULATION (The "Life" of the Dashboard) ---
|
||||
|
||||
// 1. Terminal Logger
|
||||
const terminal = document.getElementById('terminal-content');
|
||||
function addLog(message) {
|
||||
if (!terminal) return;
|
||||
const div = document.createElement('div');
|
||||
div.className = 'text-cyan-300 opacity-80 border-l-2 border-transparent hover:border-cyan-500 pl-2 py-0.5 animate-pulse-fast';
|
||||
div.innerHTML = `<span class="text-[10px] text-cyan-600 mr-2 font-bold">[${new Date().toLocaleTimeString('en-US', { hour12: false })}]</span> <span class="text-xs font-mono">${message}</span>`;
|
||||
terminal.appendChild(div);
|
||||
if (terminal.children.length > 20) terminal.removeChild(terminal.firstChild); // Keep DOM light
|
||||
terminal.scrollTop = terminal.scrollHeight;
|
||||
}
|
||||
|
||||
// 2. Update System Bars & Text
|
||||
function updateSystemStats() {
|
||||
// Server Load Bars
|
||||
document.getElementById('server-1-bar').style.width = Math.floor(Math.random() * 40 + 30) + '%'; // 30-70%
|
||||
document.getElementById('server-2-bar').style.width = Math.floor(Math.random() * 20 + 70) + '%'; // 70-90%
|
||||
document.getElementById('server-3-bar').style.width = Math.floor(Math.random() * 10 + 5) + '%'; // 5-15%
|
||||
|
||||
// Temp & Power
|
||||
document.getElementById('sys-temp').innerText = Math.floor(Math.random() * 5 + 40) + '°C';
|
||||
document.getElementById('sys-power').innerText = Math.floor(Math.random() * 3 + 96) + '%';
|
||||
|
||||
// Memory PID
|
||||
document.getElementById('mem-val').innerText = 'MEM:' + Math.floor(Math.random() * 10 + 20) + '%';
|
||||
}
|
||||
|
||||
// 3. Animate Network Graph
|
||||
function updateNetworkGraph() {
|
||||
const bars = document.querySelectorAll('#network-graph .sys-bar');
|
||||
bars.forEach(bar => {
|
||||
bar.style.height = Math.floor(Math.random() * 80 + 10) + '%';
|
||||
});
|
||||
}
|
||||
|
||||
// 4. Device Status Rotation
|
||||
function updateDeviceStatus() {
|
||||
const statuses = document.querySelectorAll('.device-status');
|
||||
const randomIdx = Math.floor(Math.random() * statuses.length);
|
||||
const el = statuses[randomIdx];
|
||||
|
||||
const states = ['ACTIVE', 'PINGING', 'SENDING', 'IDLE'];
|
||||
const newState = states[Math.floor(Math.random() * states.length)];
|
||||
|
||||
el.innerText = newState;
|
||||
if (newState === 'ACTIVE' || newState === 'SENDING') el.className = 'device-status text-cyan-300 text-[9px]';
|
||||
else el.className = 'device-status opacity-50 text-[9px]';
|
||||
}
|
||||
|
||||
// --- START SIMULATION ---
|
||||
setTimeout(() => {
|
||||
const statusEl = document.getElementById('connection-status');
|
||||
const aiStatusEl = document.getElementById('ai-status');
|
||||
|
||||
if (statusEl) {
|
||||
statusEl.innerText = "SECURE LINK ESTABLISHED";
|
||||
statusEl.classList.remove('text-red-500');
|
||||
statusEl.classList.add('text-cyan-300', 'bg-cyan-500/20', 'border-cyan-400');
|
||||
}
|
||||
|
||||
if (aiStatusEl) {
|
||||
aiStatusEl.innerHTML = "NEURAL ENGINE: <span class='text-cyan-300 drop-shadow-[0_0_10px_rgba(0,243,255,0.8)]'>ONLINE</span>";
|
||||
}
|
||||
|
||||
addLog(">> Handshake protocol complete.");
|
||||
addLog(">> Neural weights loaded into memory.");
|
||||
|
||||
// Start Interval Loops
|
||||
setInterval(() => {
|
||||
const logs = [
|
||||
"Optimizing neural pathways...", "Garbage collection active...",
|
||||
"Packet sniffing: 192.168.1.X...", "Updating heuristic models...",
|
||||
"Thermal throttling check... OK", "Syncing cloud replica...",
|
||||
"Analyzing biometric feed...", "Background process [PID:8821] started"
|
||||
];
|
||||
addLog(">> " + logs[Math.floor(Math.random() * logs.length)]);
|
||||
}, 2000);
|
||||
|
||||
setInterval(updateSystemStats, 1500);
|
||||
setInterval(updateNetworkGraph, 300); // Fast update for graph
|
||||
setInterval(updateDeviceStatus, 3000);
|
||||
|
||||
}, 1000);
|
||||
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user