Update src/App.jsx
Browse files- src/App.jsx +181 -67
src/App.jsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
import React, { useState, useEffect, useRef
|
| 2 |
import {
|
| 3 |
Activity,
|
| 4 |
Settings,
|
|
@@ -15,7 +15,9 @@ import {
|
|
| 15 |
Maximize,
|
| 16 |
Search,
|
| 17 |
ZoomIn,
|
| 18 |
-
ZoomOut
|
|
|
|
|
|
|
| 19 |
} from 'lucide-react';
|
| 20 |
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
|
| 21 |
|
|
@@ -135,46 +137,109 @@ const App = () => {
|
|
| 135 |
const [isTraining, setIsTraining] = useState(false);
|
| 136 |
const [trainingData, setTrainingData] = useState([]);
|
| 137 |
const [learningRate, setLearningRate] = useState(0.001);
|
|
|
|
|
|
|
|
|
|
| 138 |
const [isDraggingNode, setIsDraggingNode] = useState(null);
|
| 139 |
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
|
| 140 |
const [connectingFrom, setConnectingFrom] = useState(null);
|
| 141 |
|
| 142 |
const canvasRef = useRef(null);
|
| 143 |
|
| 144 |
-
// --- SIMULATION ENGINE ---
|
| 145 |
-
const runTrainingStep = (epoch) => {
|
| 146 |
-
const hasInput = nodes.some(n => n.type === 'INPUT');
|
| 147 |
-
const hasOutput = nodes.some(n => n.type === 'OUTPUT');
|
| 148 |
-
if (!hasInput || !hasOutput) return null;
|
| 149 |
|
| 150 |
-
|
| 151 |
-
|
|
|
|
| 152 |
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
}
|
| 161 |
-
|
| 162 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
});
|
| 164 |
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
|
| 169 |
-
const
|
| 170 |
-
const
|
| 171 |
-
const loss = baseLoss * Math.exp(-decay * epoch) + (0.2 / (epoch + 1)) + noise;
|
| 172 |
-
const acc = Math.min(0.99, 1 - (loss / baseLoss) + (Math.random() * 0.02));
|
| 173 |
|
| 174 |
return {
|
| 175 |
epoch,
|
| 176 |
-
loss:
|
| 177 |
-
accuracy:
|
| 178 |
};
|
| 179 |
};
|
| 180 |
|
|
@@ -184,14 +249,21 @@ const App = () => {
|
|
| 184 |
interval = setInterval(() => {
|
| 185 |
setTrainingData(prev => {
|
| 186 |
const nextEpoch = prev.length;
|
| 187 |
-
if (nextEpoch >
|
|
|
|
|
|
|
|
|
|
| 188 |
const step = runTrainingStep(nextEpoch);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
return [...prev, step];
|
| 190 |
});
|
| 191 |
-
},
|
| 192 |
}
|
| 193 |
return () => clearInterval(interval);
|
| 194 |
-
}, [isTraining, nodes, connections, learningRate]);
|
| 195 |
|
| 196 |
// --- INTERACTION HANDLERS ---
|
| 197 |
const handleWheel = (e) => {
|
|
@@ -205,7 +277,6 @@ const App = () => {
|
|
| 205 |
const newNode = {
|
| 206 |
id: `n${Date.now()}`,
|
| 207 |
type,
|
| 208 |
-
// Place node relative to current view center and zoom
|
| 209 |
x: (-viewOffset.x + 100) / zoom,
|
| 210 |
y: (-viewOffset.y + 100) / zoom,
|
| 211 |
config: { ...BLOCK_TYPES[type].config }
|
|
@@ -236,7 +307,6 @@ const App = () => {
|
|
| 236 |
if (connectingFrom) return;
|
| 237 |
setIsDraggingNode(id);
|
| 238 |
const node = nodes.find(n => n.id === id);
|
| 239 |
-
// Dragging logic needs to account for zoom
|
| 240 |
setDragOffset({ x: e.clientX / zoom - node.x, y: e.clientY / zoom - node.y });
|
| 241 |
setSelectedNodeId(id);
|
| 242 |
};
|
|
@@ -287,21 +357,24 @@ const App = () => {
|
|
| 287 |
const gridSize = 24 * zoom;
|
| 288 |
const gridOffsetX = viewOffset.x % gridSize;
|
| 289 |
const gridOffsetY = viewOffset.y % gridSize;
|
|
|
|
|
|
|
|
|
|
| 290 |
|
| 291 |
return (
|
| 292 |
<div className="flex flex-col h-screen w-screen bg-slate-950 text-slate-100 overflow-hidden font-sans">
|
| 293 |
{/* Header */}
|
| 294 |
<header className="h-16 border-b border-slate-800 flex items-center justify-between px-6 bg-slate-900/50 backdrop-blur-md z-20">
|
| 295 |
<div className="flex items-center gap-3">
|
| 296 |
-
<div className="p-2 bg-indigo-500 rounded-lg">
|
| 297 |
<Layers size={20} className="text-white" />
|
| 298 |
</div>
|
| 299 |
-
<h1 className="text-lg font-bold tracking-tight">Arch-Sim <span className="text-slate-500 font-normal">
|
| 300 |
</div>
|
| 301 |
|
| 302 |
<div className="flex items-center gap-4">
|
| 303 |
<div className="flex items-center bg-slate-800 rounded-full px-4 py-1.5 gap-3 border border-slate-700">
|
| 304 |
-
<span className="text-
|
| 305 |
<input
|
| 306 |
type="range"
|
| 307 |
min="0.0001"
|
|
@@ -309,9 +382,24 @@ const App = () => {
|
|
| 309 |
step="0.0001"
|
| 310 |
value={learningRate}
|
| 311 |
onChange={(e) => setLearningRate(parseFloat(e.target.value))}
|
| 312 |
-
className="w-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 313 |
/>
|
| 314 |
-
<span className="text-
|
| 315 |
</div>
|
| 316 |
|
| 317 |
<button
|
|
@@ -322,11 +410,11 @@ const App = () => {
|
|
| 322 |
setIsTraining(true);
|
| 323 |
}
|
| 324 |
}}
|
| 325 |
-
className={`flex items-center gap-2 px-5 py-2 rounded-lg font-medium transition-all ${
|
| 326 |
-
isTraining ? 'bg-red-500 hover:bg-red-600' : 'bg-emerald-500 hover:bg-emerald-600'
|
| 327 |
}`}
|
| 328 |
>
|
| 329 |
-
{isTraining ? <><Square size={
|
| 330 |
</button>
|
| 331 |
</div>
|
| 332 |
</header>
|
|
@@ -335,7 +423,7 @@ const App = () => {
|
|
| 335 |
{/* Sidebar: Blocks Palette */}
|
| 336 |
<aside className="w-64 border-r border-slate-800 bg-slate-900/30 p-4 flex flex-col gap-6 overflow-y-auto z-10">
|
| 337 |
<div>
|
| 338 |
-
<h3 className="text-xs font-bold text-slate-500 uppercase tracking-widest mb-4">
|
| 339 |
<div className="grid grid-cols-1 gap-2">
|
| 340 |
{Object.entries(BLOCK_TYPES).map(([key, type]) => (
|
| 341 |
<button
|
|
@@ -377,6 +465,14 @@ const App = () => {
|
|
| 377 |
}}
|
| 378 |
/>
|
| 379 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 380 |
{/* Viewport Transform Container */}
|
| 381 |
<div
|
| 382 |
className="absolute inset-0 pointer-events-none"
|
|
@@ -408,7 +504,7 @@ const App = () => {
|
|
| 408 |
<path
|
| 409 |
d={`M ${x1} ${y1} C ${x1 + dx} ${y1}, ${x2 - dx} ${y2}, ${x2} ${y2}`}
|
| 410 |
stroke="transparent"
|
| 411 |
-
strokeWidth={20 / zoom}
|
| 412 |
fill="none"
|
| 413 |
onClick={(e) => { e.stopPropagation(); deleteConnection(conn.id); }}
|
| 414 |
/>
|
|
@@ -436,13 +532,13 @@ const App = () => {
|
|
| 436 |
key={node.id}
|
| 437 |
style={{ left: node.x, top: node.y }}
|
| 438 |
onMouseDown={(e) => handleNodeMouseDown(e, node.id)}
|
| 439 |
-
className={`absolute w-40 p-4 rounded-xl border-2 transition-
|
| 440 |
-
isSelected ? 'border-indigo-500 bg-slate-800 shadow-[
|
| 441 |
}`}
|
| 442 |
>
|
| 443 |
<div className="flex justify-between items-start mb-2">
|
| 444 |
<div className={`w-8 h-8 rounded-lg ${type.color} flex items-center justify-center shadow-lg mb-2`}>
|
| 445 |
-
|
| 446 |
</div>
|
| 447 |
{isSelected && (
|
| 448 |
<button onClick={(e) => { e.stopPropagation(); deleteNode(node.id); }} className="text-slate-500 hover:text-red-400">
|
|
@@ -454,16 +550,14 @@ const App = () => {
|
|
| 454 |
<h4 className="text-xs font-bold uppercase tracking-wide truncate">{type.label}</h4>
|
| 455 |
<div className="text-[10px] text-slate-500 mb-4">{node.id}</div>
|
| 456 |
|
| 457 |
-
{/* Input Port */}
|
| 458 |
{type.inputs > 0 && (
|
| 459 |
<div
|
| 460 |
onMouseUp={(e) => endConnection(e, node.id)}
|
| 461 |
-
className={`absolute -left-3 top-1/2 -translate-y-1/2 w-6 h-6 rounded-full bg-slate-800 border-2 ${connectingFrom ? 'border-yellow-400 animate-pulse' : 'border-indigo-500'} flex items-center justify-center hover:bg-indigo-500 cursor-pointer z-30`}
|
| 462 |
>
|
| 463 |
<div className="w-1.5 h-1.5 bg-white rounded-full" />
|
| 464 |
</div>
|
| 465 |
)}
|
| 466 |
-
{/* Output Port */}
|
| 467 |
{type.outputs > 0 && (
|
| 468 |
<div
|
| 469 |
onMouseDown={(e) => startConnection(e, node.id)}
|
|
@@ -483,7 +577,6 @@ const App = () => {
|
|
| 483 |
<button
|
| 484 |
onClick={resetView}
|
| 485 |
className="p-3 bg-slate-900 border border-slate-700 rounded-lg hover:bg-slate-800 transition-colors shadow-xl text-slate-400 flex items-center gap-2"
|
| 486 |
-
title="Reset View"
|
| 487 |
>
|
| 488 |
<Maximize size={18} />
|
| 489 |
<span className="text-[10px] font-bold uppercase tracking-widest">{Math.round(zoom * 100)}%</span>
|
|
@@ -501,20 +594,30 @@ const App = () => {
|
|
| 501 |
<ZoomOut size={18} />
|
| 502 |
</button>
|
| 503 |
</div>
|
| 504 |
-
<div className="p-3 bg-slate-900/80 border border-slate-700 rounded-lg shadow-xl text-[10px] text-slate-400 flex items-center gap-2">
|
| 505 |
-
<Search size={14} /> Scroll to zoom, Drag background to pan
|
| 506 |
-
</div>
|
| 507 |
</div>
|
| 508 |
</div>
|
| 509 |
|
| 510 |
{/* Right Sidebar: Config & Monitoring */}
|
| 511 |
<aside className="w-80 border-l border-slate-800 bg-slate-900/50 flex flex-col z-10">
|
| 512 |
-
<div className="h-
|
| 513 |
-
<div className="flex items-center
|
| 514 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 515 |
</div>
|
| 516 |
|
| 517 |
-
<div className="flex-1 bg-slate-900 rounded-xl p-2 border border-slate-800 overflow-hidden">
|
| 518 |
{trainingData.length > 0 ? (
|
| 519 |
<ResponsiveContainer width="100%" height="100%">
|
| 520 |
<LineChart data={trainingData}>
|
|
@@ -529,8 +632,8 @@ const App = () => {
|
|
| 529 |
</LineChart>
|
| 530 |
</ResponsiveContainer>
|
| 531 |
) : (
|
| 532 |
-
<div className="h-full flex flex-col items-center justify-center text-slate-600 italic text-
|
| 533 |
-
Simulation idle.
|
| 534 |
</div>
|
| 535 |
)}
|
| 536 |
</div>
|
|
@@ -551,9 +654,9 @@ const App = () => {
|
|
| 551 |
</div>
|
| 552 |
</div>
|
| 553 |
|
| 554 |
-
<div className="
|
| 555 |
<div className="flex items-center gap-2 mb-6 text-slate-400 uppercase text-xs font-bold tracking-widest">
|
| 556 |
-
<Settings size={14} /> {selectedNode ? 'Block
|
| 557 |
</div>
|
| 558 |
|
| 559 |
{selectedNode ? (
|
|
@@ -588,9 +691,10 @@ const App = () => {
|
|
| 588 |
</div>
|
| 589 |
) : (
|
| 590 |
<div className="space-y-4">
|
| 591 |
-
<div className="p-4 bg-slate-800/30 rounded-xl border border-slate-700/50 text-xs text-slate-400 space-y-3">
|
| 592 |
-
<p><strong className="text-slate-
|
| 593 |
-
<p><strong className="text-slate-
|
|
|
|
| 594 |
</div>
|
| 595 |
</div>
|
| 596 |
)}
|
|
@@ -600,17 +704,27 @@ const App = () => {
|
|
| 600 |
|
| 601 |
<footer className="h-10 border-t border-slate-800 bg-slate-900/80 px-4 flex items-center justify-between text-[10px] text-slate-500 uppercase tracking-widest font-semibold">
|
| 602 |
<div className="flex gap-4">
|
| 603 |
-
<span>
|
| 604 |
-
<span>
|
| 605 |
-
<span>
|
| 606 |
</div>
|
| 607 |
-
<div>
|
| 608 |
</footer>
|
| 609 |
|
| 610 |
<style>{`
|
| 611 |
@keyframes dash {
|
| 612 |
to { stroke-dashoffset: -12; }
|
| 613 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 614 |
`}</style>
|
| 615 |
</div>
|
| 616 |
);
|
|
|
|
| 1 |
+
import React, { useState, useEffect, useRef } from 'react';
|
| 2 |
import {
|
| 3 |
Activity,
|
| 4 |
Settings,
|
|
|
|
| 15 |
Maximize,
|
| 16 |
Search,
|
| 17 |
ZoomIn,
|
| 18 |
+
ZoomOut,
|
| 19 |
+
Clock,
|
| 20 |
+
AlertTriangle
|
| 21 |
} from 'lucide-react';
|
| 22 |
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
|
| 23 |
|
|
|
|
| 137 |
const [isTraining, setIsTraining] = useState(false);
|
| 138 |
const [trainingData, setTrainingData] = useState([]);
|
| 139 |
const [learningRate, setLearningRate] = useState(0.001);
|
| 140 |
+
const [maxEpochs, setMaxEpochs] = useState(150);
|
| 141 |
+
const [simAlert, setSimAlert] = useState(null);
|
| 142 |
+
|
| 143 |
const [isDraggingNode, setIsDraggingNode] = useState(null);
|
| 144 |
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
|
| 145 |
const [connectingFrom, setConnectingFrom] = useState(null);
|
| 146 |
|
| 147 |
const canvasRef = useRef(null);
|
| 148 |
|
| 149 |
+
// --- ACCURATE SIMULATION ENGINE ---
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
|
| 151 |
+
const findPath = (startType, endType) => {
|
| 152 |
+
const startNodes = nodes.filter(n => n.type === startType);
|
| 153 |
+
const endNodes = nodes.filter(n => n.type === endType);
|
| 154 |
|
| 155 |
+
for (const start of startNodes) {
|
| 156 |
+
let visited = new Set();
|
| 157 |
+
let queue = [[start.id, []]];
|
| 158 |
+
|
| 159 |
+
while (queue.length > 0) {
|
| 160 |
+
const [currId, path] = queue.shift();
|
| 161 |
+
if (visited.has(currId)) continue;
|
| 162 |
+
visited.add(currId);
|
| 163 |
+
|
| 164 |
+
const currNode = nodes.find(n => n.id === currId);
|
| 165 |
+
const currentPath = [...path, currNode];
|
| 166 |
+
|
| 167 |
+
if (currNode.type === endType) return currentPath;
|
| 168 |
+
|
| 169 |
+
const nextConnections = connections.filter(c => c.from === currId);
|
| 170 |
+
for (const conn of nextConnections) {
|
| 171 |
+
queue.push([conn.to, currentPath]);
|
| 172 |
+
}
|
| 173 |
}
|
| 174 |
+
}
|
| 175 |
+
return null;
|
| 176 |
+
};
|
| 177 |
+
|
| 178 |
+
const runTrainingStep = (epoch) => {
|
| 179 |
+
// 1. Trace the graph to find the active computation path
|
| 180 |
+
const activePath = findPath('INPUT', 'OUTPUT');
|
| 181 |
+
|
| 182 |
+
if (!activePath) {
|
| 183 |
+
setSimAlert("Broken Computation Path: Input cannot reach Output.");
|
| 184 |
+
return null;
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
setSimAlert(null);
|
| 188 |
+
|
| 189 |
+
// 2. Calculate Architectural Metrics
|
| 190 |
+
let depth = activePath.length;
|
| 191 |
+
let totalCapacity = 0;
|
| 192 |
+
let vanishingGradientRisk = 1.0;
|
| 193 |
+
let bottleneck = Infinity;
|
| 194 |
+
|
| 195 |
+
activePath.forEach(node => {
|
| 196 |
+
const type = node.type;
|
| 197 |
+
const cfg = node.config;
|
| 198 |
+
|
| 199 |
+
// Capacity based on hidden dimensions (Parameter scaling)
|
| 200 |
+
if (cfg.dim) totalCapacity += cfg.dim;
|
| 201 |
+
if (cfg.hidden) totalCapacity += cfg.hidden;
|
| 202 |
+
if (cfg.heads) totalCapacity += (cfg.heads * cfg.headDim);
|
| 203 |
+
|
| 204 |
+
// Bottleneck detection (Information Theory)
|
| 205 |
+
if (cfg.dim) bottleneck = Math.min(bottleneck, cfg.dim);
|
| 206 |
+
|
| 207 |
+
// Gradient stability factors
|
| 208 |
+
if (type === 'LSTM') vanishingGradientRisk *= 0.95; // LSTM helps but still RNN
|
| 209 |
+
if (type === 'ATTN' || type === 'MAMBA') vanishingGradientRisk *= 0.99; // Very stable
|
| 210 |
+
if (type === 'NORM') vanishingGradientRisk = Math.min(1.0, vanishingGradientRisk * 1.5);
|
| 211 |
+
if (type === 'RESIDUAL') vanishingGradientRisk = Math.min(1.0, vanishingGradientRisk * 1.8);
|
| 212 |
});
|
| 213 |
|
| 214 |
+
// 3. Realistic Training Physics
|
| 215 |
+
// A. Learning Rate Explosion
|
| 216 |
+
if (learningRate > 0.04 && Math.random() > 0.95) {
|
| 217 |
+
setSimAlert("Gradient Explosion: Learning rate too high!");
|
| 218 |
+
return { epoch, loss: (Math.random() * 10 + 5).toFixed(4), accuracy: "0.0000" };
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
// B. Vanishing Gradients
|
| 222 |
+
// Probability of effective training decreases with depth unless stabilized
|
| 223 |
+
const effectiveLearningPower = learningRate * Math.pow(vanishingGradientRisk, depth / 2);
|
| 224 |
+
|
| 225 |
+
// C. Capacity vs Complexity
|
| 226 |
+
// If bottleneck < 128, the model "underfits" (cannot reach low loss)
|
| 227 |
+
const floorLoss = bottleneck < 128 ? 1.5 : 0.05;
|
| 228 |
+
|
| 229 |
+
// D. The Loss Function (Log-Space Convergence)
|
| 230 |
+
const prevLoss = trainingData.length > 0 ? parseFloat(trainingData[trainingData.length-1].loss) : 8.0;
|
| 231 |
+
|
| 232 |
+
// Convergence speed depends on architecture efficiency
|
| 233 |
+
const delta = (prevLoss - floorLoss) * effectiveLearningPower * 5;
|
| 234 |
+
const noise = (Math.random() - 0.5) * (0.02 / (epoch + 1));
|
| 235 |
|
| 236 |
+
const newLoss = Math.max(floorLoss, prevLoss - delta + noise);
|
| 237 |
+
const newAcc = Math.min(0.99, 1 - (newLoss / 8) + (Math.random() * 0.01));
|
|
|
|
|
|
|
| 238 |
|
| 239 |
return {
|
| 240 |
epoch,
|
| 241 |
+
loss: newLoss.toFixed(4),
|
| 242 |
+
accuracy: newAcc.toFixed(4)
|
| 243 |
};
|
| 244 |
};
|
| 245 |
|
|
|
|
| 249 |
interval = setInterval(() => {
|
| 250 |
setTrainingData(prev => {
|
| 251 |
const nextEpoch = prev.length;
|
| 252 |
+
if (nextEpoch >= maxEpochs) {
|
| 253 |
+
setIsTraining(false);
|
| 254 |
+
return prev;
|
| 255 |
+
}
|
| 256 |
const step = runTrainingStep(nextEpoch);
|
| 257 |
+
if (!step) {
|
| 258 |
+
setIsTraining(false);
|
| 259 |
+
return prev;
|
| 260 |
+
}
|
| 261 |
return [...prev, step];
|
| 262 |
});
|
| 263 |
+
}, 100);
|
| 264 |
}
|
| 265 |
return () => clearInterval(interval);
|
| 266 |
+
}, [isTraining, nodes, connections, learningRate, maxEpochs, trainingData]);
|
| 267 |
|
| 268 |
// --- INTERACTION HANDLERS ---
|
| 269 |
const handleWheel = (e) => {
|
|
|
|
| 277 |
const newNode = {
|
| 278 |
id: `n${Date.now()}`,
|
| 279 |
type,
|
|
|
|
| 280 |
x: (-viewOffset.x + 100) / zoom,
|
| 281 |
y: (-viewOffset.y + 100) / zoom,
|
| 282 |
config: { ...BLOCK_TYPES[type].config }
|
|
|
|
| 307 |
if (connectingFrom) return;
|
| 308 |
setIsDraggingNode(id);
|
| 309 |
const node = nodes.find(n => n.id === id);
|
|
|
|
| 310 |
setDragOffset({ x: e.clientX / zoom - node.x, y: e.clientY / zoom - node.y });
|
| 311 |
setSelectedNodeId(id);
|
| 312 |
};
|
|
|
|
| 357 |
const gridSize = 24 * zoom;
|
| 358 |
const gridOffsetX = viewOffset.x % gridSize;
|
| 359 |
const gridOffsetY = viewOffset.y % gridSize;
|
| 360 |
+
|
| 361 |
+
const currentEpoch = trainingData.length;
|
| 362 |
+
const trainingProgress = (currentEpoch / maxEpochs) * 100;
|
| 363 |
|
| 364 |
return (
|
| 365 |
<div className="flex flex-col h-screen w-screen bg-slate-950 text-slate-100 overflow-hidden font-sans">
|
| 366 |
{/* Header */}
|
| 367 |
<header className="h-16 border-b border-slate-800 flex items-center justify-between px-6 bg-slate-900/50 backdrop-blur-md z-20">
|
| 368 |
<div className="flex items-center gap-3">
|
| 369 |
+
<div className="p-2 bg-indigo-500 rounded-lg shadow-[0_0_15px_rgba(99,102,241,0.4)]">
|
| 370 |
<Layers size={20} className="text-white" />
|
| 371 |
</div>
|
| 372 |
+
<h1 className="text-lg font-bold tracking-tight">Arch-Sim <span className="text-slate-500 font-normal">v2.0 Accurate</span></h1>
|
| 373 |
</div>
|
| 374 |
|
| 375 |
<div className="flex items-center gap-4">
|
| 376 |
<div className="flex items-center bg-slate-800 rounded-full px-4 py-1.5 gap-3 border border-slate-700">
|
| 377 |
+
<span className="text-[10px] text-slate-400 uppercase font-bold tracking-wider">LR</span>
|
| 378 |
<input
|
| 379 |
type="range"
|
| 380 |
min="0.0001"
|
|
|
|
| 382 |
step="0.0001"
|
| 383 |
value={learningRate}
|
| 384 |
onChange={(e) => setLearningRate(parseFloat(e.target.value))}
|
| 385 |
+
className="w-20 accent-indigo-500"
|
| 386 |
+
/>
|
| 387 |
+
<span className="text-[11px] font-mono text-indigo-300 w-10">{learningRate}</span>
|
| 388 |
+
</div>
|
| 389 |
+
|
| 390 |
+
<div className="flex items-center bg-slate-800 rounded-full px-4 py-1.5 gap-3 border border-slate-700">
|
| 391 |
+
<Clock size={12} className="text-slate-400" />
|
| 392 |
+
<span className="text-[10px] text-slate-400 uppercase font-bold tracking-wider whitespace-nowrap">Max Epochs</span>
|
| 393 |
+
<input
|
| 394 |
+
type="range"
|
| 395 |
+
min="50"
|
| 396 |
+
max="1000"
|
| 397 |
+
step="50"
|
| 398 |
+
value={maxEpochs}
|
| 399 |
+
onChange={(e) => setMaxEpochs(parseInt(e.target.value))}
|
| 400 |
+
className="w-20 accent-indigo-500"
|
| 401 |
/>
|
| 402 |
+
<span className="text-[11px] font-mono text-indigo-300 w-8">{maxEpochs}</span>
|
| 403 |
</div>
|
| 404 |
|
| 405 |
<button
|
|
|
|
| 410 |
setIsTraining(true);
|
| 411 |
}
|
| 412 |
}}
|
| 413 |
+
className={`flex items-center gap-2 px-5 py-2 rounded-lg font-medium transition-all shadow-lg ${
|
| 414 |
+
isTraining ? 'bg-red-500 hover:bg-red-600 shadow-red-500/20' : 'bg-emerald-500 hover:bg-emerald-600 shadow-emerald-500/20'
|
| 415 |
}`}
|
| 416 |
>
|
| 417 |
+
{isTraining ? <><Square size={14} fill="currentColor" /> Stop</> : <><Play size={14} fill="currentColor" /> Train</>}
|
| 418 |
</button>
|
| 419 |
</div>
|
| 420 |
</header>
|
|
|
|
| 423 |
{/* Sidebar: Blocks Palette */}
|
| 424 |
<aside className="w-64 border-r border-slate-800 bg-slate-900/30 p-4 flex flex-col gap-6 overflow-y-auto z-10">
|
| 425 |
<div>
|
| 426 |
+
<h3 className="text-xs font-bold text-slate-500 uppercase tracking-widest mb-4">Architecture Blocks</h3>
|
| 427 |
<div className="grid grid-cols-1 gap-2">
|
| 428 |
{Object.entries(BLOCK_TYPES).map(([key, type]) => (
|
| 429 |
<button
|
|
|
|
| 465 |
}}
|
| 466 |
/>
|
| 467 |
|
| 468 |
+
{/* Alert Message */}
|
| 469 |
+
{simAlert && (
|
| 470 |
+
<div className="absolute top-6 left-1/2 -translate-x-1/2 bg-red-500/90 text-white px-4 py-2 rounded-full flex items-center gap-2 text-sm font-bold shadow-2xl z-50 animate-bounce">
|
| 471 |
+
<AlertTriangle size={16} />
|
| 472 |
+
{simAlert}
|
| 473 |
+
</div>
|
| 474 |
+
)}
|
| 475 |
+
|
| 476 |
{/* Viewport Transform Container */}
|
| 477 |
<div
|
| 478 |
className="absolute inset-0 pointer-events-none"
|
|
|
|
| 504 |
<path
|
| 505 |
d={`M ${x1} ${y1} C ${x1 + dx} ${y1}, ${x2 - dx} ${y2}, ${x2} ${y2}`}
|
| 506 |
stroke="transparent"
|
| 507 |
+
strokeWidth={20 / zoom}
|
| 508 |
fill="none"
|
| 509 |
onClick={(e) => { e.stopPropagation(); deleteConnection(conn.id); }}
|
| 510 |
/>
|
|
|
|
| 532 |
key={node.id}
|
| 533 |
style={{ left: node.x, top: node.y }}
|
| 534 |
onMouseDown={(e) => handleNodeMouseDown(e, node.id)}
|
| 535 |
+
className={`absolute w-40 p-4 rounded-xl border-2 transition-all cursor-move select-none pointer-events-auto ${
|
| 536 |
+
isSelected ? 'border-indigo-500 bg-slate-800 shadow-[0_0_35px_rgba(99,102,241,0.3)]' : 'border-slate-800 bg-slate-900/90'
|
| 537 |
}`}
|
| 538 |
>
|
| 539 |
<div className="flex justify-between items-start mb-2">
|
| 540 |
<div className={`w-8 h-8 rounded-lg ${type.color} flex items-center justify-center shadow-lg mb-2`}>
|
| 541 |
+
<Cpu size={14} />
|
| 542 |
</div>
|
| 543 |
{isSelected && (
|
| 544 |
<button onClick={(e) => { e.stopPropagation(); deleteNode(node.id); }} className="text-slate-500 hover:text-red-400">
|
|
|
|
| 550 |
<h4 className="text-xs font-bold uppercase tracking-wide truncate">{type.label}</h4>
|
| 551 |
<div className="text-[10px] text-slate-500 mb-4">{node.id}</div>
|
| 552 |
|
|
|
|
| 553 |
{type.inputs > 0 && (
|
| 554 |
<div
|
| 555 |
onMouseUp={(e) => endConnection(e, node.id)}
|
| 556 |
+
className={`absolute -left-3 top-1/2 -translate-y-1/2 w-6 h-6 rounded-full bg-slate-800 border-2 ${connectingFrom ? 'border-yellow-400 animate-pulse scale-125' : 'border-indigo-500'} flex items-center justify-center hover:bg-indigo-500 cursor-pointer z-30 transition-transform`}
|
| 557 |
>
|
| 558 |
<div className="w-1.5 h-1.5 bg-white rounded-full" />
|
| 559 |
</div>
|
| 560 |
)}
|
|
|
|
| 561 |
{type.outputs > 0 && (
|
| 562 |
<div
|
| 563 |
onMouseDown={(e) => startConnection(e, node.id)}
|
|
|
|
| 577 |
<button
|
| 578 |
onClick={resetView}
|
| 579 |
className="p-3 bg-slate-900 border border-slate-700 rounded-lg hover:bg-slate-800 transition-colors shadow-xl text-slate-400 flex items-center gap-2"
|
|
|
|
| 580 |
>
|
| 581 |
<Maximize size={18} />
|
| 582 |
<span className="text-[10px] font-bold uppercase tracking-widest">{Math.round(zoom * 100)}%</span>
|
|
|
|
| 594 |
<ZoomOut size={18} />
|
| 595 |
</button>
|
| 596 |
</div>
|
|
|
|
|
|
|
|
|
|
| 597 |
</div>
|
| 598 |
</div>
|
| 599 |
|
| 600 |
{/* Right Sidebar: Config & Monitoring */}
|
| 601 |
<aside className="w-80 border-l border-slate-800 bg-slate-900/50 flex flex-col z-10">
|
| 602 |
+
<div className="h-[55%] p-6 flex flex-col border-b border-slate-800 overflow-hidden">
|
| 603 |
+
<div className="flex items-center justify-between mb-4">
|
| 604 |
+
<div className="flex items-center gap-2 text-slate-400 uppercase text-xs font-bold tracking-widest">
|
| 605 |
+
<Activity size={14} /> Training Metrics
|
| 606 |
+
</div>
|
| 607 |
+
<div className="text-[10px] font-mono text-slate-500 uppercase">
|
| 608 |
+
Epoch {currentEpoch}/{maxEpochs}
|
| 609 |
+
</div>
|
| 610 |
+
</div>
|
| 611 |
+
|
| 612 |
+
{/* Progress Bar */}
|
| 613 |
+
<div className="w-full h-1.5 bg-slate-800 rounded-full mb-4 overflow-hidden">
|
| 614 |
+
<div
|
| 615 |
+
className="h-full bg-indigo-500 transition-all duration-300 shadow-[0_0_10px_rgba(99,102,241,0.5)]"
|
| 616 |
+
style={{ width: `${trainingProgress}%` }}
|
| 617 |
+
/>
|
| 618 |
</div>
|
| 619 |
|
| 620 |
+
<div className="flex-1 bg-slate-900 rounded-xl p-2 border border-slate-800 overflow-hidden relative">
|
| 621 |
{trainingData.length > 0 ? (
|
| 622 |
<ResponsiveContainer width="100%" height="100%">
|
| 623 |
<LineChart data={trainingData}>
|
|
|
|
| 632 |
</LineChart>
|
| 633 |
</ResponsiveContainer>
|
| 634 |
) : (
|
| 635 |
+
<div className="h-full flex flex-col items-center justify-center text-slate-600 italic text-xs text-center p-4">
|
| 636 |
+
Simulation idle. Ensure INPUT is linked to OUTPUT and click Train.
|
| 637 |
</div>
|
| 638 |
)}
|
| 639 |
</div>
|
|
|
|
| 654 |
</div>
|
| 655 |
</div>
|
| 656 |
|
| 657 |
+
<div className="flex-1 p-6 overflow-y-auto">
|
| 658 |
<div className="flex items-center gap-2 mb-6 text-slate-400 uppercase text-xs font-bold tracking-widest">
|
| 659 |
+
<Settings size={14} /> {selectedNode ? 'Block Parameters' : 'Sim Intelligence'}
|
| 660 |
</div>
|
| 661 |
|
| 662 |
{selectedNode ? (
|
|
|
|
| 691 |
</div>
|
| 692 |
) : (
|
| 693 |
<div className="space-y-4">
|
| 694 |
+
<div className="p-4 bg-slate-800/30 rounded-xl border border-slate-700/50 text-xs text-slate-400 space-y-3 leading-relaxed">
|
| 695 |
+
<p><strong className="text-slate-200 block mb-1">Graph Dependency:</strong> Training logic now uses a BFS trace. If a node isn't part of the direct path from Data In to Data Out, its parameters won't contribute to capacity.</p>
|
| 696 |
+
<p><strong className="text-slate-200 block mb-1">Architecture Physics:</strong> Adding layers increases depth, which increases the risk of Vanishing Gradients unless you add <span className="text-cyan-400">Residual</span> or <span className="text-slate-300">Norm</span> blocks.</p>
|
| 697 |
+
<p><strong className="text-slate-200 block mb-1">Convergence:</strong> The loss floor is determined by the "bottleneck" (the smallest hidden dimension in your path).</p>
|
| 698 |
</div>
|
| 699 |
</div>
|
| 700 |
)}
|
|
|
|
| 704 |
|
| 705 |
<footer className="h-10 border-t border-slate-800 bg-slate-900/80 px-4 flex items-center justify-between text-[10px] text-slate-500 uppercase tracking-widest font-semibold">
|
| 706 |
<div className="flex gap-4">
|
| 707 |
+
<span>Active Nodes: {nodes.length}</span>
|
| 708 |
+
<span>Valid Path: {findPath('INPUT', 'OUTPUT') ? 'YES' : 'NO'}</span>
|
| 709 |
+
<span>LR: {learningRate}</span>
|
| 710 |
</div>
|
| 711 |
+
<div>Scientific Arch-Sim Engine v2.0</div>
|
| 712 |
</footer>
|
| 713 |
|
| 714 |
<style>{`
|
| 715 |
@keyframes dash {
|
| 716 |
to { stroke-dashoffset: -12; }
|
| 717 |
}
|
| 718 |
+
input[type="range"]::-webkit-slider-thumb {
|
| 719 |
+
-webkit-appearance: none;
|
| 720 |
+
appearance: none;
|
| 721 |
+
width: 12px;
|
| 722 |
+
height: 12px;
|
| 723 |
+
background: #6366f1;
|
| 724 |
+
border-radius: 50%;
|
| 725 |
+
cursor: pointer;
|
| 726 |
+
box-shadow: 0 0 10px rgba(99,102,241,0.5);
|
| 727 |
+
}
|
| 728 |
`}</style>
|
| 729 |
</div>
|
| 730 |
);
|