/* Python (Pyodide) and the whole verification pipeline run here, off the main thread. */ let py = null, hadithReady = null; const post = (m) => self.postMessage(m); const status = (text, step) => post({ type: "status", text, step }); async function fetchBytes(url, onProgress) { let cache = null; try { cache = await caches.open("icv-v2"); const hit = await cache.match(url); if (hit) return new Uint8Array(await hit.arrayBuffer()); } catch (e) { cache = null; } const response = await fetch(url); if (!response.ok) throw new Error("تعذّر تحميل " + url + " (" + response.status + ")"); if (cache) { try { await cache.put(url, response.clone()); } catch (e) { /* optimisation only */ } } const total = Number(response.headers.get("Content-Length")) || 0; if (!response.body || !onProgress) return new Uint8Array(await response.arrayBuffer()); const reader = response.body.getReader(), chunks = []; let received = 0; for (;;) { const { done, value } = await reader.read(); if (done) break; chunks.push(value); received += value.length; onProgress(received, total); } const bytes = new Uint8Array(received); let offset = 0; for (const c of chunks) { bytes.set(c, offset); offset += c.length; } return bytes; } // Accept both layouts: files inside folders (index/, demo/) or uploaded flat next to index.html. async function fetchAny(file, base, onProgress) { let lastError = null; for (const path of [file, file.split("/").pop()]) { try { return await fetchBytes(new URL(path, base).href, onProgress); } catch (e) { lastError = e; } } throw lastError; } async function boot(m) { status("جارٍ تحميل بيئة بايثون…", 1); importScripts("https://cdn.jsdelivr.net/pyodide/v" + m.pyodideVersion + "/full/pyodide.js"); py = await loadPyodide(); status("جارٍ تحميل الكود وفهرس القرآن…", 2); for (const dir of ["/app", "/app/index", "/app/demo", "/app/data"]) py.FS.mkdir(dir); for (const file of m.files) py.FS.writeFile("/app/" + file, await fetchAny(file, m.base)); py.runPython("import sys; sys.path.insert(0, '/app')"); py.runPython("from app import get_pipeline, verify_text, verify_generated_answer, analyze_benchmark, detect_spans, verify_given_spans; get_pipeline()"); post({ type: "ready" }); hadithReady = loadHadith(m); // background; verification waits for it } async function loadHadith(m) { const file = m.lazy[0]; const bytes = await fetchAny(file, m.base, (got, total) => { const mb = (got / 1048576).toFixed(1), of = total ? " / " + (total / 1048576).toFixed(1) : ""; post({ type: "progress", got, total }); status("جارٍ تحميل كتب الحديث… " + mb + of + " ميغابايت", 3); }); py.FS.writeFile("/app/" + file, bytes); status("جارٍ تجهيز كتب الحديث…", 3); py.runPython("get_pipeline().retriever.warm()"); post({ type: "hadith" }); } self.onmessage = async (event) => { const m = event.data; if (m.type === "boot") { try { await boot(m); } catch (e) { post({ type: "error", text: String((e && e.message) || e) }); } return; } try { await hadithReady; const call = (name, ...args) => py.globals.get(name)(...args); let out; if (m.type === "verify") out = call(m.generated ? "verify_generated_answer" : "verify_text", m.text); else if (m.type === "detect") out = call("detect_spans", m.text); else if (m.type === "verify_spans") out = call("verify_given_spans", m.text, m.spans); else if (m.type === "benchmark") out = call("analyze_benchmark", m.text); post({ type: "result", id: m.id, value: out }); } catch (e) { post({ type: "result", id: m.id, value: null }); } };