/* 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 ? '' : "") + 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 = '
';
$("results").innerHTML = '
' + card.repeat(n) + "
";
}
function notice(text, kind) { $("results").innerHTML = '
' + esc(text) + "
"; }
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 '' + esc(t.token) + "" + esc(t.tag) + "";
}).join("");
const badge = det.simulated ? 'Simulation · no weights loaded' : 'Live · Hugging Face';
return '