Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
# -*- coding: utf-8 -*-
|
| 2 |
# صفحتان ثابتتان + Submit لكل سؤال يعمل فعليًا + منع تغيّر أبعاد صفحة الإدخال
|
| 3 |
|
| 4 |
-
import os, json, uuid, random, unicodedata, difflib
|
| 5 |
from dataclasses import dataclass
|
| 6 |
from pathlib import Path
|
| 7 |
from typing import List, Tuple, Optional
|
|
@@ -93,7 +93,7 @@ def norm_ar(t:str)->str:
|
|
| 93 |
t = re2.sub(AR_DIAC, "", t)
|
| 94 |
t = re2.sub(r"[إأآا]", "ا", t)
|
| 95 |
t = re2.sub(r"[يى]", "ي", t)
|
| 96 |
-
t = re2.sub(r"
|
| 97 |
t = re2.sub(r'(\p{L})\1{2,}', r'\1', t)
|
| 98 |
t = re2.sub(r'(\p{L})\1', r'\1', t)
|
| 99 |
return t.strip()
|
|
@@ -269,9 +269,7 @@ def sentence_score(s: str) -> float:
|
|
| 269 |
|
| 270 |
# ================== (NEW) جودة المشتِّتات والتطويل ==================
|
| 271 |
|
| 272 |
-
# كاش نص كامل لتحسين تقييم الجودة
|
| 273 |
global_full_text_cache = ""
|
| 274 |
-
# كاش عبارة صحيحة لتجنّب التطابق بعد التطويل
|
| 275 |
ref_phrase_cache = {}
|
| 276 |
|
| 277 |
ADJ_WHITELIST = {"التعليمية","الذكية","الرقمية","الافتراضية","التكيفية","الحديثة","المتقدمة"}
|
|
@@ -535,9 +533,7 @@ def make_mcqs(text:str, n:int=6)->List[MCQ]:
|
|
| 535 |
ch = distracts + [kp]
|
| 536 |
|
| 537 |
# ترتيب غير عشوائي: تدوير حتمي لموضع الصحيحة
|
| 538 |
-
# 1) ضع الصحيحة مؤقتًا في النهاية
|
| 539 |
ch_sorted = sorted(ch, key=lambda c: c != kp)
|
| 540 |
-
# 2) تدوير بناءً على رقم السؤال (طول القائمة الحالية) وهاش العبارة
|
| 541 |
rot = (len(items) + (hash(kp) & 3)) % 4
|
| 542 |
ch = ch_sorted[-rot:] + ch_sorted[:-rot]
|
| 543 |
|
|
@@ -598,17 +594,56 @@ def render_quiz_html(records: List[dict]) -> str:
|
|
| 598 |
|
| 599 |
# ------------------ توليد الامتحان وتبديل الصفحات ------------------
|
| 600 |
def build_quiz(text_area, file_path, n, model_id, zoom):
|
| 601 |
-
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
|
| 607 |
-
|
| 608 |
-
|
| 609 |
-
|
| 610 |
-
|
| 611 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 612 |
|
| 613 |
# ------------------ CSS ------------------
|
| 614 |
CSS = """
|
|
@@ -642,7 +677,7 @@ textarea{min-height:120px}
|
|
| 642 |
.q-badge.ok{background:#0f2f22;color:#b6f4db;border:1px solid #145b44}
|
| 643 |
.q-badge.err{background:#3a0d14;color:#ffd1d6;border:1px solid #6a1e2b}
|
| 644 |
|
| 645 |
-
.q-text{color
|
| 646 |
.opts{display:flex;flex-direction:column;gap:8px}
|
| 647 |
.opt{display:flex;gap:10px;align-items:center;background:#14161c;border:1px solid #2a2d3a;border-radius:12px;padding:10px;transition:background .15s,border-color .15s}
|
| 648 |
.opt input{accent-color:var(--accent2)}
|
|
|
|
| 1 |
# -*- coding: utf-8 -*-
|
| 2 |
# صفحتان ثابتتان + Submit لكل سؤال يعمل فعليًا + منع تغيّر أبعاد صفحة الإدخال
|
| 3 |
|
| 4 |
+
import os, json, uuid, random, unicodedata, difflib, traceback
|
| 5 |
from dataclasses import dataclass
|
| 6 |
from pathlib import Path
|
| 7 |
from typing import List, Tuple, Optional
|
|
|
|
| 93 |
t = re2.sub(AR_DIAC, "", t)
|
| 94 |
t = re2.sub(r"[إأآا]", "ا", t)
|
| 95 |
t = re2.sub(r"[يى]", "ي", t)
|
| 96 |
+
t = re2.sub(r"\s+", " ", t)
|
| 97 |
t = re2.sub(r'(\p{L})\1{2,}', r'\1', t)
|
| 98 |
t = re2.sub(r'(\p{L})\1', r'\1', t)
|
| 99 |
return t.strip()
|
|
|
|
| 269 |
|
| 270 |
# ================== (NEW) جودة المشتِّتات والتطويل ==================
|
| 271 |
|
|
|
|
| 272 |
global_full_text_cache = ""
|
|
|
|
| 273 |
ref_phrase_cache = {}
|
| 274 |
|
| 275 |
ADJ_WHITELIST = {"التعليمية","الذكية","الرقمية","الافتراضية","التكيفية","الحديثة","المتقدمة"}
|
|
|
|
| 533 |
ch = distracts + [kp]
|
| 534 |
|
| 535 |
# ترتيب غير عشوائي: تدوير حتمي لموضع الصحيحة
|
|
|
|
| 536 |
ch_sorted = sorted(ch, key=lambda c: c != kp)
|
|
|
|
| 537 |
rot = (len(items) + (hash(kp) & 3)) % 4
|
| 538 |
ch = ch_sorted[-rot:] + ch_sorted[:-rot]
|
| 539 |
|
|
|
|
| 594 |
|
| 595 |
# ------------------ توليد الامتحان وتبديل الصفحات ------------------
|
| 596 |
def build_quiz(text_area, file_path, n, model_id, zoom):
|
| 597 |
+
try:
|
| 598 |
+
text_area = (text_area or "").strip()
|
| 599 |
+
if not text_area and not file_path:
|
| 600 |
+
return "", gr.update(visible=True), gr.update(visible=False), "🛈 الصق نصًا أو ارفع ملفًا أولًا."
|
| 601 |
+
|
| 602 |
+
if text_area:
|
| 603 |
+
raw = text_area
|
| 604 |
+
else:
|
| 605 |
+
if isinstance(file_path, (list, tuple)) and file_path:
|
| 606 |
+
file_path = file_path[0]
|
| 607 |
+
if not file_path or not os.path.exists(file_path):
|
| 608 |
+
return "", gr.update(visible=True), gr.update(visible=False), "⚠️ تعذّر الوصول للملف المرفوع."
|
| 609 |
+
raw, _ = file_to_text(str(file_path), model_id=model_id, zoom=float(zoom))
|
| 610 |
+
|
| 611 |
+
cleaned = postprocess(raw)
|
| 612 |
+
|
| 613 |
+
try:
|
| 614 |
+
items = make_mcqs(cleaned, n=int(n))
|
| 615 |
+
except Exception as inner_e:
|
| 616 |
+
# Fallback بسيط يضمن توليد أسئلة حتى لو تعطل المسار الذكي
|
| 617 |
+
sents = split_sents(cleaned)[:int(n)*2]
|
| 618 |
+
if not sents:
|
| 619 |
+
raise inner_e
|
| 620 |
+
recs_items = []
|
| 621 |
+
import itertools
|
| 622 |
+
for s in sents:
|
| 623 |
+
toks = [t for t in re2.findall(r"[\p{L}]{3,}", s) if t not in AR_STOP]
|
| 624 |
+
if len(toks) < 4:
|
| 625 |
+
continue
|
| 626 |
+
kw = toks[len(toks)//3]
|
| 627 |
+
q = re2.sub(rf"(?<!\p{{L}}){re2.escape(kw)}(?!\p{{L}})", "_____", s, count=1)
|
| 628 |
+
pool = [w for w in toks if w != kw][:30]
|
| 629 |
+
random.shuffle(pool)
|
| 630 |
+
dis = list(dict.fromkeys(pool))[:3]
|
| 631 |
+
while len(dis) < 3: dis.append("اختيار")
|
| 632 |
+
ch = dis + [kw]; random.shuffle(ch)
|
| 633 |
+
recs_items.append(MCQ(id=str(uuid.uuid4())[:8], question=q, choices=ch, answer_index=ch.index(kw)))
|
| 634 |
+
if len(recs_items) >= int(n):
|
| 635 |
+
break
|
| 636 |
+
if not recs_items:
|
| 637 |
+
raise inner_e
|
| 638 |
+
items = recs_items
|
| 639 |
+
|
| 640 |
+
recs = to_records(items)
|
| 641 |
+
html = render_quiz_html(recs)
|
| 642 |
+
return html, gr.update(visible=False), gr.update(visible=True), ""
|
| 643 |
+
except Exception as e:
|
| 644 |
+
err = f"❌ حدث خطأ أثناء التوليد:\n```\n{str(e)}\n```"
|
| 645 |
+
traceback.print_exc()
|
| 646 |
+
return "", gr.update(visible=True), gr.update(visible=False), err
|
| 647 |
|
| 648 |
# ------------------ CSS ------------------
|
| 649 |
CSS = """
|
|
|
|
| 677 |
.q-badge.ok{background:#0f2f22;color:#b6f4db;border:1px solid #145b44}
|
| 678 |
.q-badge.err{background:#3a0d14;color:#ffd1d6;border:1px solid #6a1e2b}
|
| 679 |
|
| 680 |
+
.q-text{color:var(--text);font-size:1.06rem;line-height:1.8;margin:8px 0 12px}
|
| 681 |
.opts{display:flex;flex-direction:column;gap:8px}
|
| 682 |
.opt{display:flex;gap:10px;align-items:center;background:#14161c;border:1px solid #2a2d3a;border-radius:12px;padding:10px;transition:background .15s,border-color .15s}
|
| 683 |
.opt input{accent-color:var(--accent2)}
|