Template-pohjainen projektipipeline + opettavat selitykset
Uusi lähestymistapa: sen sijaan, että malli keksii rakenteen tyhjästä, sille annetaan mallipohja (template) joka sisältää: - Tiedostojärjestys (models → schemas → main → pyproject.toml) - Esimerkkikoodi jokaiselle tiedostolle (few-shot) - Yksityiskohtaiset ohjeet (importit, nimeämiskäytännöt, patternit) Jokainen vaihe selitetään terminaalissa: 💡 models.py — "Define the SQLAlchemy model. Always include engine with check_same_thread=False for SQLite..." 💡 Koodikatselmointi — "Testaaja tarkistaa importit, nimeämiset..." 💡 Tulos — "Aja: uv run uvicorn main:app --reload" templates/fastapi-crud.json sisältää täydellisen esimerkkiprojektin jota malli adaptoi käyttäjän kuvaukseen. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -566,66 +566,98 @@ Provide a brief risk assessment with severity (low/medium/high/critical).` },
|
||||
termPanel?.addEventListener('click', () => termInput?.focus());
|
||||
document.addEventListener('click', (e) => { if (!termInput?.contains(e.target) && !dropdown?.contains(e.target)) hideDD(); });
|
||||
|
||||
// === Project pipeline ===
|
||||
// === Template-pohjainen projektipipeline ===
|
||||
let templates = {};
|
||||
|
||||
// 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) {}
|
||||
})();
|
||||
|
||||
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>`);
|
||||
}
|
||||
|
||||
async function kpnProject(task) {
|
||||
const mgr = agents.manager || Object.values(agents)[0];
|
||||
const cdr = agents.coder || Object.values(agents)[1];
|
||||
const tst = agents.tester || Object.values(agents)[2];
|
||||
|
||||
termLog(`<span style="color:var(--purple);font-weight:bold">━━━ Projekti käynnistyy ━━━</span>`);
|
||||
termLog(`\n<span style="color:#d29922;font-weight:bold">[1] ${esc(mgr.name)}</span> — suunnittelu`);
|
||||
const mgrPrompt = (mgr.prompt ? mgr.prompt + '\n\n' : '') + `List the source files needed for this project. One file per line, format:\nfilename.py: what this file contains\n\nRules:\n- Max 4 files\n- Only .py, .toml, .json, .html files\n- No directories, just filenames\n- Dependencies first (models.py before main.py)\n- Use pyproject.toml for deps\n\nProject: ${task}`;
|
||||
const plan = await kpnRun(mgr.model, mgrPrompt);
|
||||
if (!plan) { termLog(' ✗ Keskeytyi', '#f85149'); return; }
|
||||
|
||||
const fileList = plan.split('\n').map(l => l.trim().replace(/^[\d\.\-\*\s]+/,'').replace(/\*+/g,'').replace(/`/g,'')).map(l => {
|
||||
if (l.includes(':')) { const [n,...d] = l.split(':'); return { name: n.trim(), desc: d.join(':').trim() }; }
|
||||
return { name: l.trim(), desc: '' };
|
||||
}).filter(f => f.name.length > 0 && f.name.length < 40 && !f.name.includes('/') && !f.name.includes(' ') && /\.\w{1,5}$/.test(f.name));
|
||||
|
||||
if (!fileList.length) {
|
||||
termLog(' Ei tiedostojakoa — generoidaan yhtenä', '#8b949e');
|
||||
await kpnRun(cdr.model, `${cdr.prompt ? cdr.prompt+'\n\n' : ''}Project: ${task}\n\nWrite all the code.`);
|
||||
termLog(`\n<span style="color:var(--purple);font-weight:bold">━━━ Valmis ━━━</span>`);
|
||||
// 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:#8b949e">${fileList.length} tiedostoa: ${fileList.map(f=>f.name).join(', ')}</span>`);
|
||||
termLog(`<span style="color:var(--purple);font-weight:bold">━━━ ${esc(template.name)} — ${esc(task)} ━━━</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.`);
|
||||
|
||||
const files = {};
|
||||
for (let i = 0; i < fileList.length; i++) {
|
||||
const f = fileList[i];
|
||||
termLog(`\n<span style="color:#3fb950;font-weight:bold">[${i+2}] ${esc(cdr.name)}</span> — ${esc(f.name)}`);
|
||||
let ctx = '';
|
||||
const prev = Object.entries(files);
|
||||
if (prev.length) ctx = 'Already written:\n' + prev.map(([n,c]) => `--- ${n} ---\n${c}`).join('\n\n') + '\n\n';
|
||||
let extra = '';
|
||||
if (f.name === 'pyproject.toml') extra = '\nUse format: [project]\\nname="proj"\\nversion="0.1.0"\\nrequires-python=">=3.11"\\ndependencies=["fastapi","uvicorn"]';
|
||||
// Varmistetaan oikeat importit: listaa aiempien tiedostojen exportit
|
||||
let importHint = '';
|
||||
const prevNames = Object.keys(files);
|
||||
if (prevNames.length > 0) {
|
||||
importHint = '\n\nIMPORTANT: Import from already written files. ';
|
||||
importHint += prevNames.map(n => `from ${n.replace('.py','')} import ...`).join(', ');
|
||||
importHint += '. Use correct imports for all dependencies.';
|
||||
importHint += '\nUse separate names for Pydantic schemas (e.g. UserCreate, UserResponse) vs SQLAlchemy models (e.g. User).';
|
||||
|
||||
for (let i = 0; i < template.order.length; i++) {
|
||||
const fileName = template.order[i];
|
||||
const fileDef = template.files[fileName];
|
||||
if (!fileDef) continue;
|
||||
|
||||
const step = i + 1;
|
||||
termLog(`\n<span style="color:#3fb950;font-weight:bold">[${step}/${template.order.length}] ${esc(cdr.name)}</span> — ${esc(fileName)}`);
|
||||
|
||||
// Opettava selitys: miksi tämä tiedosto, mitä se sisältää
|
||||
explainStep(fileName, fileDef.instructions);
|
||||
|
||||
// Rakennetaan prompti: esimerkki + konteksti + ohje
|
||||
let prompt = '';
|
||||
|
||||
// Agentin system prompt
|
||||
if (cdr.prompt) prompt += cdr.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';
|
||||
|
||||
// Aiemmin generoidut tiedostot (konteksti)
|
||||
const prevFiles = Object.entries(files);
|
||||
if (prevFiles.length > 0) {
|
||||
prompt += 'Already written files in THIS project:\n';
|
||||
for (const [name, code] of prevFiles) {
|
||||
prompt += `--- ${name} ---\n${code}\n\n`;
|
||||
}
|
||||
}
|
||||
const coderPrompt = (cdr.prompt ? cdr.prompt+'\n\n' : '') + `${ctx}Project: ${task}\nWrite ONLY "${f.name}"${f.desc ? ': '+f.desc : ''}.${extra}${importHint}\nInclude all necessary imports. Write complete, working code.`;
|
||||
const code = await kpnRun(cdr.model, coderPrompt);
|
||||
if (!code) { termLog(` ✗ Keskeytyi (${f.name})`, '#f85149'); return; }
|
||||
files[f.name] = code;
|
||||
|
||||
// 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.';
|
||||
|
||||
const code = await kpnRun(cdr.model, prompt);
|
||||
if (!code) {
|
||||
termLog(` ✗ Keskeytyi (${fileName})`, '#f85149');
|
||||
return;
|
||||
}
|
||||
files[fileName] = code;
|
||||
}
|
||||
|
||||
// Review
|
||||
const allCode = Object.entries(files).map(([n,c]) => `--- ${n} ---\n${c}`).join('\n\n');
|
||||
termLog(`\n<span style="color:var(--accent);font-weight:bold">[${fileList.length+2}] ${esc(tst.name)}</span> — review`);
|
||||
const tstPrompt = (tst.prompt ? tst.prompt+'\n\n' : '') + `Review briefly. Say LGTM if ok.\n${allCode}`;
|
||||
termLog(`\n<span style="color:var(--accent);font-weight:bold">[${template.order.length + 1}] ${esc(tst.name)}</span> — review`);
|
||||
explainStep('Koodikatselmointi', 'Testaaja tarkistaa importit, nimeämiset, puuttuvat virheenkäsittelyt ja tiedostojen yhteensopivuuden.');
|
||||
|
||||
const tstPrompt = (tst.prompt ? tst.prompt+'\n\n' : '') +
|
||||
`Review this project. Check:\n1. All imports are correct (files import from each other)\n2. Pydantic schema names don't conflict with SQLAlchemy models\n3. All CRUD endpoints exist\n4. Error handling is present\nIf everything is correct, say "LGTM". Otherwise list specific issues.\n\n${allCode}`;
|
||||
const review = await kpnRun(tst.model, tstPrompt);
|
||||
|
||||
if (review && !review.toLowerCase().includes('lgtm')) {
|
||||
termLog(`\n<span style="color:#d29922;font-weight:bold">[${fileList.length+3}] ${esc(cdr.name)}</span> — korjaukset`);
|
||||
await kpnRun(cdr.model, `${cdr.prompt ? cdr.prompt+'\n\n' : ''}Fix issues:\n${review}\n\nCode:\n${allCode}`);
|
||||
termLog(`\n<span style="color:#d29922;font-weight:bold">[${template.order.length + 2}] ${esc(cdr.name)}</span> — korjaukset`);
|
||||
explainStep('Korjausluuppi', 'Testaaja löysi ongelmia. Koodari saa palautteen ja korjaa koodin.');
|
||||
await kpnRun(cdr.model, `${cdr.prompt ? cdr.prompt+'\n\n' : ''}Fix these issues:\n${review}\n\nCurrent code:\n${allCode}\n\nWrite the corrected files.`);
|
||||
}
|
||||
|
||||
termLog(`\n<span style="color:var(--purple);font-weight:bold">━━━ Valmis (${Object.keys(files).length} tiedostoa) ━━━</span>`);
|
||||
explainStep('Tulos', `Projekti "${task}" generoitu ${Object.keys(files).length} tiedostoon. Klikkaa "Avaa editorissa" tutkiaksesi koodia. Aja: uv run uvicorn main:app --reload`);
|
||||
renderProjectCard(files, task);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user