FI/SV/EN kielituki about valmis (testaamatta)

This commit is contained in:
2026-04-03 09:56:18 +03:00
parent 2a242efbd8
commit 3d2b2342e0
4 changed files with 147 additions and 28 deletions

View File

@@ -120,6 +120,7 @@
.main-panel.active { display: block; }
@keyframes spin { to { transform: rotate(360deg); } }
@keyframes blink { 0%,100% { opacity:1; } 50% { opacity:0; } }
.code-output {
font-family: 'Courier New', Courier, monospace;
@@ -1286,36 +1287,87 @@
termPanel.scrollTop = termPanel.scrollHeight;
}
async function kpnRun(model, prompt) {
// Aktiiviset streaming-rivit task_id:n mukaan
const activeStreams = {};
// Lähettää promptin mallille ja palauttaa vastauksen (tai null virhetilanteessa)
async function kpnRun(model, prompt, silent) {
termLog(` → <span style="color:#58a6ff">${model}</span> käsittelee...`, '#8b949e');
try {
const taskId = crypto.randomUUID();
// Liitetään yhteinen konteksti + agentin oma system prompt
const agent = Object.values(agentPrompts).find(a => a.model === model);
const parts = [];
if (sharedPrompt) parts.push(sharedPrompt);
if (agent && agent.prompt) parts.push(agent.prompt);
parts.push(prompt);
const fullPrompt = parts.join('\n\n');
// Luodaan streaming-rivi terminaaliin
if (!silent) {
const streamDiv = document.createElement('div');
streamDiv.className = 'terminal-line';
streamDiv.style.color = '#c9d1d9';
streamDiv.innerHTML = ' <span class="stream-content"></span><span style="color:#8b949e;animation:blink 1s infinite">▌</span>';
termPanel.appendChild(streamDiv);
termPanel.scrollTop = termPanel.scrollHeight;
activeStreams[taskId] = streamDiv;
}
const res = await fetch('/api/v1/chat/completions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ model, prompt: fullPrompt, task_id: taskId }),
});
// Poistetaan streaming-rivi
if (activeStreams[taskId]) {
activeStreams[taskId].remove();
delete activeStreams[taskId];
}
if (!res.ok) {
termLog(` ✗ Virhe: ${res.status} ${res.statusText}`, '#f85149');
return;
const errText = await res.text().catch(() => res.statusText);
termLog(`${errText}`, '#f85149');
return null;
}
const data = await res.json();
const response = (data.response || '').trim();
const tokGen = data.tokens_generated || 0;
termLog(` <span style="color:#3fb950">✓</span> <span style="color:#58a6ff">${data.model || model}</span> <span style="color:#8b949e">(${tokGen} tok)</span>`);
termLog(` ${response.replace(/</g,'&lt;').replace(/\n/g,'\n ')}`, '#c9d1d9');
if (!silent) {
termLog(` ${response.replace(/</g,'&lt;').replace(/\n/g,'\n ')}`, '#c9d1d9');
}
return response;
} catch (e) {
termLog(`${e.message}`, '#f85149');
return null;
}
}
// Pipeline: manageri → koodari → testaaja
async function kpnPipeline(task) {
termLog(`<span style="color:#a371f7;font-weight:bold">━━━ Pipeline käynnistyy ━━━</span>`);
// Vaihe 1: Manageri analysoi
termLog(`\n<span style="color:#d29922;font-weight:bold">[1/3] Manageri</span> — tehtävän analyysi`);
const managerPrompt = `Analysoi seuraava ohjelmistokehitystehtävä ja kirjoita koodarille selkeä tekninen ohje mitä koodata. Vastaa lyhyesti.\n\nTehtävä: ${task}`;
const plan = await kpnRun(agentPrompts.manager.model, managerPrompt);
if (!plan) { termLog(' ✗ Pipeline keskeytyi (manageri)', '#f85149'); return; }
// Vaihe 2: Koodari toteuttaa
termLog(`\n<span style="color:#3fb950;font-weight:bold">[2/3] Koodari</span> — 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; }
// Vaihe 3: Testaaja arvioi
termLog(`\n<span style="color:#58a6ff;font-weight:bold">[3/3] Testaaja</span> — 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);
termLog(`\n<span style="color:#a371f7;font-weight:bold">━━━ Pipeline valmis ━━━</span>`);
}
function termExec(cmd) {
termLog(`<span class="terminal-prompt">$</span> ${cmd.replace(/</g,'&lt;')}`);
termHistory.unshift(cmd);
@@ -1331,6 +1383,7 @@
if (sub === 'help' || !sub) {
termLog(' kpn hello — iloinen tervehdys verkosta', '#a5d6ff');
termLog(' kpn run &lt;malli&gt; "&lt;prompti&gt;" — aja tehtävä verkossa', '#a5d6ff');
termLog(' kpn pipeline "&lt;tehtävä&gt;" — manageri → koodari → testaaja', '#a5d6ff');
termLog(' kpn status — verkon tila', '#a5d6ff');
termLog(' kpn models — käytettävissä olevat mallit', '#a5d6ff');
termLog(' kpn clear — tyhjennä terminaali', '#a5d6ff');
@@ -1358,6 +1411,18 @@
return;
}
if (sub === 'pipeline') {
const afterPipeline = cmd.replace(/^kpn\s+pipeline\s*/, '');
const pMatch = afterPipeline.match(/^"(.+)"$|^'(.+)'$|^(.+)$/);
const pTask = (pMatch && (pMatch[1] || pMatch[2] || pMatch[3] || '')).trim();
if (!pTask) {
termLog(' Käyttö: kpn pipeline "&lt;tehtävä&gt;"', '#f85149');
return;
}
kpnPipeline(pTask);
return;
}
if (sub === 'hello') {
kpnRun('smollm-135m', 'Tervehdi käyttäjää iloisesti ja lyhyesti suomeksi. Ole innostunut ja energinen! Vastaa yhdellä lauseella.');
return;
@@ -1617,6 +1682,16 @@
console.log(`[${model}] ${tokGen} tokenia | ${typeof durMs === 'number' ? durMs.toFixed(0) : durMs}ms | ${tokS} tok/s | "${(data.response || '').substring(0, 60)}..."`);
} else if (data.type === "llm_chunk") {
// Terminaalin streaming: päivitetään aktiivinen rivi
if (data.task_id && activeStreams[data.task_id]) {
const streamDiv = activeStreams[data.task_id];
const contentEl = streamDiv.querySelector('.stream-content');
if (contentEl) {
contentEl.textContent += data.token || '';
termPanel.scrollTop = termPanel.scrollHeight;
}
}
// Streaming: näytetään generointi reaaliaikaisesti
const model = data.model || '';
const isCoder = model.includes('Coder');