diff --git a/network-poc/static/index.html b/network-poc/static/index.html index df19615..f092cb4 100644 --- a/network-poc/static/index.html +++ b/network-poc/static/index.html @@ -1819,27 +1819,111 @@ } } - // Pipeline: manageri → koodari → testaaja + // Pipeline: manageri → koodari (per tiedosto) → testaaja → korjausluuppi async function kpnPipeline(task) { termLog(`━━━ Pipeline käynnistyy ━━━`); - // Vaihe 1: Manageri analysoi - termLog(`\n[1/3] Manageri — tehtävän analyysi`); - const managerPrompt = `Analysoi seuraava ohjelmistokehitystehtävä ja kirjoita koodarille selkeä tekninen ohje mitä koodata. Vastaa lyhyesti.\n\nTehtävä: ${task}`; + // Vaihe 1: Manageri pilkkoo projektin tiedostoiksi + termLog(`\n[1] Manageri — projektin suunnittelu`); + 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. + +Task: ${task}`; const plan = await kpnRun(agentPrompts.manager.model, managerPrompt); if (!plan) { termLog(' ✗ Pipeline keskeytyi (manageri)', '#f85149'); return; } - // Vaihe 2: Koodari toteuttaa - termLog(`\n[2/3] Koodari — toteutus`); - const coderPrompt = `${plan}\n\nKirjoita koodi yllä olevan ohjeen mukaisesti.`; - const code = await kpnRun(agentPrompts.coder.model, coderPrompt); - if (!code) { termLog(' ✗ Pipeline keskeytyi (koodari)', '#f85149'); return; } + // Parsitaan tiedostolista: "FILENAME: description" -rivit + const fileList = plan.split('\n') + .map(line => line.trim()) + .filter(line => line.includes(':') && (line.includes('.') || line.includes('/'))) + .map(line => { + const [name, ...desc] = line.replace(/^[\d\.\-\*]+\s*/, '').split(':'); + return { name: name.trim().replace(/\*+/g, ''), desc: desc.join(':').trim() }; + }) + .filter(f => f.name.length > 0 && f.name.length < 50); - // Vaihe 3: Testaaja arvioi - termLog(`\n[3/3] Testaaja — arviointi`); - const testerPrompt = `Arvioi seuraava koodi lyhyesti. Onko siinä bugeja? Puuttuuko testejä? Anna arvosana 1-5.\n\nTehtävä: ${task}\n\nKoodi:\n${code}`; - await kpnRun(agentPrompts.tester.model, testerPrompt); + if (fileList.length === 0) { + // Fallback: manageri ei tuottanut tiedostolistaa, käytetään koko vastausta ohjeena + termLog(' Ei tiedostojakoa — generoidaan yhtenä kokonaisuutena'); + termLog(`\n[2] Koodari — toteutus`); + const code = await kpnRun(agentPrompts.coder.model, `${plan}\n\nWrite the code.`); + if (code) { + termLog(`\n━━━ Pipeline valmis ━━━`); + } + return; + } + termLog(` ${fileList.length} tiedostoa: ${fileList.map(f => f.name).join(', ')}`); + + // Vaihe 2: Koodari generoi tiedosto kerrallaan, konteksti ketjutetaan + const generatedFiles = {}; + for (let i = 0; i < fileList.length; i++) { + const file = fileList[i]; + termLog(`\n[${i + 2}] Koodari — ${esc(file.name)}`); + + // Rakennetaan konteksti: aiemmin generoidut tiedostot + let context = ''; + const prevFiles = Object.entries(generatedFiles); + if (prevFiles.length > 0) { + context = 'Already written files:\n' + prevFiles.map(([name, code]) => + `--- ${name} ---\n${code}` + ).join('\n\n') + '\n\n'; + } + + const coderPrompt = `${context}Write ONLY the file "${file.name}": ${file.desc} +Project: ${task}`; + const code = await kpnRun(agentPrompts.coder.model, coderPrompt); + if (!code) { + termLog(` ✗ Pipeline keskeytyi (${file.name})`, '#f85149'); + return; + } + generatedFiles[file.name] = code; + } + + // Vaihe 3: Testaaja arvioi koko projektin + const allCode = Object.entries(generatedFiles) + .map(([name, code]) => `--- ${name} ---\n${code}`) + .join('\n\n'); + + termLog(`\n[${fileList.length + 2}] Testaaja — arviointi`); + 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); + + // 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`); + const fixPrompt = `Fix the issues found in the review. +Review feedback: ${review} + +Current code: +${allCode} + +Write the corrected code.`; + const fixedCode = await kpnRun(agentPrompts.coder.model, fixPrompt); + if (fixedCode) { + termLog(`\n[${fileList.length + 4}] Testaaja — uudelleenarviointi`); + await kpnRun(agentPrompts.tester.model, `Review the corrected code briefly:\n${fixedCode}`); + } + } + + termLog(`\n━━━ Pipeline valmis (${Object.keys(generatedFiles).length} tiedostoa) ━━━`); + } + + // Yksinkertainen pipeline (vanha: manageri → koodari → testaaja) + async function kpnPipelineSimple(task) { + termLog(`━━━ Pipeline käynnistyy ━━━`); + termLog(`\n[1/3] Manageri`); + const plan = await kpnRun(agentPrompts.manager.model, `Analyse this task briefly and write a technical spec for a coder:\n${task}`); + if (!plan) return; + termLog(`\n[2/3] Koodari`); + const code = await kpnRun(agentPrompts.coder.model, `${plan}\n\nWrite the code.`); + if (!code) return; + termLog(`\n[3/3] Testaaja`); + await kpnRun(agentPrompts.tester.model, `Review briefly:\n${code}`); termLog(`\n━━━ Pipeline valmis ━━━`); } @@ -1858,7 +1942,8 @@ if (sub === 'help' || !sub) { termLog(' kpn hello — iloinen tervehdys verkosta', '#a5d6ff'); termLog(' kpn run <malli> "<prompti>" — aja tehtävä verkossa', '#a5d6ff'); - termLog(' kpn pipeline "<tehtävä>" — manageri → koodari → testaaja', '#a5d6ff'); + termLog(' kpn pipeline "<tehtävä>" — nopea: manageri → koodari → testaaja', '#a5d6ff'); + termLog(' kpn project "<kuvaus>" — projekti: tiedostojako + generointi + review', '#a5d6ff'); termLog(' kpn load — lataa kielimalli omalle koneelle', '#a5d6ff'); termLog(' kpn status — verkon tila', '#a5d6ff'); termLog(' kpn models — käytettävissä olevat mallit', '#a5d6ff'); @@ -1926,13 +2011,26 @@ } if (sub === 'pipeline') { - const afterPipeline = cmd.replace(/^kpn\s+pipeline\s*/, ''); - const pMatch = afterPipeline.match(/^"(.+)"$|^'(.+)'$|^(.+)$/); + const afterCmd = cmd.replace(/^kpn\s+pipeline\s*/, ''); + const pMatch = afterCmd.match(/^"(.+)"$|^'(.+)'$|^(.+)$/); const pTask = (pMatch && (pMatch[1] || pMatch[2] || pMatch[3] || '')).trim(); if (!pTask) { termLog(' Käyttö: kpn pipeline "<tehtävä>"', '#f85149'); return; } + kpnPipelineSimple(pTask); + return; + } + + if (sub === 'project') { + const afterCmd = cmd.replace(/^kpn\s+project\s*/, ''); + const pMatch = afterCmd.match(/^"(.+)"$|^'(.+)'$|^(.+)$/); + const pTask = (pMatch && (pMatch[1] || pMatch[2] || pMatch[3] || '')).trim(); + if (!pTask) { + termLog(' Käyttö: kpn project "<projektin kuvaus>"', '#f85149'); + termLog(' Esim: kpn project "FastAPI + SQLite REST API for users"', '#8b949e'); + return; + } kpnPipeline(pTask); return; } @@ -1969,7 +2067,7 @@ // Tab-completion: ennustava komennonsyöttö sana kerrallaan const kpnCommands = { - 'kpn': ['help', 'run', 'pipeline', 'load', 'status', 'models', 'hello', 'clear'], + 'kpn': ['help', 'run', 'project', 'pipeline', 'load', 'status', 'models', 'hello', 'clear'], 'kpn run': ['coder', 'coder-3b', 'manager', 'tester', 'qa', 'data', 'observer', 'qwen-coder', 'qwen-coder-3b', 'smollm-135m', 'qwen-05b', 'phi3-mini'], 'kpn load': ['1', '2'], 'kpn pipeline': ['"'], @@ -1980,6 +2078,7 @@ 'kpn run coder-3b': ['"binary search tree in rust"', '"REST API with Flask"', '"async web scraper in python"'], 'kpn run manager': ['"suunnittele REST API"', '"priorisoi tiimin tehtävät"'], 'kpn run tester': ['"testaa login-toiminto"'], + 'kpn project': ['"FastAPI + SQLite REST API for users"', '"Flask todo app with database"', '"CLI tool for CSV processing in Python"'], 'kpn pipeline': ['"rakenna todo-sovellus"', '"tee laskin pythonilla"'], };