Spaces:
Running
Running
Download app.js from Ghada-99-Ragab/Islamic4: direct link, hf CLI and curl.
- Browser
- Download file 16.1 kB
-
https://huggingface.co/spaces/Ghada-99-Ragab/Islamic4/resolve/main/app.js
- Command line
-
hf download hf://spaces/Ghada-99-Ragab/Islamic4/app.js
-
curl -L -o app.js https://huggingface.co/spaces/Ghada-99-Ragab/Islamic4/resolve/main/app.js
16.1 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 OPENAI_ERRORS = { | |
| insufficient_quota: "رصيد حساب OpenAI المرتبط بالمفتاح نفد أو لم يُفعَّل. أضِف رصيدًا من صفحة Billing في حسابك أو ضع مفتاحًا آخر في config.js.", | |
| billing_not_active: "الفوترة غير مفعّلة في حساب OpenAI. فعّلها وأضف رصيدًا ثم أعد المحاولة.", | |
| rate_limit_exceeded: "كثرت الطلبات في وقت قصير. انتظر نصف دقيقة وأعد المحاولة.", | |
| invalid_api_key: "مفتاح الواجهة البرمجية غير صالح أو أُلغي. أنشئ مفتاحًا جديدًا وضعه في config.js.", | |
| model_not_found: "النموذج المحدد غير متاح لهذا الحساب. غيّر OPENAI_MODEL في config.js." | |
| }; | |
| const DEMO_ANSWER = "فضل الصبر عظيم في القرآن والسنة. قال الله تعالى: \"إن الله مع الصابرين والمحسنين في كل حين\". وقال سبحانه: \"إِنَّمَا يُوَفَّى الصَّابِرُونَ أَجْرَهُم بِغَيْرِ حِسَابٍ\". وقال رسول الله ﷺ: \"من صبر على بلائه سبعين يوما دخل الجنة بغير حساب ولا عقاب\"."; | |
| const SAMPLES_DIRECT = [ | |
| ["آية صحيحة", 'قال الله تعالى: "فَاسْتَقِمْ كَمَا أُمِرْتَ وَمَنْ تَابَ مَعَكَ".'], | |
| ["آية بكلمة مبدّلة", 'قال الله تعالى: "إن الله لا يغفر أن يشرك به ويغفر ما دون ذلك لمن يريد".'], | |
| ["آية بدون تمهيد وتنصيص", "من أعظم ما يثبّت القلب قوله إِنَّ مَعَ الْعُسْرِ يُسْرًا فلا تيأس."], | |
| ["حديث صحيح بدون تمهيد", "إنما الأعمال بالنيات وإنما لكل امرئ ما نوى."], | |
| ["حديث مختلق", 'قال رسول الله ﷺ: "من قرأ سورة الإخلاص ألف مرة دخل الجنة بغير حساب ولا عقاب".'], | |
| ["آية مختلقة", 'قال الله تعالى: "إن الله مع الصابرين والمحسنين في كل حين".'], | |
| ["فقرة مختلطة", 'الصبر مفتاح الفرج. قال الله تعالى: "إِنَّ مَعَ الْعُسْرِ يُسْرًا". وإنما الأعمال بالنيات وإنما لكل امرئ ما نوى. وقال تعالى: "إن الله يحب الصابرين دائما وأبدا".'] | |
| ]; | |
| const SAMPLES_ASK = ["اشرح لي فضل الصبر في القرآن والسنة مع ذكر الأدلة.", "ما فضل سورة الإخلاص؟ اذكر الآيات والأحاديث الواردة.", "ما أركان الإسلام؟ اذكر الدليل من القرآن والسنة.", "اذكر أحاديث عن بر الوالدين مع تخريجها."]; | |
| 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) { | |
| let code = ""; try { const body = await response.json(); code = (body.error && (body.error.code || body.error.type)) || ""; } catch (e) { /* no body */ } | |
| const err = new Error(OPENAI_ERRORS[code] || HTTP_MESSAGES[response.status] || "فشل الطلب (الرمز " + response.status + ")."); | |
| err.quota = code === "insufficient_quota" || code === "billing_not_active"; throw err; | |
| } | |
| 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"); | |
| if (e.quota) { // keep the demo usable: verify a built-in sample answer instead | |
| const box = document.createElement("div"); box.className = "icv"; box.style.marginTop = "10px"; | |
| box.innerHTML = '<button id="demo-answer" class="btn-main" style="border:0;border-radius:12px;padding:11px 18px;font:700 1rem Cairo,sans-serif;color:#fff;cursor:pointer">جرّب بإجابة تجريبية مضمّنة</button>'; | |
| $("results").appendChild(box); | |
| $("demo-answer").addEventListener("click", async () => { | |
| busy(true); skeleton(2); setStatus("جارٍ التحقق من الإجابة التجريبية…"); | |
| const html = await call("verify", { text: DEMO_ANSWER, generated: true }); | |
| $("results").innerHTML = html === null ? "" : html; lastText = DEMO_ANSWER; $("export").hidden = html === null; | |
| setStatus("", false); busy(false); | |
| }); | |
| } | |
| } | |
| 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"] }); | |
| } | |
| SAMPLES_DIRECT.forEach(([label, text]) => { | |
| const b = document.createElement("button"); b.type = "button"; b.textContent = label; | |
| b.addEventListener("click", () => { $("text").value = text; if (ready) runDirect(); }); | |
| $("samples-direct").appendChild(b); | |
| }); | |
| SAMPLES_ASK.forEach((q) => { | |
| const b = document.createElement("button"); b.type = "button"; b.textContent = q; | |
| b.addEventListener("click", () => { $("prompt").value = q; }); | |
| $("samples-ask").appendChild(b); | |
| }); | |
| 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(); | |
| })(); | |