FlameF0X commited on
Commit
9ef4115
·
verified ·
1 Parent(s): 2627715

Update src/App.jsx

Browse files
Files changed (1) hide show
  1. src/App.jsx +181 -67
src/App.jsx CHANGED
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect, useRef, useMemo } from 'react';
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
- let capacity = 0;
151
- let vanishingGradientRisk = 1.0;
 
152
 
153
- nodes.forEach(n => {
154
- if (n.type === 'EMBED') capacity += (n.config.dim || 512) * 0.1;
155
- if (n.type === 'ATTN') capacity += (n.config.heads * n.config.headDim) * 0.5;
156
- if (n.type === 'FFN') capacity += (n.config.mult * 512) * 0.3;
157
- if (n.type === 'LSTM') {
158
- capacity += (n.config.hidden || 512) * 0.4;
159
- vanishingGradientRisk *= 0.85;
 
 
 
 
 
 
 
 
 
 
 
160
  }
161
- if (n.type === 'NORM') vanishingGradientRisk = Math.min(1.0, vanishingGradientRisk + 0.2);
162
- if (n.type === 'RESIDUAL') vanishingGradientRisk = Math.min(1.0, vanishingGradientRisk + 0.3);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  });
164
 
165
- const trainability = Math.max(0.1, (capacity / 500) * vanishingGradientRisk);
166
- const noise = (Math.random() - 0.5) * 0.05;
167
- const lrFactor = learningRate > 0.01 ? (0.5 + Math.random()) : 1.0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
 
169
- const baseLoss = 8.0;
170
- const decay = 0.05 * trainability * (1 / lrFactor);
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: Math.max(0, loss).toFixed(4),
177
- accuracy: Math.max(0, acc).toFixed(4)
178
  };
179
  };
180
 
@@ -184,14 +249,21 @@ const App = () => {
184
  interval = setInterval(() => {
185
  setTrainingData(prev => {
186
  const nextEpoch = prev.length;
187
- if (nextEpoch > 150) { setIsTraining(false); return prev; }
 
 
 
188
  const step = runTrainingStep(nextEpoch);
 
 
 
 
189
  return [...prev, step];
190
  });
191
- }, 150);
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">v1.2</span></h1>
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-xs text-slate-400 uppercase font-semibold">LR</span>
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-24 accent-indigo-500"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313
  />
314
- <span className="text-xs font-mono w-12">{learningRate}</span>
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={16} fill="currentColor" /> Stop</> : <><Play size={16} fill="currentColor" /> Start Training</>}
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">Components</h3>
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} // Adjust hit area based on 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-shadow cursor-move select-none pointer-events-auto ${
440
- isSelected ? 'border-indigo-500 bg-slate-800 shadow-[0_0_25px_rgba(99,102,241,0.2)]' : 'border-slate-800 bg-slate-900/90'
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
- <Cpu size={14} />
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-1/2 p-6 flex flex-col border-b border-slate-800">
513
- <div className="flex items-center gap-2 mb-4 text-slate-400 uppercase text-xs font-bold tracking-widest">
514
- <Activity size={14} /> Training Metrics
 
 
 
 
 
 
 
 
 
 
 
 
 
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-sm text-center">
533
- Simulation idle.
534
  </div>
535
  )}
536
  </div>
@@ -551,9 +654,9 @@ const App = () => {
551
  </div>
552
  </div>
553
 
554
- <div className="h-1/2 p-6 overflow-y-auto">
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 Config' : 'Project Info'}
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-300">Navigation:</strong> Click and drag the background to pan. <span className="text-indigo-400 font-bold">Use mouse wheel to zoom.</span></p>
593
- <p><strong className="text-slate-300">Infinite Grid:</strong> The canvas background scales and repeats automatically as you navigate.</p>
 
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>Blocks: {nodes.length}</span>
604
- <span>Links: {connections.length}</span>
605
- <span>Zoom: {(zoom * 100).toFixed(0)}%</span>
606
  </div>
607
- <div>Educational Arch-Sim Environment</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
  );