Web Worker: WASM-inferenssi erillisessä säikeessä, UI ei jäädy

- Poistettu kaikki web_sys::window() -kutsut Rust WASM:sta
- Uudet Worker-yhteensopivat apufunktiot: perf_now(), worker_fetch(), sleep_ms()
- worker.js lataa ja ajaa WASM-moduulin erillisessä säikeessä
- ensureCoderNode käynnistää Workerin pääsäikeen sijaan
- Selaimen UI pysyy responsiivisena inferenssin aikana

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-06 19:59:09 +03:00
parent fc95cf8c1b
commit b3646ae5d3
6 changed files with 129 additions and 70 deletions

View File

@@ -3233,7 +3233,9 @@ Write the corrected code.`;
const _prevConsoleLog = console.log;
console.log = function(...args) { _prevConsoleLog.apply(console, args); codeLogListener(...args); };
// Käynnistä Coder-node automaattisesti ensimmäisellä kerralla
// Web Worker -pohjainen laskentasolmu — UI ei jäädy inferenssin aikana
let coderWorker = null;
async function ensureCoderNode() {
if (coderJoined) return;
coderJoined = true;
@@ -3243,10 +3245,21 @@ Write the corrected code.`;
setStep('step-wasm', 'active');
try {
if (!wasmInitialized) {
await init();
wasmInitialized = true;
}
// Käynnistetään WASM Web Workerissa
coderWorker = new Worker('./worker.js');
// Workerin console.log-viestit → pääsäikeen kuuntelija
// Worker ei voi kutsua console.log näkyvästi, joten WASM:n console_log
// ei näy automaattisesti. Workerissa console.log menee Workerin konsoliin.
await new Promise((resolve, reject) => {
coderWorker.onmessage = (e) => {
if (e.data.type === 'ready') resolve();
else if (e.data.type === 'error') reject(new Error(e.data.message));
};
coderWorker.postMessage({ type: 'init' });
});
setStep('step-wasm', 'done');
setStep('step-tokenizer', 'active');
@@ -3260,30 +3273,23 @@ Write the corrected code.`;
selected_task: coderSize === '3b' ? 'qwen-coder-3b' : 'qwen-coder-05b'
};
const taskId = coderSize === '3b' ? 5 : 4;
// Tunnistetaan WebGPU myös koodilaboratorion puolella
let coderHasWebGPU = false;
if (navigator.gpu) {
try {
const adapter = await navigator.gpu.requestAdapter();
if (adapter) {
try {
const testDevice = await adapter.requestDevice({ requiredLimits: { maxInterStageShaderComponents: 60 } });
coderHasWebGPU = true;
testDevice.destroy();
} catch(e) {
coderHasWebGPU = false;
}
}
} catch(e) {}
}
await start_agent_node(wsUrl, coderHasWebGPU, JSON.stringify(deviceInfo), taskId);
document.getElementById('coder-status').textContent = 'Connected';
document.getElementById('coder-status').style.color = '#d29922';
coderWsReady = true;
// Proaktiivinen mallin esilataus: lähetetään tyhjä warmup-prompt
// joka triggeröi get_or_build_model:n ilman varsinaista generointia.
// Pipeline-tilakone seuraa logeja ja merkkaa vaiheet valmiiksi.
// Käynnistetään node Workerissa
coderWorker.onmessage = (e) => {
if (e.data.type === 'started') {
document.getElementById('coder-status').textContent = 'Connected';
document.getElementById('coder-status').style.color = '#d29922';
coderWsReady = true;
} else if (e.data.type === 'error') {
console.log('[Worker] Virhe: ' + e.data.message);
}
};
coderWorker.postMessage({
type: 'start',
data: { hubUrl: wsUrl, hasWebGPU: false, deviceInfo: JSON.stringify(deviceInfo), taskId }
});
// Warmup
setTimeout(() => {
if (uiSocket && uiSocket.readyState === 1) {
uiSocket.send(JSON.stringify({
@@ -3297,7 +3303,7 @@ Write the corrected code.`;
if (pendingCodePrompt) {
setTimeout(() => {
sendCodeToHub(pendingCodePrompt);
}, 2000); // Hieman pidempi odotus jotta warmup ehtii ensin
}, 2000);
pendingCodePrompt = null;
}
} catch(e) {