diff --git a/network-poc/static/index.html b/network-poc/static/index.html
index f092cb4..bffe3ec 100644
--- a/network-poc/static/index.html
+++ b/network-poc/static/index.html
@@ -1103,6 +1103,7 @@
+
@@ -1819,12 +1820,64 @@
}
}
+ // Pipeline-vaiheiden seuranta ja visualisointi
+ const pipelineSteps = [];
+ function pipelineStep(agent, label, status, input, output) {
+ const step = { agent, label, status, input: input || '', output: output || '' };
+ // Päivitetään olemassaoleva tai lisätään uusi
+ const existing = pipelineSteps.find(s => s.label === label && s.status !== 'done');
+ if (existing && status !== 'done') {
+ Object.assign(existing, step);
+ } else if (status === 'done' && existing) {
+ existing.status = 'done';
+ existing.output = output || existing.output;
+ } else {
+ pipelineSteps.push(step);
+ }
+ renderPipelineSteps();
+ // Päivitetään agentin avatar tooltip
+ const avatarMap = { manager: 'avatar-kpn', coder: 'avatar-coder', tester: 'avatar-tester', qa: 'avatar-qa', data: 'avatar-data' };
+ const avatarId = avatarMap[agent];
+ if (avatarId) {
+ const el = document.getElementById(avatarId);
+ if (el) {
+ const truncOut = (output || '').substring(0, 200).replace(/\n/g, ' ');
+ el.title = `${label}\n${status === 'active' ? '⏳ Käsittelee...' : '✓ Valmis'}\n\nInput: ${(input || '').substring(0, 100)}...\nOutput: ${truncOut}...`;
+ }
+ }
+ }
+
+ function renderPipelineSteps() {
+ const container = document.getElementById('pipeline-steps');
+ if (!container) return;
+ if (pipelineSteps.length === 0) { container.style.display = 'none'; return; }
+ container.style.display = 'block';
+ container.innerHTML = pipelineSteps.map((s, i) => {
+ const colors = { manager: '#d29922', coder: '#3fb950', tester: '#58a6ff', qa: '#a371f7', data: '#d2a8ff' };
+ const color = colors[s.agent] || '#8b949e';
+ const icon = s.status === 'done' ? '✓' : s.status === 'active' ? '◷' : '◯';
+ const iconColor = s.status === 'done' ? '#3fb950' : s.status === 'active' ? '#d29922' : '#8b949e';
+ const arrow = i < pipelineSteps.length - 1 ? ' → ' : '';
+ // Tooltip: input/output esikatselu
+ const tip = esc(`${s.label}\nInput: ${(s.input || '').substring(0, 150)}\nOutput: ${(s.output || '').substring(0, 150)}`).replace(/\n/g, '
');
+ return `${icon} ${esc(s.label)}${arrow}`;
+ }).join('');
+ }
+
+ function pipelineClear() {
+ pipelineSteps.length = 0;
+ const container = document.getElementById('pipeline-steps');
+ if (container) container.style.display = 'none';
+ }
+
// Pipeline: manageri → koodari (per tiedosto) → testaaja → korjausluuppi
async function kpnPipeline(task) {
+ pipelineClear();
termLog(`━━━ Pipeline käynnistyy ━━━`);
// Vaihe 1: Manageri pilkkoo projektin tiedostoiksi
termLog(`\n[1] Manageri — projektin suunnittelu`);
+ pipelineStep('manager', 'Suunnittelu', 'active', task);
const managerPrompt = `You are a project manager. Break this task into individual source files.
For each file, write one line: FILENAME: description
List only the essential files (max 5). No explanations.
@@ -1832,6 +1885,7 @@ List only the essential files (max 5). No explanations.
Task: ${task}`;
const plan = await kpnRun(agentPrompts.manager.model, managerPrompt);
if (!plan) { termLog(' ✗ Pipeline keskeytyi (manageri)', '#f85149'); return; }
+ pipelineStep('manager', 'Suunnittelu', 'done', task, plan);
// Parsitaan tiedostolista: "FILENAME: description" -rivit
const fileList = plan.split('\n')
@@ -1861,6 +1915,7 @@ Task: ${task}`;
for (let i = 0; i < fileList.length; i++) {
const file = fileList[i];
termLog(`\n[${i + 2}] Koodari — ${esc(file.name)}`);
+ pipelineStep('coder', file.name, 'active', file.desc);
// Rakennetaan konteksti: aiemmin generoidut tiedostot
let context = '';
@@ -1879,6 +1934,7 @@ Project: ${task}`;
return;
}
generatedFiles[file.name] = code;
+ pipelineStep('coder', file.name, 'done', file.desc, code);
}
// Vaihe 3: Testaaja arvioi koko projektin
@@ -1887,15 +1943,18 @@ Project: ${task}`;
.join('\n\n');
termLog(`\n[${fileList.length + 2}] Testaaja — arviointi`);
+ pipelineStep('tester', 'Review', 'active', `${Object.keys(generatedFiles).length} tiedostoa`);
const reviewPrompt = `Review this project. List bugs or issues. Be brief.
If the code is correct, say "LGTM".
${allCode}`;
const review = await kpnRun(agentPrompts.tester.model, reviewPrompt);
+ pipelineStep('tester', 'Review', 'done', `${Object.keys(generatedFiles).length} tiedostoa`, review);
// Vaihe 4: Korjausluuppi — jos testaaja löysi ongelmia
if (review && !review.toLowerCase().includes('lgtm') && !review.toLowerCase().includes('looks good')) {
termLog(`\n[${fileList.length + 3}] Koodari — korjaukset`);
+ pipelineStep('coder', 'Korjaukset', 'active', review);
const fixPrompt = `Fix the issues found in the review.
Review feedback: ${review}
@@ -1904,9 +1963,12 @@ ${allCode}
Write the corrected code.`;
const fixedCode = await kpnRun(agentPrompts.coder.model, fixPrompt);
+ pipelineStep('coder', 'Korjaukset', 'done', review, fixedCode);
if (fixedCode) {
termLog(`\n[${fileList.length + 4}] Testaaja — uudelleenarviointi`);
- await kpnRun(agentPrompts.tester.model, `Review the corrected code briefly:\n${fixedCode}`);
+ pipelineStep('tester', 'Re-review', 'active', fixedCode);
+ const reReview = await kpnRun(agentPrompts.tester.model, `Review the corrected code briefly:\n${fixedCode}`);
+ pipelineStep('tester', 'Re-review', 'done', fixedCode, reReview);
}
}