/* Nawah-Router demo — editable categories, live routing, connectors weighted by probability. */ /* Three backbones on the same head: a 6M bidirectional BERT encoder (Arabic), the 52M Llama decoder (Arabic), and a 6M BERT encoder pretrained bilingually (Arabic+English). Switching re-routes immediately so the comparison lands on the same text and the same categories. Declared here because run() references it. */ var currentModel = "6M-BERT"; const $ = id => document.getElementById(id); const PRESETS = [ { name: "توجيه مساعد ذكي", text: "عندي كود بايثون بيطلع خطأ عند قراءة ملف CSV كبير، ممكن تشوف السبب وتظبطه؟", cats: ["مهمة برمجية بسيطة", "مهمة برمجية معقدة", "ترجمة", "كتابة إبداعية", "بحث عن معلومة مباشرة", "محادثة عابرة"] }, { name: "فرز خدمة العملاء", text: "الطلب تأخر ساعة ونصف عن الموعد والسائق ما رد على الاتصال، وأبغى تعويض عن التأخير.", cats: ["شكوى غاضبة تحتاج تصعيدًا فوريًا", "استفسار عادي غير مستعجل", "طلب استرجاع مبلغ", "رسالة شكر وثناء", "رسالة مزعجة إعلانية"] }, { name: "تصنيف المحتوى", text: "بعد شهرين من الاستخدام اليومي، جودة الصوت ممتازة لكن البطارية لا تدوم أكثر من خمس ساعات.", cats: ["مراجعة منتج", "خبر اقتصادي", "إعلان تجاري", "مقال رأي", "محتوى تقني متقدم"] }, { name: "تعقيد الطلب", text: "أحتاج خطة كاملة لترحيل قاعدة بيانات من MySQL إلى PostgreSQL مع الحفاظ على البيانات وبدون توقف الخدمة.", cats: ["طلب بسيط ومباشر", "يحتاج خطوتين أو ثلاثًا", "طلب معقّد متعدد الخطوات", "يحتاج نموذجًا أكبر"] }, { name: "أي أداة نستدعي", text: "كم صار سعر صرف الدولار مقابل الجنيه اليوم؟", cats: ["بحث في الويب", "حاسبة", "تقويم ومواعيد", "قاعدة بيانات", "لا يحتاج أداة"] }, { name: "دعم فني", text: "ما أقدر أسجل دخول للحساب، يطلع لي أن كلمة المرور غلط مع إني متأكد منها.", cats: ["مشكلة في الدخول إلى الحساب", "استفسار عن الفاتورة", "طلب ميزة جديدة", "بلاغ عطل حرج يوقف الخدمة", "أخرى / لا ينطبق"] }, { name: "English: assistant routing", text: "My Python script throws an error reading a large CSV file, can you find the bug and fix it?", cats: ["simple coding task", "complex coding task", "translation", "creative writing", "direct factual lookup", "casual conversation"] }, { name: "English: support triage", text: "The order is an hour and a half late and the driver isn't answering my calls. I want a refund for the delay.", cats: ["angry complaint needing immediate escalation", "routine non-urgent enquiry", "refund request", "thank-you message", "spam / promotional message"] }, { name: "English: content classification", text: "After two months of daily use, the sound quality is excellent but the battery doesn't last more than five hours.", cats: ["product review", "economic news", "advertisement", "opinion piece", "advanced technical content"] }, { name: "English: task complexity", text: "I need a full plan to migrate a database from MySQL to PostgreSQL, keeping the data intact with no service downtime.", cats: ["simple, direct request", "needs two or three steps", "complex multi-step request", "needs a larger model"] }, { name: "English: tool routing", text: "What's today's exchange rate between the dollar and the pound?", cats: ["web search", "calculator", "calendar and scheduling", "database", "no tool needed"] }, { name: "English: technical support", text: "I can't log into my account, it says my password is wrong even though I'm sure it's correct.", cats: ["account login issue", "billing enquiry", "feature request", "critical outage report", "other / not applicable"] }, ]; let rows = []; // [{el, input, fill, pct}] let timer = null, seq = 0; /* A half-typed category name is not a category, so edits to the lane list do not auto-route the way prompt edits do. Any change here sets `dirty`, which suspends auto-routing until the user submits — otherwise every keystroke would score a lane that does not exist yet. */ let dirty = false; function setDirty(on) { dirty = on; $("go").classList.toggle("dirty", on); $("go").disabled = !on && !$("text").value.trim(); $("dirtynote").textContent = on ? "تغيّرت الفئات — اضغط للتوجيه" : ""; } function cats() { return rows.map(r => r.input.value.trim()); } function addRow(value = "", focus = false) { const el = document.createElement("div"); el.className = "row"; el.innerHTML = `
— `; const input = el.querySelector("input"); input.value = value; input.dir = "auto"; // mixed Arabic/English category text, now that both route input.addEventListener("input", () => setDirty(true)); input.addEventListener("keydown", ev => { if (ev.key === "Enter") { ev.preventDefault(); submit(); } }); el.querySelector(".del").addEventListener("click", () => { rows = rows.filter(r => r.el !== el); el.remove(); drawWires(); setDirty(true); }); $("rows").appendChild(el); rows.push({ el, input, fill: el.querySelector(".fill"), pct: el.querySelector(".pct") }); if (focus) input.focus(); drawWires(); } function setPreset(p, btn) { document.querySelectorAll(".preset").forEach(b => b.classList.remove("on")); if (btn) btn.classList.add("on"); $("text").value = p.text; $("rows").innerHTML = ""; rows = []; p.cats.forEach(c => addRow(c)); updateCount(); setDirty(false); schedule(0); } /* Connectors: prompt -> model -> each category, thickness and opacity carrying the score. Redrawn on resize and whenever rows change, since row heights move. */ function drawWires(scores) { const svg = $("wires"), stage = svg.parentElement; if (window.innerWidth <= 900) { svg.innerHTML = ""; return; } const S = stage.getBoundingClientRect(); const P = $("promptcard").getBoundingClientRect(); const N = $("node").getBoundingClientRect(); const rtl = getComputedStyle(document.body).direction === "rtl"; // in RTL the prompt sits on the right, so wires run right-to-left const px = (rtl ? P.left : P.right) - S.left, py = P.top + P.height / 2 - S.top; const nIn = (rtl ? N.right : N.left) - S.left, nOut = (rtl ? N.left : N.right) - S.left; const ny = N.top + N.height / 2 - S.top; let d = `