tilakoneen fiksailuja

This commit is contained in:
Jaakko Vanhala
2026-04-04 21:22:46 +03:00
parent 3ada8949d0
commit 133ff38fa4

View File

@@ -1417,6 +1417,9 @@
document.querySelector(`.main-tab[onclick*="${tab}"]`).classList.add('active');
window.location.hash = tab;
// Siivotaan streaming-kortit näkymistä tab-vaihdon yhteydessä
document.querySelectorAll('.streaming-card').forEach(el => el.remove());
// Päivitetään admin-sessio vastaamaan nykyistä välilehteä
if (window._uiSocket && window._uiSocket.readyState === 1) {
const viewTask = tab === 'codelab' ? 'codelab-viewer' : 'viewer';
@@ -2034,64 +2037,95 @@
if (chatBox.children.length > 5) chatBox.removeChild(chatBox.firstChild);
chatBox.scrollTop = chatBox.scrollHeight;
} else if (data.type === "llm_done") {
const term = document.getElementById('agent-terminal');
if (term) {
const model = data.model || 'llm';
// Reititetäänkö agents-näkymään vai codelab-näkymään?
const isAgentsTask = data.task_id && activeStreams[data.task_id];
const isCoder = (data.model || '').includes('Coder');
if (isAgentsTask) {
// Agents-pipeline: päivitetään terminaali
const term = document.getElementById('agent-terminal');
if (term) {
const model = data.model || 'llm';
const tokGen = data.tokens_generated || 0;
const durMs = typeof data.duration_ms === 'number' ? data.duration_ms.toFixed(0) : data.duration_ms || '?';
const tokS = data.tokens_per_sec || '?';
const div = document.createElement('div');
div.className = 'terminal-line';
div.style.color = '#a5d6ff';
div.innerHTML = `${model} <span style="color:#8b949e">${tokGen} tok | ${durMs}ms | ${tokS} tok/s</span>`;
term.appendChild(div);
while (term.children.length > 50) term.removeChild(term.firstChild);
term.scrollTop = term.scrollHeight;
document.querySelectorAll('.avatar-card').forEach(c => c.classList.remove('active'));
document.getElementById('avatar-kpn').classList.add('active');
}
} else if (isCoder) {
// Codelab: erillinen addCodeResult-handler käsittelee (rivi 2364)
// Poistetaan vain streaming-kortti codelabista
if (codeResults) codeResults.querySelector('.streaming-card')?.remove();
} else {
// Muu malli (network-näkymä): näytetään chatBoxissa
chatBox.querySelector('.streaming-card')?.remove();
chatBox.classList.remove('hidden');
const nodeId = data.node_id || "?";
const model = data.model || "LLM";
const tokGen = data.tokens_generated || 0;
const durMs = typeof data.duration_ms === 'number' ? data.duration_ms.toFixed(0) : data.duration_ms || '?';
const tokS = data.tokens_per_sec || '?';
const div = document.createElement('div');
div.className = 'terminal-line';
div.style.color = '#a5d6ff';
div.innerHTML = `${model} <span style="color:#8b949e">${tokGen} tok | ${durMs}ms | ${tokS} tok/s</span>`;
term.appendChild(div);
while (term.children.length > 50) term.removeChild(term.firstChild);
term.scrollTop = term.scrollHeight;
document.querySelectorAll('.avatar-card').forEach(c => c.classList.remove('active'));
document.getElementById('avatar-kpn').classList.add('active');
const durMs = data.duration_ms || 0;
const tokS = data.tokens_per_sec || 0;
const loadMs = data.load_time_ms || 0;
const msgDiv = document.createElement('div');
msgDiv.className = 'chat-msg';
msgDiv.style.borderLeftColor = '#a371f7';
msgDiv.innerHTML = `
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
<span style="color:#a371f7;font-weight:600;font-size:15px">Solmu #${nodeId}${model}</span>
<span style="color:#8b949e;font-size:12px">${typeof durMs === 'number' ? durMs.toFixed(0) : durMs}ms | ${tokS} tok/s</span>
</div>
<div style="font-size:13px;color:#8b949e;margin-bottom:6px">
Prompt: <span style="color:#d29922">"${data.prompt || ''}"</span>
</div>
<div style="font-size:14px;color:var(--text-color);line-height:1.5;${(model.includes('Coder') || (data.response||'').includes('def ')) ? 'font-family:Courier New,monospace;background:#010409;padding:10px;border-radius:4px;white-space:pre-wrap;font-size:12px' : ''}">
${(data.response || '<em>tyhjä vastaus</em>').replace(/</g, '&lt;').replace(/>/g, '&gt;')}
</div>
<div style="margin-top:8px;font-size:12px;color:#8b949e">
${tokGen} tokenia generoitu | malli ladattu: ${typeof loadMs === 'number' ? loadMs.toFixed(0) : loadMs}ms
</div>`;
chatBox.appendChild(msgDiv);
if (chatBox.children.length > 5) chatBox.removeChild(chatBox.firstChild);
chatBox.scrollTop = chatBox.scrollHeight;
}
// Poistetaan streaming-kortti
chatBox.querySelector('.streaming-card')?.remove();
chatBox.classList.remove('hidden');
const nodeId = data.node_id || "?";
const model = data.model || "LLM";
const tokGen = data.tokens_generated || 0;
const durMs = data.duration_ms || 0;
const tokS = data.tokens_per_sec || 0;
const loadMs = data.load_time_ms || 0;
const msgDiv = document.createElement('div');
msgDiv.className = 'chat-msg';
msgDiv.style.borderLeftColor = '#a371f7';
msgDiv.innerHTML = `
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
<span style="color:#a371f7;font-weight:600;font-size:15px">Solmu #${nodeId}${model}</span>
<span style="color:#8b949e;font-size:12px">${typeof durMs === 'number' ? durMs.toFixed(0) : durMs}ms | ${tokS} tok/s</span>
</div>
<div style="font-size:13px;color:#8b949e;margin-bottom:6px">
Prompt: <span style="color:#d29922">"${data.prompt || ''}"</span>
</div>
<div style="font-size:14px;color:var(--text-color);line-height:1.5;${(model.includes('Coder') || (data.response||'').includes('def ')) ? 'font-family:Courier New,monospace;background:#010409;padding:10px;border-radius:4px;white-space:pre-wrap;font-size:12px' : ''}">
${(data.response || '<em>tyhjä vastaus</em>').replace(/</g, '&lt;').replace(/>/g, '&gt;')}
</div>
<div style="margin-top:8px;font-size:12px;color:#8b949e">
${tokGen} tokenia generoitu | malli ladattu: ${typeof loadMs === 'number' ? loadMs.toFixed(0) : loadMs}ms
</div>`;
chatBox.appendChild(msgDiv);
if (chatBox.children.length > 5) chatBox.removeChild(chatBox.firstChild);
chatBox.scrollTop = chatBox.scrollHeight;
metrics.tasks++;
metrics.totalTokens += tokGen;
metrics.totalTimeMs += durMs;
metrics.totalTokens += (data.tokens_generated || 0);
metrics.totalTimeMs += (data.duration_ms || 0);
flashComputing();
updateMetrics();
console.log(`[${model}] ${tokGen} tokenia | ${typeof durMs === 'number' ? durMs.toFixed(0) : durMs}ms | ${tokS} tok/s | "${(data.response || '').substring(0, 60)}..."`);
console.log(`[${data.model || 'LLM'}] ${data.tokens_generated || 0} tokenia | ${typeof data.duration_ms === 'number' ? data.duration_ms.toFixed(0) : data.duration_ms || '?'}ms | ${data.tokens_per_sec || '?'} tok/s | "${(data.response || '').substring(0, 60)}..."`);
} else if (data.type === "llm_error") {
// Virheenkäsittely: siivotaan streaming-tila
const errMsg = data.error || 'Tuntematon virhe';
if (data.task_id && activeStreams[data.task_id]) {
// Agents-pipeline: näytetään virhe terminaalissa
activeStreams[data.task_id].remove();
delete activeStreams[data.task_id];
}
chatBox.querySelector('.streaming-card')?.remove();
if (codeResults) codeResults.querySelector('.streaming-card')?.remove();
const term = document.getElementById('agent-terminal');
if (term) {
const div = document.createElement('div');
div.className = 'terminal-line';
div.style.color = '#f85149';
div.innerHTML = ` ✗ LLM-virhe: ${errMsg}`;
term.appendChild(div);
term.scrollTop = term.scrollHeight;
}
console.warn('[LLM Error]', errMsg);
} else if (data.type === "llm_chunk") {
// Terminaalin streaming: päivitetään aktiivinen rivi
// Agents-terminaalin streaming: päivitetään aktiivinen rivi task_id:n perusteella
if (data.task_id && activeStreams[data.task_id]) {
const streamDiv = activeStreams[data.task_id];
const contentEl = streamDiv.querySelector('.stream-content');
@@ -2099,41 +2133,42 @@
contentEl.textContent += data.token || '';
termPanel.scrollTop = termPanel.scrollHeight;
}
}
// Agents-pipeline omistaa tämän chunkin, ei näytetä muualla
} else {
// Ei agents-task → näytetään streaming-kortti oikeassa näkymässä
const model = data.model || '';
const isCoder = model.includes('Coder');
const targetBox = isCoder ? codeResults : chatBox;
// Streaming: näytetään generointi reaaliaikaisesti
const model = data.model || '';
const isCoder = model.includes('Coder');
const targetBox = isCoder ? codeResults : chatBox;
if (targetBox) {
let streamEl = targetBox.querySelector('.streaming-card');
if (!streamEl) {
streamEl = document.createElement('div');
streamEl.className = isCoder ? 'code-task-card streaming-card' : 'chat-msg streaming-card';
streamEl.style.borderLeftColor = '#a371f7';
streamEl.innerHTML = `
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px">
<span style="color:#a371f7;font-weight:600">${model}</span>
<span class="stream-counter" style="color:var(--accent-color);font-size:12px">0 tok</span>
</div>
<div style="font-size:13px;color:#8b949e;margin-bottom:4px">Prompt: "${data.prompt || ''}"</div>
<div class="stream-text" style="font-size:14px;color:var(--text-color);line-height:1.5;${isCoder ? 'font-family:Courier New,monospace;background:#010409;padding:8px;border-radius:4px;white-space:pre-wrap;font-size:12px;color:#3fb950' : ''}"></div>
<div style="margin-top:6px;font-size:11px;color:#d29922">
<span class="spinner" style="display:inline-block;animation:spin 1s linear infinite">&#9696;</span> Generating...
</div>`;
if (isCoder) {
targetBox.insertBefore(streamEl, targetBox.firstChild);
} else {
targetBox.appendChild(streamEl);
if (targetBox) {
let streamEl = targetBox.querySelector('.streaming-card');
if (!streamEl) {
streamEl = document.createElement('div');
streamEl.className = isCoder ? 'code-task-card streaming-card' : 'chat-msg streaming-card';
streamEl.style.borderLeftColor = '#a371f7';
streamEl.innerHTML = `
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px">
<span style="color:#a371f7;font-weight:600">${model}</span>
<span class="stream-counter" style="color:var(--accent-color);font-size:12px">0 tok</span>
</div>
<div style="font-size:13px;color:#8b949e;margin-bottom:4px">Prompt: "${data.prompt || ''}"</div>
<div class="stream-text" style="font-size:14px;color:var(--text-color);line-height:1.5;${isCoder ? 'font-family:Courier New,monospace;background:#010409;padding:8px;border-radius:4px;white-space:pre-wrap;font-size:12px;color:#3fb950' : ''}"></div>
<div style="margin-top:6px;font-size:11px;color:#d29922">
<span class="spinner" style="display:inline-block;animation:spin 1s linear infinite">&#9696;</span> Generating...
</div>`;
if (isCoder) {
targetBox.insertBefore(streamEl, targetBox.firstChild);
} else {
targetBox.appendChild(streamEl);
}
}
const textEl = streamEl.querySelector('.stream-text');
const counterEl = streamEl.querySelector('.stream-counter');
if (textEl) textEl.textContent += data.token || '';
const tokCount = (textEl.textContent || '').split('').length;
if (counterEl) counterEl.textContent = tokCount + ' tok';
targetBox.scrollTop = targetBox.scrollHeight;
}
const textEl = streamEl.querySelector('.stream-text');
const counterEl = streamEl.querySelector('.stream-counter');
if (textEl) textEl.textContent += data.token || '';
const tokCount = (textEl.textContent || '').split('').length;
if (counterEl) counterEl.textContent = tokCount + ' tok';
targetBox.scrollTop = targetBox.scrollHeight;
}
} else if (data.type === "llm_prompt") {
if (data.task_id) {
@@ -2360,11 +2395,13 @@
if (codeResults.children.length > 10) codeResults.removeChild(codeResults.lastChild);
}
// Kuuntele coder-tuloksia UI WebSocketista
// Kuuntele coder-tuloksia UI WebSocketista (vain ei-agents-tehtävät)
uiSocket.addEventListener('message', (event) => {
try {
const data = JSON.parse(event.data);
if (data.type === 'llm_done' && (data.model || '').includes('Coder')) {
// Ohita agents-pipelinen tehtävät — ne käsitellään kpnRun:issa
if (data.task_id && activeStreams[data.task_id]) return;
addCodeResult(data);
}
} catch(e) {}