AI_Zig/visualizer.html

128 lines
5.3 KiB
HTML

<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<title>Zig AI Visualizer</title>
<style>
body { margin: 0; background-color: #111; color: #eee; font-family: monospace; overflow: hidden; }
#info { position: absolute; top: 10px; left: 10px; background: rgba(0,0,0,0.7); padding: 10px; border-radius: 5px; pointer-events: none; }
h1 { margin: 0; font-size: 1.2em; color: #f4a261; }
.stat { font-size: 0.9em; color: #aaa; }
canvas { display: block; }
</style>
</head>
<body>
<div id="info">
<h1>Zig Neural Network</h1>
<div class="stat">Epoca: <span id="epoch">Waiting...</span></div>
<div class="stat">Loss: <span id="loss">Waiting...</span></div>
<div class="stat" style="font-size: 0.8em; margin-top:5px">Verde: Positivo | Rosso: Negativo</div>
</div>
<canvas id="netCanvas"></canvas>
<script>
const canvas = document.getElementById('netCanvas');
const ctx = canvas.getContext('2d');
const epochEl = document.getElementById('epoch');
const lossEl = document.getElementById('loss');
// Adatta il canvas alla finestra
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resize);
resize();
// Funzione per disegnare la rete
function drawNetwork(data) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Calcoliamo la struttura completa (Input Layer + Hidden Layers)
// Il JSON ha solo i layer "Dense", dobbiamo dedurre l'input layer dal primo strato
const layers = data.layers;
const structure = [layers[0].inputs]; // Aggiungiamo il numero di input come primo "strato visivo"
layers.forEach(l => structure.push(l.neurons));
const layerWidth = canvas.width / structure.length;
const maxNeurons = Math.max(...structure);
// Coordinate dei nodi per disegnare le linee dopo
let nodePositions = [];
// 1. Calcoliamo le posizioni dei nodi
structure.forEach((neuronCount, layerIdx) => {
const x = (layerIdx * layerWidth) + (layerWidth / 2);
const layerNodes = [];
for (let i = 0; i < neuronCount; i++) {
// Centriamo verticalmente
const y = (canvas.height / 2) - ((neuronCount * 60) / 2) + (i * 60);
layerNodes.push({x, y});
}
nodePositions.push(layerNodes);
});
// 2. Disegniamo le CONNESSIONI (Pesi)
// Iteriamo sui layer "reali" (dal secondo array di posizioni in poi)
layers.forEach((layer, lIdx) => {
const sourceNodes = nodePositions[lIdx]; // Nodi input per questo layer
const targetNodes = nodePositions[lIdx + 1]; // Nodi di questo layer
targetNodes.forEach((target, neuronIdx) => {
sourceNodes.forEach((source, inputIdx) => {
// Recuperiamo il peso dal JSON array piatto
// Indice = (NeuroneCorrente * NumeroInput) + InputCorrente
const weightIdx = (neuronIdx * layer.inputs) + inputIdx;
const weight = layer.weights[weightIdx];
ctx.beginPath();
ctx.moveTo(source.x, source.y);
ctx.lineTo(target.x, target.y);
// Stile Linea
const intensity = Math.min(Math.abs(weight), 2); // Cap a 2 per non esplodere
ctx.lineWidth = intensity * 2;
ctx.strokeStyle = weight > 0 ? `rgba(0, 255, 100, ${Math.min(Math.abs(weight), 1)})`
: `rgba(255, 50, 50, ${Math.min(Math.abs(weight), 1)})`;
ctx.stroke();
});
});
});
// 3. Disegniamo i NODI (Cerchi) sopra le linee
nodePositions.forEach((layerNodes, lIdx) => {
layerNodes.forEach((node, nIdx) => {
ctx.beginPath();
ctx.arc(node.x, node.y, 15, 0, Math.PI * 2);
ctx.fillStyle = '#2a2a2a';
ctx.fill();
ctx.strokeStyle = '#fff';
ctx.lineWidth = 2;
ctx.stroke();
});
});
}
// Loop principale
async function update() {
try {
// Aggiungiamo un timestamp per evitare che il browser usi la cache
const response = await fetch('network_state.json?t=' + new Date().getTime());
if (!response.ok) throw new Error("File non trovato");
const data = await response.json();
epochEl.innerText = data.epoch;
lossEl.innerText = data.loss;
drawNetwork(data);
} catch (e) {
console.log("In attesa di dati...", e);
}
}
setInterval(update, 20);
</script>
</body>
</html>