Frontend uudelleenrakennettu: Astro-komponentit, Wasm pääsäikeessä, ei Workeria

Vanha frontend siirretty temp/. Uusi rakenne:
- StatusBar.astro, Terminal.astro, Editor.astro, Guide.astro
- global.css erillinen
- Wasm pääsäikeessä (ei Worker — yksinkertainen, debugattava)
- Tab-completion, dropdown, projektikortti, Monaco, GUIDE.md
- Ei tokenisointia eikä koodilaboratoriota

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jaakko Vanhala
2026-04-09 20:17:39 +03:00
parent e3fdb91ac5
commit a8c4af0975
9617 changed files with 996171 additions and 5349 deletions

View File

@@ -1,6 +0,0 @@
---
// AgentChat Placeholder
---
<div id="astro-agent-chat">
<!-- Chat component will go here -->
</div>

View File

@@ -0,0 +1,15 @@
<!-- Monaco Editor paneeli -->
<div id="panel-editor" class="panel">
<div style="display:flex;height:calc(100vh - 200px);gap:0;border:1px solid var(--border);border-radius:6px;overflow:hidden">
<div id="editor-filetree" style="width:200px;min-width:150px;background:var(--bg);border-right:1px solid var(--border);overflow-y:auto;font-family:'Courier New',monospace;font-size:13px">
<div style="padding:10px 12px;color:#8b949e;font-size:11px;text-transform:uppercase;letter-spacing:0.5px;border-bottom:1px solid var(--border)">Tiedostot</div>
<div id="editor-file-list" style="padding:4px 0">
<div style="padding:8px 16px;color:#8b949e;font-size:12px">Generoi projekti:<br><code style="color:var(--accent)">kpn project "..."</code></div>
</div>
</div>
<div style="flex:1;display:flex;flex-direction:column">
<div id="editor-tabs" style="display:flex;background:var(--bg);border-bottom:1px solid var(--border);min-height:35px;align-items:flex-end;padding:0 8px;gap:2px;overflow-x:auto"></div>
<div id="monaco-container" style="flex:1"></div>
</div>
</div>
</div>

View File

@@ -0,0 +1,6 @@
<!-- Opas-paneeli: ladataan GUIDE.md fetchillä -->
<div id="panel-guide" class="panel">
<div id="guide-content" style="max-width:800px;margin:0 auto;padding:20px;line-height:1.7;font-size:15px">
<p style="color:#8b949e">Ladataan opasta...</p>
</div>
</div>

View File

@@ -1,215 +0,0 @@
---
// 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>

View File

@@ -0,0 +1,15 @@
<!-- Hub-yhteys + laskentasolmun tila -->
<div class="status-bar">
<span class="status-group" title="Hub-yhteyden tila">
<span id="hub-dot" class="status-dot" style="background:#d29922"></span>
<span style="color:#8b949e">Hub:</span>
<span id="hub-label" style="color:#d29922">Yhdistetään...</span>
</span>
<span class="status-separator">│</span>
<span class="status-group">
<span id="compute-dot" class="status-dot" style="background:#30363d"></span>
<span style="color:#8b949e">Laskenta:</span>
<span id="compute-label" style="color:#8b949e">—</span>
<button id="compute-btn" class="btn btn-accent" title="Käynnistä kielimalli">Alusta</button>
</span>
</div>

View File

@@ -0,0 +1,10 @@
<!-- Pipeline-palkki + Terminaali + Input -->
<div id="pipeline-bar" class="pipeline-bar"></div>
<div id="terminal" class="terminal"></div>
<div class="terminal-input-row">
<span class="terminal-prompt">$</span>
<input id="term-input" class="terminal-input" type="text"
placeholder='kpn run coder "hello world in python"'
spellcheck="false" autocomplete="off">
<div id="term-dropdown" class="terminal-dropdown"></div>
</div>