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); } }