Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
# -*- coding: utf-8 -*-
|
| 2 |
-
# صفحتان ثابتتان + Submit لكل سؤال يعمل فعليًا +
|
| 3 |
|
| 4 |
import os, json, uuid, random, unicodedata
|
| 5 |
from dataclasses import dataclass
|
|
@@ -82,7 +82,7 @@ def strip_headers(t:str)->str:
|
|
| 82 |
out=[]
|
| 83 |
for ln in t.splitlines():
|
| 84 |
if re2.match(r"^\s*--- \[Page \d+\] ---\s*$", ln): continue
|
| 85 |
-
if re2.match(r"^\s*(Page\s*\d+|صفحة\s
|
| 86 |
if re2.match(r"^\s*[-–—_*]{3,}\s*$", ln): continue
|
| 87 |
out.append(ln)
|
| 88 |
return "\n".join(out)
|
|
@@ -102,7 +102,7 @@ def postprocess(raw:str)->str:
|
|
| 102 |
t = strip_headers(raw).replace("\r","\n")
|
| 103 |
t = re2.sub(r"\n{3,}", "\n\n", t)
|
| 104 |
t = re2.sub(r"\d+\s*[\[\(][^\]\)]*[\]\)]", " ", t)
|
| 105 |
-
t = re2.sub(r"\[
|
| 106 |
return norm_ar(t)
|
| 107 |
|
| 108 |
# ------------------ توليد أسئلة ------------------
|
|
@@ -157,7 +157,7 @@ def make_mcqs(text:str, n:int=6)->List[MCQ]:
|
|
| 157 |
sent_for[kw]=s
|
| 158 |
items=[]; used=set()
|
| 159 |
for kw in [k for k in kws if k in sent_for]:
|
| 160 |
-
if len(items)
|
| 161 |
s=sent_for[kw]
|
| 162 |
if s in used: continue
|
| 163 |
q=re2.sub(rf"(?<!\p{{L}}){re2.escape(kw)}(?!\p{{L}})", "_____", s, count=1)
|
|
@@ -226,65 +226,26 @@ def build_quiz(text_area, file_path, n, model_id, zoom):
|
|
| 226 |
recs = to_records(items)
|
| 227 |
return render_quiz_html(recs), gr.update(visible=False), gr.update(visible=True), ""
|
| 228 |
|
| 229 |
-
# ------------------ CSS
|
| 230 |
CSS = """
|
| 231 |
:root{
|
| 232 |
--bg:#0e0e11; --panel:#15161a; --card:#1a1b20; --muted:#a7b0be;
|
| 233 |
--text:#f6f7fb; --accent:#6ee7b7; --accent2:#34d399; --danger:#ef4444; --border:#262833;
|
| 234 |
}
|
| 235 |
body{direction:rtl; font-family:system-ui,'Cairo','IBM Plex Arabic',sans-serif; background:var(--bg);}
|
| 236 |
-
.gradio-container{max-width:
|
| 237 |
-
|
| 238 |
-
h2.top{color:#eaeaf2;margin:8px 0 18px}
|
| 239 |
-
|
| 240 |
-
/* الحاوية الرئيسية للواجهة الأولى — واسعة وثابتة الارتفاع */
|
| 241 |
-
.input-panel{
|
| 242 |
-
background:var(--panel);
|
| 243 |
-
border:1px solid var(--border);
|
| 244 |
-
border-radius:16px;
|
| 245 |
-
padding:18px;
|
| 246 |
-
box-shadow:0 18px 42px rgba(0,0,0,.38);
|
| 247 |
-
min-height:520px; /* رفعتها لتكون واسعة وثابتة */
|
| 248 |
-
display:flex; flex-direction:column; gap:14px;
|
| 249 |
-
}
|
| 250 |
|
| 251 |
-
/*
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
height:140px;
|
| 255 |
-
min-height:140px; max-height:140px;
|
| 256 |
-
overflow:hidden;
|
| 257 |
-
border:2px dashed #3b3f52;
|
| 258 |
-
background:#121318;
|
| 259 |
-
border-radius:12px;
|
| 260 |
-
padding:12px;
|
| 261 |
-
color:#cfd5e3;
|
| 262 |
-
}
|
| 263 |
-
#drop_wrap [data-testid="file"]{
|
| 264 |
-
position:absolute; inset:0; height:100%; width:100%;
|
| 265 |
-
overflow:hidden !important;
|
| 266 |
-
}
|
| 267 |
-
#drop_wrap [data-testid="file"] *{ max-height:100% !important; }
|
| 268 |
-
|
| 269 |
-
/* إخفاء كل أشكال المعاينة/الشبكات/الأسماء الطويلة */
|
| 270 |
-
#drop_wrap [class*="preview"],
|
| 271 |
-
#drop_wrap [class*="file-preview"],
|
| 272 |
-
#drop_wrap .file-preview,
|
| 273 |
-
#drop_wrap .file-preview *,
|
| 274 |
-
#drop_wrap .upload-preview,
|
| 275 |
-
#drop_wrap .grid, #drop_wrap .grid-wrap,
|
| 276 |
-
#drop_wrap .thumbnail, #drop_wrap .thumbnails,
|
| 277 |
-
#drop_wrap .label, #drop_wrap .hidden,
|
| 278 |
-
#drop_wrap .file-name, #drop_wrap .file-info, #drop_wrap .file-size {
|
| 279 |
-
display:none !important;
|
| 280 |
-
}
|
| 281 |
-
#drop_wrap input[type="file"]{
|
| 282 |
-
position:absolute; inset:0; height:100%; width:100%;
|
| 283 |
-
opacity:0; cursor:pointer;
|
| 284 |
-
}
|
| 285 |
-
|
| 286 |
-
/* باقي التنسيقات */
|
| 287 |
.small{opacity:.9;color:#d9dee8}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 288 |
.button-primary>button{background:linear-gradient(180deg,var(--accent),var(--accent2));border:none;color:#0b0d10;font-weight:800}
|
| 289 |
.button-primary>button:hover{filter:brightness(.95)}
|
| 290 |
textarea{min-height:120px}
|
|
@@ -297,7 +258,7 @@ textarea{min-height:120px}
|
|
| 297 |
.q-badge.ok{background:#083a2a;color:#b6f4db;border:1px solid #145b44}
|
| 298 |
.q-badge.err{background:#3a0d14;color:#ffd1d6;border:1px solid #6a1e2b}
|
| 299 |
|
| 300 |
-
.q-text{color
|
| 301 |
.opts{display:flex;flex-direction:column;gap:8px}
|
| 302 |
.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}
|
| 303 |
.opt input{accent-color:var(--accent2)}
|
|
@@ -315,9 +276,10 @@ textarea{min-height:120px}
|
|
| 315 |
.q-note.warn{color:#ffd1d6}
|
| 316 |
"""
|
| 317 |
|
| 318 |
-
# ------------------ JS: Submit (
|
| 319 |
ATTACH_LISTENERS_JS = """
|
| 320 |
() => {
|
|
|
|
| 321 |
if (window.__q_submit_bound_multi2) { return 'already'; }
|
| 322 |
window.__q_submit_bound_multi2 = true;
|
| 323 |
|
|
@@ -340,43 +302,41 @@ ATTACH_LISTENERS_JS = """
|
|
| 340 |
|
| 341 |
const chosenLabel = chosen.closest('.opt');
|
| 342 |
|
| 343 |
-
//
|
| 344 |
if (chosen.value === correct) {
|
| 345 |
chosenLabel.classList.add('ok');
|
| 346 |
if (badge){ badge.hidden=false; badge.className='q-badge ok'; badge.textContent='Correct!'; }
|
|
|
|
| 347 |
card.querySelectorAll('input[type="radio"]').forEach(i => i.disabled = true);
|
| 348 |
e.target.disabled = true;
|
| 349 |
if (note) note.textContent = '';
|
| 350 |
return;
|
| 351 |
}
|
| 352 |
|
| 353 |
-
//
|
| 354 |
-
chosenLabel.classList.add('err');
|
| 355 |
if (badge){ badge.hidden=false; badge.className='q-badge err'; badge.textContent='Incorrect.'; }
|
| 356 |
if (note) note.textContent = '';
|
|
|
|
| 357 |
});
|
| 358 |
|
| 359 |
return 'wired-multi2';
|
| 360 |
}
|
| 361 |
"""
|
| 362 |
|
|
|
|
|
|
|
| 363 |
# ------------------ واجهة Gradio ------------------
|
| 364 |
with gr.Blocks(title="Question Generator", css=CSS) as demo:
|
| 365 |
gr.Markdown("<h2 class='top'>Question Generator</h2>")
|
| 366 |
|
| 367 |
-
# الصفحة 1: إدخال
|
| 368 |
page1 = gr.Group(visible=True, elem_classes=["input-panel"])
|
| 369 |
with page1:
|
| 370 |
gr.Markdown("اختر **أحد** الخيارين ثم اضغط الزر.", elem_classes=["small"])
|
| 371 |
text_area = gr.Textbox(lines=6, placeholder="ألصق نصك هنا...", label="لصق نص")
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
with gr.Group(elem_id="drop_wrap"):
|
| 375 |
-
file_comp = gr.File(
|
| 376 |
-
label=None, file_count="single",
|
| 377 |
-
file_types=[".pdf",".txt"], type="filepath"
|
| 378 |
-
)
|
| 379 |
-
|
| 380 |
num_q = gr.Slider(4, 20, value=DEFAULT_NUM_QUESTIONS, step=1, label="عدد الأسئلة")
|
| 381 |
with gr.Accordion("خيارات PDF المصوّر (اختياري)", open=False):
|
| 382 |
trocr_model = gr.Dropdown(
|
|
|
|
| 1 |
# -*- coding: utf-8 -*-
|
| 2 |
+
# صفحتان ثابتتان + Submit لكل سؤال يعمل فعليًا + منع تغيّر أبعاد صفحة الإدخال
|
| 3 |
|
| 4 |
import os, json, uuid, random, unicodedata
|
| 5 |
from dataclasses import dataclass
|
|
|
|
| 82 |
out=[]
|
| 83 |
for ln in t.splitlines():
|
| 84 |
if re2.match(r"^\s*--- \[Page \d+\] ---\s*$", ln): continue
|
| 85 |
+
if re2.match(r"^\s*(Page\s*\d+|صفحة\s*\d+)\s*$", ln): continue
|
| 86 |
if re2.match(r"^\s*[-–—_*]{3,}\s*$", ln): continue
|
| 87 |
out.append(ln)
|
| 88 |
return "\n".join(out)
|
|
|
|
| 102 |
t = strip_headers(raw).replace("\r","\n")
|
| 103 |
t = re2.sub(r"\n{3,}", "\n\n", t)
|
| 104 |
t = re2.sub(r"\d+\s*[\[\(][^\]\)]*[\]\)]", " ", t)
|
| 105 |
+
t = re2.sub(r"\[\d+\]", " ", t)
|
| 106 |
return norm_ar(t)
|
| 107 |
|
| 108 |
# ------------------ توليد أسئلة ------------------
|
|
|
|
| 157 |
sent_for[kw]=s
|
| 158 |
items=[]; used=set()
|
| 159 |
for kw in [k for k in kws if k in sent_for]:
|
| 160 |
+
if len(items)>=n: break
|
| 161 |
s=sent_for[kw]
|
| 162 |
if s in used: continue
|
| 163 |
q=re2.sub(rf"(?<!\p{{L}}){re2.escape(kw)}(?!\p{{L}})", "_____", s, count=1)
|
|
|
|
| 226 |
recs = to_records(items)
|
| 227 |
return render_quiz_html(recs), gr.update(visible=False), gr.update(visible=True), ""
|
| 228 |
|
| 229 |
+
# ------------------ CSS ------------------
|
| 230 |
CSS = """
|
| 231 |
:root{
|
| 232 |
--bg:#0e0e11; --panel:#15161a; --card:#1a1b20; --muted:#a7b0be;
|
| 233 |
--text:#f6f7fb; --accent:#6ee7b7; --accent2:#34d399; --danger:#ef4444; --border:#262833;
|
| 234 |
}
|
| 235 |
body{direction:rtl; font-family:system-ui,'Cairo','IBM Plex Arabic',sans-serif; background:var(--bg);}
|
| 236 |
+
.gradio-container{max-width:980px;margin:0 auto;padding:12px 12px 40px;}
|
| 237 |
+
h2.top{color:#eaeaf2;margin:6px 0 16px}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 238 |
|
| 239 |
+
/* صفحة الإدخال ثابتة الارتفاع ولا تتغير بعد الرفع */
|
| 240 |
+
.input-panel{background:var(--panel);border:1px solid var(--border);border-radius:14px;padding:16px;
|
| 241 |
+
box-shadow:0 16px 38px rgba(0,0,0,.35); min-height:360px; display:flex; flex-direction:column; gap:12px;}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
.small{opacity:.9;color:#d9dee8}
|
| 243 |
+
|
| 244 |
+
/* منع لوحة المعاينة الخاصة بالملفات التي تغيّر التخطيط */
|
| 245 |
+
[data-testid="file"] .file-preview, [data-testid="file"] .file-preview * { display:none !important; }
|
| 246 |
+
[data-testid="file"] .grid-wrap { display:block !important; }
|
| 247 |
+
.upload-like{border:2px dashed #3b3f52;background:#121318;border-radius:12px;padding:12px;color:#cfd5e3;min-height:90px}
|
| 248 |
+
|
| 249 |
.button-primary>button{background:linear-gradient(180deg,var(--accent),var(--accent2));border:none;color:#0b0d10;font-weight:800}
|
| 250 |
.button-primary>button:hover{filter:brightness(.95)}
|
| 251 |
textarea{min-height:120px}
|
|
|
|
| 258 |
.q-badge.ok{background:#083a2a;color:#b6f4db;border:1px solid #145b44}
|
| 259 |
.q-badge.err{background:#3a0d14;color:#ffd1d6;border:1px solid #6a1e2b}
|
| 260 |
|
| 261 |
+
.q-text{color:var(--text);font-size:1.06rem;line-height:1.8;margin:8px 0 12px}
|
| 262 |
.opts{display:flex;flex-direction:column;gap:8px}
|
| 263 |
.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}
|
| 264 |
.opt input{accent-color:var(--accent2)}
|
|
|
|
| 276 |
.q-note.warn{color:#ffd1d6}
|
| 277 |
"""
|
| 278 |
|
| 279 |
+
# ------------------ JS: ربط Submit بعد الرندر (مع Output مخفي لضمان التنفيذ) ------------------
|
| 280 |
ATTACH_LISTENERS_JS = """
|
| 281 |
() => {
|
| 282 |
+
// اربط مرة واحدة فقط
|
| 283 |
if (window.__q_submit_bound_multi2) { return 'already'; }
|
| 284 |
window.__q_submit_bound_multi2 = true;
|
| 285 |
|
|
|
|
| 302 |
|
| 303 |
const chosenLabel = chosen.closest('.opt');
|
| 304 |
|
| 305 |
+
// حالة صحيحة: لوّن أخضر وأقفل السؤال كاملاً
|
| 306 |
if (chosen.value === correct) {
|
| 307 |
chosenLabel.classList.add('ok');
|
| 308 |
if (badge){ badge.hidden=false; badge.className='q-badge ok'; badge.textContent='Correct!'; }
|
| 309 |
+
// أقفل هذا السؤال فقط بعد الصح
|
| 310 |
card.querySelectorAll('input[type="radio"]').forEach(i => i.disabled = true);
|
| 311 |
e.target.disabled = true;
|
| 312 |
if (note) note.textContent = '';
|
| 313 |
return;
|
| 314 |
}
|
| 315 |
|
| 316 |
+
// حالة خاطئة: لوّن أحمر فقط، ولا تعطل أي شيء — ليقدر يجرّب خيار آخر
|
| 317 |
+
chosenLabel.classList.add('err'); // اتركه أحمر
|
| 318 |
if (badge){ badge.hidden=false; badge.className='q-badge err'; badge.textContent='Incorrect.'; }
|
| 319 |
if (note) note.textContent = '';
|
| 320 |
+
// مهم: لا تعطّل الراديو ولا الزر
|
| 321 |
});
|
| 322 |
|
| 323 |
return 'wired-multi2';
|
| 324 |
}
|
| 325 |
"""
|
| 326 |
|
| 327 |
+
|
| 328 |
+
|
| 329 |
# ------------------ واجهة Gradio ------------------
|
| 330 |
with gr.Blocks(title="Question Generator", css=CSS) as demo:
|
| 331 |
gr.Markdown("<h2 class='top'>Question Generator</h2>")
|
| 332 |
|
| 333 |
+
# الصفحة 1: إدخال ثابت لا تتغير أبعاده
|
| 334 |
page1 = gr.Group(visible=True, elem_classes=["input-panel"])
|
| 335 |
with page1:
|
| 336 |
gr.Markdown("اختر **أحد** الخيارين ثم اضغط الزر.", elem_classes=["small"])
|
| 337 |
text_area = gr.Textbox(lines=6, placeholder="ألصق نصك هنا...", label="لصق نص")
|
| 338 |
+
file_comp = gr.File(label="أو ارفع ملف (PDF / TXT)", file_count="single",
|
| 339 |
+
file_types=[".pdf",".txt"], type="filepath", elem_classes=["upload-like"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 340 |
num_q = gr.Slider(4, 20, value=DEFAULT_NUM_QUESTIONS, step=1, label="عدد الأسئلة")
|
| 341 |
with gr.Accordion("خيارات PDF المصوّر (اختياري)", open=False):
|
| 342 |
trocr_model = gr.Dropdown(
|