uusi projekti

This commit is contained in:
Jaakko Vanhala
2026-04-12 10:28:57 +03:00
parent 094b183c17
commit 2f140c8a15
16 changed files with 521 additions and 102 deletions

View File

@@ -501,10 +501,16 @@ OUTPUT FORMAT:
// Wasm-autostart vain jos natiivisolmua ei löydy (tarkistetaan onopen:ssa)
// === Pipeline-keskeytys ===
let pipelineAbort = null; // AbortController tai null
// === kpnRun: lähettää promptin mallille ===
const activeStreams = {};
async function kpnRun(model, prompt, silent, agentOpts) {
// Tarkistetaan keskeytys
if (pipelineAbort?.signal?.aborted) return null;
const taskId = crypto.randomUUID();
const statusDiv = document.createElement('div');
statusDiv.className = 'terminal-line';
@@ -514,10 +520,6 @@ OUTPUT FORMAT:
termPanel.scrollTop = termPanel.scrollHeight;
try {
// Ei odotetaan Wasmia — lähetetään suoraan hubille.
// Jos hub löytää natiivisolmun, vastaus tulee nopeasti.
// Jos 503, käynnistetään Wasm-fallback.
if (!silent) {
const streamDiv = document.createElement('div');
streamDiv.className = 'terminal-line';
@@ -535,18 +537,18 @@ OUTPUT FORMAT:
model,
prompt,
task_id: taskId,
system_prompt: opts.systemPrompt || settings.systemPrompt || undefined,
system_prompt: opts.prompt || settings.systemPrompt || undefined,
temperature: opts.temperature ?? settings.temperature ?? undefined,
top_k: opts.topK ?? settings.topK ?? undefined,
max_tokens: opts.maxTokens ?? settings.maxTokens ?? undefined,
repeat_penalty: opts.repeatPenalty ?? settings.repeatPenalty ?? undefined,
stop: settings.stopSequences ? settings.stopSequences.split('\\n').filter(Boolean) : undefined,
};
const res = await fetch('/api/v1/chat/completions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
signal: pipelineAbort?.signal,
});
if (res.status === 503 && !wasmNodeStarted) {
@@ -619,7 +621,7 @@ OUTPUT FORMAT:
const kpnExamples = {
'kpn run coder': ['"hello world in python"','"fibonacci in rust"','"quicksort in javascript"'],
'kpn run coder-3b': ['"REST API with Flask"','"binary search tree"'],
'kpn project': ['"FastAPI + SQLite REST API"','"CLI tool for CSV processing"'],
'kpn project': ['"FastAPI + SQLite REST API"','"UWB indoor positioning analytics — CSV cart tracking data, heatmaps, statistics, Docker (MariaDB + Jupyter)"','"CLI tool for CSV processing"'],
'kpn pipeline': ['"todo-sovellus"','"laskin pythonilla"'],
};
@@ -753,15 +755,30 @@ OUTPUT FORMAT:
// === Template-pohjainen projektipipeline ===
let templates = {};
const TEMPLATE_FILES = ['fastapi-crud.json', 'data-analytics.json'];
// Ladataan mallipohjat
(async () => {
try {
const res = await fetch('/templates/fastapi-crud.json');
if (res.ok) { const t = await res.json(); templates[t.name] = t; }
} catch(e) {}
for (const file of TEMPLATE_FILES) {
try {
const res = await fetch(`/templates/${file}`);
if (res.ok) { const t = await res.json(); templates[t.name] = t; }
} catch(e) {}
}
})();
// Valitaan mallipohja Asiakkaan briefin perusteella (keywords-match)
function selectTemplate(brief) {
const lower = brief.toLowerCase();
let best = null, bestScore = 0;
for (const t of Object.values(templates)) {
const keywords = t.keywords || [];
const score = keywords.filter(k => lower.includes(k)).length;
if (score > bestScore) { bestScore = score; best = t; }
}
return best; // null = vapaa tila
}
function explainStep(title, explanation) {
termLog(`\n <span style="color:#a371f7;font-size:12px">💡 ${esc(title)}</span>`);
termLog(` <span style="color:#8b949e;font-size:12px">${esc(explanation)}</span>`);
@@ -769,18 +786,11 @@ OUTPUT FORMAT:
async function kpnProject(task) {
const cli = agents.client || Object.values(agents)[0];
const mgr = agents.manager || Object.values(agents)[1];
const cdr = agents.coder || Object.values(agents)[2];
// Etsitään sopivin mallipohja
const template = Object.values(templates)[0]; // Toistaiseksi vain FastAPI CRUD
if (!template) {
termLog(' ✗ Mallipohjia ei ladattu', '#f85149');
return;
}
termLog(`<span style="color:var(--purple);font-weight:bold">━━━ ${esc(template.name)} — ${esc(task)} ━━━</span>`);
// Asiakas: jalostaa vaatimukset
termLog(`<span style="color:var(--purple);font-weight:bold">━━━ Projekti — ${esc(task)} ━━━</span>`);
termLog(`\n<span style="color:#f0883e;font-weight:bold">[0] ${esc(cli.name)}</span> — vaatimusmäärittely`);
highlightAgent('client');
explainStep('Vaatimusmäärittely', `${cli.name} muotoilee idean selkeiksi vaatimuksiksi: ominaisuudet, datamallit, rajapinnat.`);
@@ -788,37 +798,72 @@ OUTPUT FORMAT:
if (!brief) { termLog(' ✗ Vaatimusmäärittely epäonnistui', '#f85149'); return; }
termLog(` <span style="color:#8b949e">Vaatimukset valmiit → Manageri</span>`);
explainStep('Mallipohja', `Käytetään "${template.name}" -mallipohjaa jossa ${template.order.length} tiedostoa: ${template.order.join(', ')}. Jokainen tiedosto generoidaan järjestyksessä, ja aiemmat tiedostot annetaan kontekstina seuraavalle.`);
// Valitaan mallipohja automaattisesti briefin perusteella
const template = selectTemplate(brief);
// Tiedostolista: mallipohjasta tai managerin dynaamisesta suunnitelmasta
let fileOrder = [];
let fileDefs = {};
if (template) {
// Mallipohja löytyi — käytetään sen rakennetta
fileOrder = template.order;
fileDefs = template.files;
explainStep('Mallipohja', `Tunnistettiin "${template.name}" — ${fileOrder.length} tiedostoa: ${fileOrder.join(', ')}.`);
} else {
// Vapaa tila — Manageri päättää tiedostorakenteen
termLog(`\n<span style="color:#d29922;font-weight:bold">[1] ${esc(mgr.name)}</span> — tiedostorakenne`);
highlightAgent('manager');
explainStep('Vapaa tila', 'Sopivaa mallipohjaa ei löytynyt. Manageri suunnittelee tiedostorakenteen vaatimusten perusteella.');
const planPrompt = `PROJECT REQUIREMENTS:\n${brief}\n\nPlan the file structure for this project. List each file on its own line:\nfilename.ext: one-line description\n\nMaximum 6 files. List dependency files first.`;
const plan = await kpnRun(mgr.model, planPrompt, false, mgr);
if (!plan) { termLog(' ✗ Suunnittelu epäonnistui', '#f85149'); return; }
// Parsitaan managerin tuottama tiedostolista
for (const line of plan.split('\n')) {
const m = line.match(/^\s*[-*]?\s*(\S+\.\w+)\s*[:\-]\s*(.+)/);
if (m) {
const fname = m[1].replace(/^`|`$/g, '');
fileOrder.push(fname);
fileDefs[fname] = { description: m[2].trim(), instructions: m[2].trim() };
}
}
if (fileOrder.length === 0) {
termLog(' ✗ Manageri ei tuottanut tiedostolistaa', '#f85149');
return;
}
explainStep('Suunnitelma', `${fileOrder.length} tiedostoa: ${fileOrder.join(', ')}`);
}
const files = {};
for (let i = 0; i < template.order.length; i++) {
const fileName = template.order[i];
const fileDef = template.files[fileName];
for (let i = 0; i < fileOrder.length; i++) {
const fileName = fileOrder[i];
const fileDef = fileDefs[fileName];
if (!fileDef) continue;
const step = i + 1;
// Valitaan oikea agentti tiedostotyypin mukaan
const isDbFile = fileName === 'models.py' || fileName === 'database.py';
const isDbFile = fileName === 'models.py' || fileName === 'database.py' || fileName === 'etl.py';
const dataAgent = agents.data || Object.values(agents)[3];
const fileAgent = isDbFile && dataAgent ? dataAgent : cdr;
const fileAgentKey = isDbFile && dataAgent ? 'data' : 'coder';
termLog(`\n<span style="color:#3fb950;font-weight:bold">[${step}/${template.order.length}] ${esc(fileAgent.name)}</span> — ${esc(fileName)}`);
termLog(`\n<span style="color:#3fb950;font-weight:bold">[${step}/${fileOrder.length}] ${esc(fileAgent.name)}</span> — ${esc(fileName)}`);
highlightAgent(fileAgentKey);
// Opettava selitys: miksi tämä tiedosto, mitä se sisältää
explainStep(fileName, fileDef.instructions);
explainStep(fileName, fileDef.instructions || fileDef.description);
// Rakennetaan prompti: esimerkki + konteksti + ohje
// Rakennetaan prompti
let prompt = '';
// Agentin system prompt (data-agentti models.py:lle, koodari muille)
if (fileAgent.prompt) prompt += fileAgent.prompt + '\n\n';
// Esimerkki (few-shot)
prompt += `EXAMPLE of ${fileName} (for a different project, adapt to this one):\n`;
prompt += '```\n' + fileDef.example + '\n```\n\n';
// Esimerkki (vain mallipohjatilassa)
if (fileDef.example) {
prompt += `EXAMPLE of ${fileName} (for a different project, adapt to this one):\n`;
prompt += '```\n' + fileDef.example + '\n```\n\n';
}
// Aiemmin generoidut tiedostot (konteksti)
const prevFiles = Object.entries(files);
@@ -834,8 +879,8 @@ OUTPUT FORMAT:
// Tehtävä
prompt += `NOW write "${fileName}" for THIS project: ${task}\n`;
prompt += fileDef.instructions + '\n';
prompt += 'Adapt the example to match the project description. Import from already written files. Write ONLY the code, no explanations.';
if (fileDef.instructions) prompt += fileDef.instructions + '\n';
prompt += 'Adapt to the project requirements. Import from already written files. Write ONLY the code, no explanations.';
const code = await kpnRun(fileAgent.model, prompt, false, fileAgent);
if (!code) {