File size: 3,711 Bytes
0390c03
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/* 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 }); }
};