Spaces:
Running
Running
Download app.js from Ghada-99-Ragab/Islamic3: direct link, hf CLI and curl.
- Browser
- Download file 11.4 kB
-
https://huggingface.co/spaces/Ghada-99-Ragab/Islamic3/resolve/main/app.js
- Command line
-
hf download hf://spaces/Ghada-99-Ragab/Islamic3/app.js
-
curl -L -o app.js https://huggingface.co/spaces/Ghada-99-Ragab/Islamic3/resolve/main/app.js
11.4 kB
| /* Main-thread UI. Heavy work (Python/Pyodide) lives in worker.js. */ | |
| (function () { | |
| const cfg = window.ICV_CONFIG, cache = window.icvCache, $ = (id) => document.getElementById(id); | |
| const PY_FILES = ["app.py", "verifier.py", "retrieval.py", "index_builder.py", "normalization.py", "alignment.py", "similarity.py", | |
| "detector.py", "scanner.py", "idgham.py", "ui.py", "llm_client.py", "benchmark.py", "index/quran.idx.gz", "demo/examples.json"]; | |
| const SYSTEM_PROMPT = "أنت مساعد معرفي في العلوم الإسلامية. أجب بالعربية بإيجاز ودقة. عند الاستشهاد بآية قرآنية أو حديث نبوي اكتب نصه كاملًا واذكر السورة ورقم الآية أو مصدر الحديث."; | |
| const HTTP_MESSAGES = { 400: "رفض الخادم الطلب.", 401: "مفتاح الواجهة البرمجية غير صالح.", 403: "المفتاح غير مصرّح له بهذا الطلب.", 429: "تم تجاوز حد الاستخدام أو الرصيد؛ حاول لاحقًا." }; | |
| const esc = (s) => String(s).replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c])); | |
| let worker = null, ready = false, nextId = 0, examples = [], exampleIndex = 0, lastText = ""; | |
| const pending = new Map(); | |
| /* ---------- toast + clipboard ---------- */ | |
| let toastTimer = 0; | |
| function toast(msg, error) { | |
| const t = $("toast"); t.textContent = msg; t.classList.toggle("err", !!error); t.classList.add("show"); | |
| clearTimeout(toastTimer); toastTimer = setTimeout(() => t.classList.remove("show"), 2200); | |
| } | |
| async function copyText(text) { | |
| try { if (navigator.clipboard && window.isSecureContext) { await navigator.clipboard.writeText(text); return true; } } catch (e) { /* fall back */ } | |
| const area = document.createElement("textarea"); // works inside iframes / non-secure contexts | |
| area.value = text; area.setAttribute("readonly", ""); area.style.cssText = "position:fixed;top:-1000px;opacity:0"; | |
| document.body.appendChild(area); area.select(); area.setSelectionRange(0, text.length); | |
| let ok = false; try { ok = document.execCommand("copy"); } catch (e) { ok = false; } | |
| area.remove(); return ok; | |
| } | |
| window.icvCopy = async (button) => { | |
| const ok = await copyText(button.dataset.text || ""); | |
| toast(ok ? "تم نسخ النص بنجاح" : "تعذّر نسخ النص، حدّده يدويًا", !ok); | |
| }; | |
| /* ---------- status, boot progress, skeletons ---------- */ | |
| function setStatus(text, busy = true) { $("status").innerHTML = (busy && text ? '<span class="spinner"></span>' : "") + esc(text); } | |
| function setStep(step, fraction) { | |
| document.querySelectorAll(".boot .step").forEach((el) => { | |
| const n = Number(el.dataset.step); el.classList.toggle("done", n < step); el.classList.toggle("on", n === step); | |
| }); | |
| if (fraction != null) { const bar = $("bootbar"); bar.classList.remove("indeterminate"); bar.firstElementChild.style.width = Math.round(fraction * 100) + "%"; } | |
| } | |
| function skeleton(n = 3) { | |
| const card = '<div class="panel sk-card"><div class="skeleton sk-title"></div><div class="skeleton sk-line w1"></div><div class="skeleton sk-line w2"></div><div class="skeleton sk-line w3"></div></div>'; | |
| $("results").innerHTML = '<div class="icv" aria-busy="true">' + card.repeat(n) + "</div>"; | |
| } | |
| function notice(text, kind) { $("results").innerHTML = '<div class="icv"><div class="notice ' + kind + '">' + esc(text) + "</div></div>"; } | |
| function busy(flag) { for (const id of ["verify", "example", "ask"]) $(id).disabled = flag || !ready; } | |
| /* ---------- worker RPC ---------- */ | |
| function call(type, payload) { | |
| return new Promise((resolve) => { const id = ++nextId; pending.set(id, resolve); worker.postMessage({ type, id, ...payload }); }); | |
| } | |
| /* ---------- model card (Subtask 1A, CAMeLBERT-MSA) ---------- */ | |
| function modelCard(det) { | |
| const chips = det.tags.map((t) => { | |
| const kind = t.tag === "O" ? "O" : t.tag.slice(2); | |
| return '<span class="chip ' + kind + '">' + esc(t.token) + "<small>" + esc(t.tag) + "</small></span>"; | |
| }).join(""); | |
| const badge = det.simulated ? '<span class="badge-sim">Simulation · no weights loaded</span>' : '<span class="badge-live">Live · Hugging Face</span>'; | |
| return '<div class="icv"><div class="panel model-card"><div class="head"><span class="title">CAMeLBERT-MSA · Token Classification (Subtask 1A)</span>' + badge + | |
| '</div><div class="chips">' + chips + "</div></div></div>"; | |
| } | |
| /* ---------- direct verification ---------- */ | |
| async function runDirect() { | |
| const text = $("text").value; | |
| if (!text.trim()) return notice("الرجاء إدخال نص للتحقق منه.", "warn"); | |
| const engine = $("engine").value, key = "r:" + cache.hash(engine + "|" + text); | |
| busy(true); lastText = text; $("export").hidden = true; | |
| const hit = cache.get(key); | |
| if (hit) { $("results").innerHTML = hit; setStatus("تم عرض النتيجة من الذاكرة المحلية.", false); $("export").hidden = false; busy(false); return; } | |
| skeleton(); setStatus("جارٍ التحقق… (قد ينتظر إتمام تجهيز كتب الحديث)"); | |
| let html; | |
| if (engine === "camelbert") { | |
| setStatus("CAMeLBERT-MSA: جارٍ استخراج المقاطع…"); | |
| const det = await window.icvDetectors.detectSpans(text, async () => JSON.parse((await call("detect", { text })) || "[]")); | |
| setStatus("جارٍ التحقق من المقاطع المستخرجة…"); | |
| const report = await call("verify_spans", { text, spans: JSON.stringify(det.spans) }); | |
| html = report === null ? null : modelCard(det) + report; | |
| } else { | |
| html = await call("verify", { text, generated: false }); | |
| } | |
| if (html === null) notice("حدث خطأ غير متوقع أثناء التحقق.", "bad"); | |
| else { $("results").innerHTML = html; cache.set(key, html); $("export").hidden = false; } | |
| setStatus("", false); busy(false); | |
| } | |
| /* ---------- Ask then Verify: pre-configured ChatGPT client ---------- */ | |
| async function askChatGPT(prompt) { | |
| if (!prompt.trim()) throw new Error("اكتب سؤالًا أولًا."); | |
| if (!cfg.OPENAI_API_KEY) throw new Error("لم يتم ضبط مفتاح ChatGPT في ملف config.js."); | |
| let response; | |
| try { | |
| response = await fetch(cfg.OPENAI_ENDPOINT, { method: "POST", | |
| headers: { "Content-Type": "application/json", Authorization: "Bearer " + cfg.OPENAI_API_KEY }, | |
| body: JSON.stringify({ model: cfg.OPENAI_MODEL, messages: [{ role: "system", content: SYSTEM_PROMPT }, { role: "user", content: prompt.trim() }] }) }); | |
| } catch (e) { throw new Error("تعذّر الاتصال بالخدمة. تحقق من الاتصال بالإنترنت."); } | |
| if (!response.ok) throw new Error(HTTP_MESSAGES[response.status] || "فشل الطلب (الرمز " + response.status + ")."); | |
| const data = await response.json(), answer = data.choices && data.choices[0] && data.choices[0].message.content; | |
| if (!answer || !answer.trim()) throw new Error("لم يُرجع النموذج أي نص."); | |
| return answer.trim(); | |
| } | |
| async function runAsk() { | |
| busy(true); $("export").hidden = true; | |
| try { | |
| skeleton(2); setStatus("جارٍ سؤال ChatGPT…"); | |
| const answer = await askChatGPT($("prompt").value); | |
| setStatus("جارٍ التحقق من إجابة النموذج…"); | |
| const html = await call("verify", { text: answer, generated: true }); | |
| if (html === null) notice("تعذّر التحقق من إجابة النموذج.", "bad"); | |
| else { $("results").innerHTML = html; lastText = answer; $("export").hidden = false; } | |
| } catch (e) { notice(e.message, "warn"); } | |
| setStatus("", false); busy(false); | |
| } | |
| /* ---------- benchmark export (IslamicEval-style 1A/1B/1C) ---------- */ | |
| async function exportBenchmark(kind) { | |
| if (!lastText) return; | |
| toast("Preparing export…"); | |
| const raw = await call("benchmark", { text: lastText }); | |
| if (!raw) return toast("تعذّر التصدير", true); | |
| const data = JSON.parse(raw), body = kind === "tsv" ? data.tsv : JSON.stringify(data, null, 2); | |
| const blob = new Blob([body], { type: kind === "tsv" ? "text/tab-separated-values" : "application/json" }); | |
| const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = "islamiceval_" + data.Response_ID + "." + kind; | |
| document.body.appendChild(a); a.click(); a.remove(); setTimeout(() => URL.revokeObjectURL(a.href), 4000); | |
| if (!(await copyText(body))) return; toast("Exported · copied to clipboard"); | |
| } | |
| /* ---------- boot ---------- */ | |
| function start() { | |
| try { worker = new Worker("worker.js"); } catch (e) { return setStatus("تعذّر تشغيل النظام: " + e.message, false); } | |
| worker.onerror = (ev) => setStatus("تعذّر تشغيل النظام: " + (ev.message || ""), false); | |
| worker.onmessage = (event) => { | |
| const m = event.data; | |
| if (m.type === "status") { setStatus(m.text); setStep(m.step, m.step === 1 ? 0.1 : m.step === 2 ? 0.3 : null); } | |
| else if (m.type === "progress") setStep(3, 0.35 + 0.65 * (m.total ? m.got / m.total : 0.5)); | |
| else if (m.type === "error") setStatus("تعذّر تشغيل النظام: " + m.text + ". جرّب متصفح كمبيوتر حديثًا ثم أعد تحميل الصفحة.", false); | |
| else if (m.type === "result") { const r = pending.get(m.id); pending.delete(m.id); r(m.value); } | |
| else if (m.type === "ready") { ready = true; busy(false); setStep(3, 0.35); setStatus("النظام جاهز. جارٍ تجهيز كتب الحديث في الخلفية…"); } | |
| else if (m.type === "hadith") { setStep(4, 1); setStatus("جاهز بالكامل. يعمل الكود داخل متصفحك ولا يُرسل نصك إلى أي خادم.", false); setTimeout(() => $("boot").hidden = true, 1800); } | |
| }; | |
| worker.postMessage({ type: "boot", base: location.href, pyodideVersion: cfg.PYODIDE_VERSION, files: PY_FILES, lazy: ["index/hadith.idx.gz"] }); | |
| } | |
| document.querySelectorAll(".tab").forEach((tab) => tab.addEventListener("click", () => { | |
| document.querySelectorAll(".tab").forEach((t) => t.classList.toggle("active", t === tab)); | |
| for (const name of ["direct", "ask"]) $("tab-" + name).hidden = tab.dataset.tab !== name; | |
| })); | |
| $("engine").addEventListener("change", () => { $("engine-hint").textContent = $("engine").selectedOptions[0].dataset.hint; }); | |
| $("engine").dispatchEvent(new Event("change")); | |
| $("verify").addEventListener("click", runDirect); | |
| $("ask").addEventListener("click", runAsk); | |
| $("example").addEventListener("click", () => { if (!examples.length) return; $("text").value = examples[exampleIndex++ % examples.length].text; runDirect(); }); | |
| $("export-json").addEventListener("click", () => exportBenchmark("json")); | |
| $("export-tsv").addEventListener("click", () => exportBenchmark("tsv")); | |
| document.addEventListener("click", (e) => { const b = e.target.closest && e.target.closest(".copy-btn"); if (b && !b.getAttribute("onclick")) window.icvCopy(b); }); | |
| cache.json("demo/examples.json").then((d) => { examples = d; }).catch(() => cache.json("examples.json").then((d) => { examples = d; }).catch(() => {})); | |
| busy(true); setStep(1, 0.05); start(); | |
| })(); | |