feat: complete revolution architecture modernization
- Decoupled robust frontend into an Astro framework in `frontend/`. - Replaced direct WebSocket broadcast with Smart Routing to distribute workload only to idle capable nodes, preventing 503 errors and duplicate responses. - Rewrote WASM panic points (`unwrap()` handling) into panic-safe match blocks in qwen_coder.rs preventing Node Web Workers from crashing. - Integrated robust dynamic Three.js 3D visualization. - Resolved mermaid and THREE.js frontend hydration issues.
This commit is contained in:
6
network-poc/frontend/src/components/AgentChat.astro
Normal file
6
network-poc/frontend/src/components/AgentChat.astro
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
// AgentChat Placeholder
|
||||
---
|
||||
<div id="astro-agent-chat">
|
||||
<!-- Chat component will go here -->
|
||||
</div>
|
||||
215
network-poc/frontend/src/components/Network3D.astro
Normal file
215
network-poc/frontend/src/components/Network3D.astro
Normal file
@@ -0,0 +1,215 @@
|
||||
---
|
||||
// Network3D.astro - Visualizes the Agentic Network connections
|
||||
---
|
||||
<div id="network-canvas-container"></div>
|
||||
|
||||
<style>
|
||||
#network-canvas-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
opacity: 0.6;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import * as THREE from 'three';
|
||||
|
||||
const container = document.getElementById('network-canvas-container');
|
||||
if (container) {
|
||||
const scene = new THREE.Scene();
|
||||
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
|
||||
const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
|
||||
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
renderer.setPixelRatio(window.devicePixelRatio);
|
||||
container.appendChild(renderer.domElement);
|
||||
|
||||
// Kipinän brändivärit
|
||||
const palette = [
|
||||
new THREE.Color(0x58a6ff), // Sininen
|
||||
new THREE.Color(0xff6b00), // Oranssi
|
||||
new THREE.Color(0x2ea043), // Vihreä
|
||||
new THREE.Color(0xd29922), // Keltainen
|
||||
new THREE.Color(0x8b949e) // Harmaa
|
||||
];
|
||||
|
||||
// 1. Luodaan perusnoodit (partikkelit)
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
const particlesCount = 120;
|
||||
const posArray = new Float32Array(particlesCount * 3);
|
||||
const colorArray = new Float32Array(particlesCount * 3);
|
||||
|
||||
for (let i = 0; i < particlesCount; i++) {
|
||||
// Levitä pallomaisesti
|
||||
posArray[i*3] = (Math.random() - 0.5) * 12;
|
||||
posArray[i*3+1] = (Math.random() - 0.5) * 12;
|
||||
posArray[i*3+2] = (Math.random() - 0.5) * 12;
|
||||
|
||||
// Valitse satunnainen väri paletista
|
||||
const color = palette[Math.floor(Math.random() * palette.length)];
|
||||
colorArray[i*3] = color.r;
|
||||
colorArray[i*3+1] = color.g;
|
||||
colorArray[i*3+2] = color.b;
|
||||
}
|
||||
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(posArray, 3));
|
||||
geometry.setAttribute('color', new THREE.BufferAttribute(colorArray, 3));
|
||||
|
||||
const material = new THREE.PointsMaterial({
|
||||
size: 0.08,
|
||||
vertexColors: true,
|
||||
transparent: true,
|
||||
opacity: 0.8
|
||||
});
|
||||
|
||||
const particlesMesh = new THREE.Points(geometry, material);
|
||||
scene.add(particlesMesh);
|
||||
|
||||
// 2. Yhdistävät viivat (vain visualisointiin, himmeät)
|
||||
const lineMaterial = new THREE.LineBasicMaterial({
|
||||
vertexColors: true,
|
||||
transparent: true,
|
||||
opacity: 0.15,
|
||||
blending: THREE.AdditiveBlending
|
||||
});
|
||||
const lineGeometry = new THREE.BufferGeometry();
|
||||
lineGeometry.setAttribute('position', new THREE.BufferAttribute(posArray, 3));
|
||||
lineGeometry.setAttribute('color', new THREE.BufferAttribute(colorArray, 3));
|
||||
const linesMesh = new THREE.LineSegments(lineGeometry, lineMaterial);
|
||||
particlesMesh.add(linesMesh);
|
||||
|
||||
// 3. Räjähdykset ja kipinät (Sparks)
|
||||
const maxSparks = 1000;
|
||||
const sparkGeometry = new THREE.BufferGeometry();
|
||||
const sparkPositions = new Float32Array(maxSparks * 3);
|
||||
const sparkColors = new Float32Array(maxSparks * 3);
|
||||
const sparkVelocities = new Float32Array(maxSparks * 3);
|
||||
const sparkLifetimes = new Float32Array(maxSparks);
|
||||
|
||||
// Piilota aluksi kaikki kipinät kauas
|
||||
for(let i=0; i<maxSparks*3; i++) sparkPositions[i] = 1000;
|
||||
|
||||
sparkGeometry.setAttribute('position', new THREE.BufferAttribute(sparkPositions, 3));
|
||||
sparkGeometry.setAttribute('color', new THREE.BufferAttribute(sparkColors, 3));
|
||||
|
||||
const sparkMaterial = new THREE.PointsMaterial({
|
||||
size: 0.04,
|
||||
vertexColors: true,
|
||||
transparent: true,
|
||||
opacity: 1.0,
|
||||
blending: THREE.AdditiveBlending
|
||||
});
|
||||
const sparksMesh = new THREE.Points(sparkGeometry, sparkMaterial);
|
||||
particlesMesh.add(sparksMesh); // Lisätään pääverkon sisään jotta pyörii sen mukana
|
||||
|
||||
camera.position.z = 6;
|
||||
|
||||
function triggerExplosion() {
|
||||
// Valitse satunnainen noodisolmu lähtöpisteeksi
|
||||
const nodeIdx = Math.floor(Math.random() * particlesCount);
|
||||
const nx = posArray[nodeIdx * 3];
|
||||
const ny = posArray[nodeIdx * 3 + 1];
|
||||
const nz = posArray[nodeIdx * 3 + 2];
|
||||
|
||||
const nr = colorArray[nodeIdx * 3];
|
||||
const ng = colorArray[nodeIdx * 3 + 1];
|
||||
const nb = colorArray[nodeIdx * 3 + 2];
|
||||
|
||||
const sparkCount = 15 + Math.random() * 25; // 15-40 kipinää
|
||||
let spawned = 0;
|
||||
|
||||
for(let i=0; i<maxSparks; i++) {
|
||||
if(sparkLifetimes[i] <= 0) {
|
||||
sparkLifetimes[i] = 0.5 + Math.random() * 1.5; // Elinikä 0.5-2sek
|
||||
|
||||
// Alkusijainti on noodi itse
|
||||
sparkPositions[i*3] = nx;
|
||||
sparkPositions[i*3+1] = ny;
|
||||
sparkPositions[i*3+2] = nz;
|
||||
|
||||
// Satunnainen lentosuunta ja voima (pallomaisesti)
|
||||
const u = Math.random();
|
||||
const v = Math.random();
|
||||
const theta = u * 2.0 * Math.PI;
|
||||
const phi = Math.acos(2.0 * v - 1.0);
|
||||
const speed = 2.0 + Math.random() * 3.0; // Nopeus
|
||||
|
||||
sparkVelocities[i*3] = Math.sin(phi) * Math.cos(theta) * speed;
|
||||
sparkVelocities[i*3+1] = Math.sin(phi) * Math.sin(theta) * speed;
|
||||
sparkVelocities[i*3+2] = Math.cos(phi) * speed;
|
||||
|
||||
// Kipinän väri (noodin väri + satunnaista vaaleutta)
|
||||
sparkColors[i*3] = Math.min(1.0, nr + Math.random() * 0.5);
|
||||
sparkColors[i*3+1] = Math.min(1.0, ng + Math.random() * 0.5);
|
||||
sparkColors[i*3+2] = Math.min(1.0, nb + Math.random() * 0.5);
|
||||
|
||||
spawned++;
|
||||
if(spawned >= sparkCount) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Animaatiolooppi
|
||||
let lastTime = performance.now();
|
||||
|
||||
function animate(time: number) {
|
||||
requestAnimationFrame(animate);
|
||||
const dt = (time - lastTime) / 1000; // sekunteja
|
||||
lastTime = time;
|
||||
if (dt > 0.1) return; // vältä lagipiikkien aiheuttamat hyperhypyt
|
||||
|
||||
const elapsedTime = time / 1000;
|
||||
particlesMesh.rotation.y = elapsedTime * 0.05;
|
||||
particlesMesh.rotation.x = elapsedTime * 0.02;
|
||||
|
||||
// Arvo satunnaisia räjähdyksiä (n. 1 per sekunti)
|
||||
if (Math.random() < 1.0 * dt) {
|
||||
triggerExplosion();
|
||||
}
|
||||
|
||||
// Päivitä kipinät
|
||||
let sparksUpdated = false;
|
||||
for(let i=0; i<maxSparks; i++) {
|
||||
if(sparkLifetimes[i] > 0) {
|
||||
sparkLifetimes[i] -= dt;
|
||||
if(sparkLifetimes[i] <= 0) {
|
||||
sparkPositions[i*3] = 1000; // Piilota kuollessaan
|
||||
} else {
|
||||
// Liikuta nopeuden mukaan
|
||||
sparkPositions[i*3] += sparkVelocities[i*3] * dt;
|
||||
sparkPositions[i*3+1] += sparkVelocities[i*3+1] * dt;
|
||||
sparkPositions[i*3+2] += sparkVelocities[i*3+2] * dt;
|
||||
|
||||
// Painovoimaefekti / ilmanvastus
|
||||
sparkVelocities[i*3+1] -= 2.0 * dt; // Painovoima
|
||||
sparkVelocities[i*3] *= 0.98; // Ilmanvastus
|
||||
sparkVelocities[i*3+1] *= 0.98;
|
||||
sparkVelocities[i*3+2] *= 0.98;
|
||||
}
|
||||
sparksUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (sparksUpdated) {
|
||||
sparkGeometry.attributes.position.needsUpdate = true;
|
||||
sparkGeometry.attributes.color.needsUpdate = true;
|
||||
}
|
||||
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
|
||||
requestAnimationFrame((time) => { lastTime = time; requestAnimationFrame(animate); });
|
||||
|
||||
// Pakotettu uudelleenkokoyritys
|
||||
window.addEventListener('resize', () => {
|
||||
camera.aspect = window.innerWidth / window.innerHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user