toka toimiva vedos

This commit is contained in:
2026-04-01 23:52:39 +03:00
parent 02f6684378
commit 9a72d35081
1420 changed files with 79953 additions and 64 deletions

View File

@@ -226,6 +226,54 @@
}
.toggle-tokens:hover { color: var(--text-color); border-color: #8b949e; }
.task-option {
background: var(--panel-bg);
border: 2px solid var(--border-color);
border-radius: 8px;
padding: 14px;
cursor: pointer;
transition: border-color 0.2s;
position: relative;
}
.task-option:hover { border-color: #8b949e; }
.task-option.selected { border-color: var(--accent-color); background: #58a6ff10; }
.task-title { font-weight: 600; font-size: 15px; color: var(--text-color); margin-bottom: 4px; }
.task-desc { font-size: 12px; color: #8b949e; line-height: 1.4; margin-bottom: 8px; }
.task-size { font-size: 11px; color: #6e7681; }
.task-badge {
position: absolute;
top: 10px;
right: 10px;
font-size: 10px;
font-weight: 600;
padding: 2px 8px;
border-radius: 10px;
}
.task-ready { background: #23392050; color: var(--success-color); border: 1px solid #23392080; }
.task-soon { background: #d2992215; color: #d29922; border: 1px solid #d2992240; }
.download-bar {
background: #0d1117;
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 12px 16px;
margin-bottom: 16px;
display: none;
}
.download-bar .bar-track {
background: #21262d;
border-radius: 4px;
height: 8px;
margin-top: 8px;
overflow: hidden;
}
.download-bar .bar-fill {
background: var(--accent-color);
height: 100%;
border-radius: 4px;
transition: width 0.3s ease;
}
.metric-card {
background: var(--panel-bg);
border: 1px solid var(--border-color);
@@ -270,10 +318,53 @@
<div id="compat-banner" class="compat-banner"></div>
<div id="initial-state">
<!-- Tehtävävalitsin -->
<div style="background:#0d1117;border:1px solid var(--border-color);border-radius:6px;padding:16px;margin-bottom:16px;text-align:left">
<div style="font-weight:600;font-size:15px;margin-bottom:12px">Valitse tehtävä</div>
<div id="task-selector" style="display:grid;grid-template-columns:1fr 1fr;gap:8px">
<label class="task-option selected" data-task="tokenize">
<input type="radio" name="task" value="tokenize" checked style="display:none">
<div class="task-title">Tokenisointivertailu</div>
<div class="task-desc">EN/FI-kieliparien tokenisointitehokkuuden vertailu Qwen2.5-tokenizeria käyttäen</div>
<div class="task-size">Lataus: ~7 MB (tokenizer)</div>
<span class="task-badge task-ready">Valmis</span>
</label>
<label class="task-option" data-task="smollm-135m">
<input type="radio" name="task" value="smollm-135m" style="display:none">
<div class="task-title">SmolLM 135M</div>
<div class="task-desc">Kevyt kielimalli tekstigeneraatioon — sopii kaikille laitteille (CPU)</div>
<div class="task-size">Lataus: ~269 MB (safetensors) + 2 MB (tokenizer)</div>
<span class="task-badge task-ready">Valmis</span>
</label>
<label class="task-option" data-task="qwen-05b">
<input type="radio" name="task" value="qwen-05b" style="display:none">
<div class="task-title">Qwen2.5 0.5B</div>
<div class="task-desc">Tehokkaampi kielimalli — vaatii WebGPU:n ja vähintään 1 GB muistia</div>
<div class="task-size">Lataus: ~430 MB (Q4)</div>
<span class="task-badge task-soon">Tulossa</span>
</label>
<label class="task-option" data-task="phi3-mini">
<input type="radio" name="task" value="phi3-mini" style="display:none">
<div class="task-title">Phi-3 Mini 3.8B</div>
<div class="task-desc">Iso kielimalli — vaatii tehokkaan GPU:n ja vähintään 4 GB VRAM</div>
<div class="task-size">Lataus: ~2.3 GB (Q4)</div>
<span class="task-badge task-soon">Tulossa</span>
</label>
</div>
</div>
<button id="start-btn" class="btn">Liity laskentaverkkoon</button>
</div>
<div id="active-state" class="hidden">
<div id="download-bar" class="download-bar">
<div style="display:flex;justify-content:space-between;font-size:13px">
<span id="dl-label">Ladataan mallia...</span>
<span id="dl-pct" style="color:var(--accent-color);font-weight:600">0%</span>
</div>
<div class="bar-track"><div id="dl-fill" class="bar-fill" style="width:0%"></div></div>
<div id="dl-detail" style="font-size:11px;color:#8b949e;margin-top:4px">0 / 0 MB</div>
</div>
<!-- Resurssipaneeli -->
<div style="background:#0d1117;border:1px solid var(--border-color);border-radius:6px;padding:16px;margin-bottom:16px">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">
@@ -335,7 +426,17 @@
const statVram = document.getElementById('stat-vram');
const statTasks = document.getElementById('stat-tasks');
const chatBox = document.getElementById('chat-box');
// Tehtävävalitsin
let selectedTask = 'tokenize';
document.querySelectorAll('.task-option').forEach(opt => {
opt.addEventListener('click', () => {
document.querySelectorAll('.task-option').forEach(o => o.classList.remove('selected'));
opt.classList.add('selected');
selectedTask = opt.dataset.task;
});
});
let currentChatMsg = null;
// Reaaliaikaiset metriikat
@@ -424,6 +525,17 @@
chatBox.appendChild(msgDiv);
if (chatBox.children.length > 5) chatBox.removeChild(chatBox.firstChild);
chatBox.scrollTop = chatBox.scrollHeight;
} else if (data.type === "download_progress") {
const dlBar = document.getElementById('download-bar');
if (data.pct < 100) {
dlBar.style.display = 'block';
document.getElementById('dl-label').textContent = `Ladataan: ${data.file}`;
document.getElementById('dl-pct').textContent = data.pct + '%';
document.getElementById('dl-fill').style.width = data.pct + '%';
document.getElementById('dl-detail').textContent = `${data.loaded_mb} / ${data.total_mb} MB`;
} else {
dlBar.style.display = 'none';
}
} else if (data.type === "pair_task") {
chatBox.classList.remove('hidden');
if (chatBox.children.length === 1 && chatBox.children[0].textContent.includes('Odotetaan')) {
@@ -453,6 +565,9 @@
metrics.totalTimeMs += ms;
updateMetrics();
// Lokiboksiin yhteenveto
console.log(`EN: ${en.token_count} tokenia (${(en.chars_per_token||0).toFixed(2)} m/t) vs FI: ${fi.token_count} tokenia (${(fi.chars_per_token||0).toFixed(2)} m/t) | ylikustannus: ${overhead}% | ${typeof ms === 'number' ? ms.toFixed(2) : ms}ms`);
const enCpt = parseFloat((en.chars_per_token || 0).toFixed(2));
const fiCpt = parseFloat((fi.chars_per_token || 0).toFixed(2));
@@ -480,7 +595,7 @@
<span style="color:var(--accent-color);font-weight:600;font-size:15px">Solmu #${nodeId}</span>
<div style="display:flex;gap:8px;align-items:center">
<button class="toggle-tokens" onclick="document.getElementById('${detailId}').classList.toggle('visible')">Tokenit</button>
<span style="color:#8b949e;font-size:13px">${ms}ms</span>
<span style="color:#8b949e;font-size:13px">${typeof ms === 'number' ? ms.toFixed(2) : ms}ms</span>
</div>
</div>
<div style="font-size:14px;display:grid;grid-template-columns:32px 1fr auto auto auto;gap:6px 10px;align-items:baseline">
@@ -508,6 +623,46 @@
if (!msgDiv.parentNode) chatBox.appendChild(msgDiv);
if (chatBox.children.length > 8) chatBox.removeChild(chatBox.firstChild);
chatBox.scrollTop = chatBox.scrollHeight;
} else if (data.type === "llm_done") {
// SmolLM / LLM-inferenssin tulos
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">
${data.response || '<em>tyhjä vastaus</em>'}
</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 > 8) chatBox.removeChild(chatBox.firstChild);
chatBox.scrollTop = chatBox.scrollHeight;
metrics.tasks++;
metrics.totalTokens += tokGen;
metrics.totalTimeMs += durMs;
updateMetrics();
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") {
// Streaming-token LLM:ltä — näytetään lokissa
// (chat-ikkunan streaming tulisi toteuttaa samalla logiikalla kuin pair_task/pair_done)
}
} catch(e) {}
};
@@ -520,7 +675,8 @@
cpu_cores: navigator.hardwareConcurrency || 0,
device_memory_gb: navigator.deviceMemory || 0,
platform: navigator.platform || "",
gpu: null
gpu: null,
selected_task: selectedTask
};
if (navigator.gpu) {
@@ -625,7 +781,9 @@
// WebAssembly yhdistää oikeaksi Agent Nodeksi
const wsUrl = `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/ws`;
await start_agent_node(wsUrl, hasWebGPU, JSON.stringify(deviceInfo));
const taskIds = {'tokenize': 0, 'smollm-135m': 1, 'qwen-05b': 2, 'phi3-mini': 3};
const taskId = taskIds[selectedTask] || 0;
await start_agent_node(wsUrl, hasWebGPU, JSON.stringify(deviceInfo), taskId);
} catch(e) {
console.log("Virhe GPU-käynnistyksessä: " + e);
}