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
|
|
@@ -70,8 +70,7 @@ def is_good(t: str, min_chars=250, min_alpha=0.15) -> bool:
|
|
| 70 |
def file_to_text(path: str, model_id=DEFAULT_TROCR_MODEL, zoom=DEFAULT_TROCR_ZOOM) -> Tuple[str,str]:
|
| 71 |
ext = Path(path).suffix.lower()
|
| 72 |
if ext == ".txt":
|
| 73 |
-
with open(path,"r",encoding="utf-8",errors="ignore") as f:
|
| 74 |
-
return f.read(), "plain text"
|
| 75 |
raw = extract_text_with_pypdf(path)
|
| 76 |
if is_good(raw): return raw, "embedded (pypdf)"
|
| 77 |
return extract_text_with_ocr(path, model_id, zoom), "OCR (TrOCR)"
|
|
@@ -82,7 +81,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)
|
|
@@ -226,7 +225,7 @@ 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;
|
|
@@ -236,16 +235,65 @@ body{direction:rtl; font-family:system-ui,'Cairo','IBM Plex Arabic',sans-serif;
|
|
| 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{
|
| 241 |
-
|
| 242 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
|
| 244 |
-
/*
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
|
|
|
|
|
|
| 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}
|
|
@@ -273,13 +321,12 @@ textarea{min-height:120px}
|
|
| 273 |
}
|
| 274 |
.q-actions .q-submit:disabled{opacity:.5;cursor:not-allowed}
|
| 275 |
.q-note{color:#ffd1d6}
|
| 276 |
-
.q-note.warn{color:#
|
| 277 |
"""
|
| 278 |
|
| 279 |
-
# ------------------ JS:
|
| 280 |
ATTACH_LISTENERS_JS = """
|
| 281 |
() => {
|
| 282 |
-
// اربط مرة واحدة فقط
|
| 283 |
if (window.__q_submit_bound_multi2) { return 'already'; }
|
| 284 |
window.__q_submit_bound_multi2 = true;
|
| 285 |
|
|
@@ -302,30 +349,26 @@ ATTACH_LISTENERS_JS = """
|
|
| 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>")
|
|
@@ -335,13 +378,23 @@ with gr.Blocks(title="Question Generator", css=CSS) as demo:
|
|
| 335 |
with page1:
|
| 336 |
gr.Markdown("اختر **أحد** الخيارين ثم اضغط الزر.", elem_classes=["small"])
|
| 337 |
text_area = gr.Textbox(lines=6, placeholder="ألصق نصك هنا...", label="لصق نص")
|
| 338 |
-
|
| 339 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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(
|
| 343 |
choices=[
|
| 344 |
-
"
|
| 345 |
"microsoft/trocr-large-printed",
|
| 346 |
"microsoft/trocr-base-handwritten",
|
| 347 |
"microsoft/trocr-large-handwritten",
|
|
|
|
| 1 |
# -*- coding: utf-8 -*-
|
| 2 |
+
# صفحتان ثابتتان + Submit لكل سؤال (محاولات متعددة) + واجهة إدخال لا تتغير أبعادها بعد رفع الملف
|
| 3 |
|
| 4 |
import os, json, uuid, random, unicodedata
|
| 5 |
from dataclasses import dataclass
|
|
|
|
| 70 |
def file_to_text(path: str, model_id=DEFAULT_TROCR_MODEL, zoom=DEFAULT_TROCR_ZOOM) -> Tuple[str,str]:
|
| 71 |
ext = Path(path).suffix.lower()
|
| 72 |
if ext == ".txt":
|
| 73 |
+
with open(path,"r",encoding="utf-8",errors="ignore") as f: return f.read(), "plain text"
|
|
|
|
| 74 |
raw = extract_text_with_pypdf(path)
|
| 75 |
if is_good(raw): return raw, "embedded (pypdf)"
|
| 76 |
return extract_text_with_ocr(path, model_id, zoom), "OCR (TrOCR)"
|
|
|
|
| 81 |
out=[]
|
| 82 |
for ln in t.splitlines():
|
| 83 |
if re2.match(r"^\s*--- \[Page \d+\] ---\s*$", ln): continue
|
| 84 |
+
if re2.match(r"^\s*(Page\s*\d+|صفحة\s*\د+)\s*$", ln): continue
|
| 85 |
if re2.match(r"^\s*[-–—_*]{3,}\s*$", ln): continue
|
| 86 |
out.append(ln)
|
| 87 |
return "\n".join(out)
|
|
|
|
| 225 |
recs = to_records(items)
|
| 226 |
return render_quiz_html(recs), gr.update(visible=False), gr.update(visible=True), ""
|
| 227 |
|
| 228 |
+
# ------------------ CSS (ثبات واجهة الإدخال + منع تمدد مكوّن الملف) ------------------
|
| 229 |
CSS = """
|
| 230 |
:root{
|
| 231 |
--bg:#0e0e11; --panel:#15161a; --card:#1a1b20; --muted:#a7b0be;
|
|
|
|
| 235 |
.gradio-container{max-width:980px;margin:0 auto;padding:12px 12px 40px;}
|
| 236 |
h2.top{color:#eaeaf2;margin:6px 0 16px}
|
| 237 |
|
| 238 |
+
/* 1) لوحة الإدخال ثابتة الارتفاع */
|
| 239 |
+
.input-panel{
|
| 240 |
+
background:var(--panel);
|
| 241 |
+
border:1px solid var(--border);
|
| 242 |
+
border-radius:14px;
|
| 243 |
+
padding:16px;
|
| 244 |
+
box-shadow:0 16px 38px rgba(0,0,0,.35);
|
| 245 |
+
min-height:420px; /* ارتفاع ثابت مريح */
|
| 246 |
+
display:flex; flex-direction:column; gap:12px;
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
/* 2) حاوية إسقاط ثابتة الارتفاع تمنع أي تمدد */
|
| 250 |
+
.drop-wrap{
|
| 251 |
+
position:relative;
|
| 252 |
+
height:120px; /* ثبّت ارتفاع منطقة الرفع */
|
| 253 |
+
min-height:120px; max-height:120px;
|
| 254 |
+
overflow:hidden; /* قصّ أي معاينات */
|
| 255 |
+
border:2px dashed #3b3f52;
|
| 256 |
+
background:#121318;
|
| 257 |
+
border-radius:12px;
|
| 258 |
+
padding:10px;
|
| 259 |
+
color:#cfd5e3;
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
/* 3) امنع مكوّن الملف من تغيير الحجم داخليًا مهما أضاف معاينات */
|
| 263 |
+
.drop-wrap [data-testid="file"]{
|
| 264 |
+
position:absolute; inset:0;
|
| 265 |
+
height:100%; width:100%;
|
| 266 |
+
overflow:hidden !important;
|
| 267 |
+
}
|
| 268 |
+
.drop-wrap [data-testid="file"] *{ max-height:100% !important; }
|
| 269 |
+
|
| 270 |
+
/* 4) إخفاء أي معاينة/شبكة/اسم ملف طويلة قد تضيفها إصدارات مختلفة من Gradio */
|
| 271 |
+
.drop-wrap [class*="preview"],
|
| 272 |
+
.drop-wrap [class*="file-preview"],
|
| 273 |
+
.drop-wrap .file-preview,
|
| 274 |
+
.drop-wrap .file-preview * ,
|
| 275 |
+
.drop-wrap .upload-preview,
|
| 276 |
+
.drop-wrap .grid,
|
| 277 |
+
.drop-wrap .grid-wrap,
|
| 278 |
+
.drop-wrap .thumbnail,
|
| 279 |
+
.drop-wrap .thumbnails,
|
| 280 |
+
.drop-wrap .label,
|
| 281 |
+
.drop-wrap .hidden,
|
| 282 |
+
.drop-wrap .file-name,
|
| 283 |
+
.drop-wrap .file-info,
|
| 284 |
+
.drop-wrap .file-size {
|
| 285 |
+
display:none !important;
|
| 286 |
+
}
|
| 287 |
|
| 288 |
+
/* 5) اترك منطقة النقر على الرفع فعّالة على المساحة كلها */
|
| 289 |
+
.drop-wrap input[type="file"]{
|
| 290 |
+
position:absolute; inset:0;
|
| 291 |
+
height:100%; width:100%;
|
| 292 |
+
opacity:0; cursor:pointer;
|
| 293 |
+
}
|
| 294 |
|
| 295 |
+
/* عناصر أخرى */
|
| 296 |
+
.small{opacity:.9;color:#d9dee8}
|
| 297 |
.button-primary>button{background:linear-gradient(180deg,var(--accent),var(--accent2));border:none;color:#0b0d10;font-weight:800}
|
| 298 |
.button-primary>button:hover{filter:brightness(.95)}
|
| 299 |
textarea{min-height:120px}
|
|
|
|
| 321 |
}
|
| 322 |
.q-actions .q-submit:disabled{opacity:.5;cursor:not-allowed}
|
| 323 |
.q-note{color:#ffd1d6}
|
| 324 |
+
.q-note.warn{color:#ffd1د6}
|
| 325 |
"""
|
| 326 |
|
| 327 |
+
# ------------------ JS: Submit (محاولات متعددة) ------------------
|
| 328 |
ATTACH_LISTENERS_JS = """
|
| 329 |
() => {
|
|
|
|
| 330 |
if (window.__q_submit_bound_multi2) { return 'already'; }
|
| 331 |
window.__q_submit_bound_multi2 = true;
|
| 332 |
|
|
|
|
| 349 |
|
| 350 |
const chosenLabel = chosen.closest('.opt');
|
| 351 |
|
| 352 |
+
// صحيح: اخضر + قفل السؤال كاملاً
|
| 353 |
if (chosen.value === correct) {
|
| 354 |
chosenLabel.classList.add('ok');
|
| 355 |
if (badge){ badge.hidden=false; badge.className='q-badge ok'; badge.textContent='Correct!'; }
|
|
|
|
| 356 |
card.querySelectorAll('input[type="radio"]').forEach(i => i.disabled = true);
|
| 357 |
e.target.disabled = true;
|
| 358 |
if (note) note.textContent = '';
|
| 359 |
return;
|
| 360 |
}
|
| 361 |
|
| 362 |
+
// خطأ: أحمر فقط، ولا تعطيل لأي عنصر — تسمح بمحاولات غير محدودة
|
| 363 |
+
chosenLabel.classList.add('err');
|
| 364 |
if (badge){ badge.hidden=false; badge.className='q-badge err'; badge.textContent='Incorrect.'; }
|
| 365 |
if (note) note.textContent = '';
|
|
|
|
| 366 |
});
|
| 367 |
|
| 368 |
return 'wired-multi2';
|
| 369 |
}
|
| 370 |
"""
|
| 371 |
|
|
|
|
|
|
|
| 372 |
# ------------------ واجهة Gradio ------------------
|
| 373 |
with gr.Blocks(title="Question Generator", css=CSS) as demo:
|
| 374 |
gr.Markdown("<h2 class='top'>Question Generator</h2>")
|
|
|
|
| 378 |
with page1:
|
| 379 |
gr.Markdown("اختر **أحد** الخيارين ثم اضغط الزر.", elem_classes=["small"])
|
| 380 |
text_area = gr.Textbox(lines=6, placeholder="ألصق نصك هنا...", label="لصق نص")
|
| 381 |
+
|
| 382 |
+
# مكوّن الملف داخل حاوية drop-wrap لتثبيت الارتفاع
|
| 383 |
+
with gr.Box(elem_classes=["drop-wrap"]):
|
| 384 |
+
file_comp = gr.File(
|
| 385 |
+
label="أو ارفع ملف (PDF / TXT)",
|
| 386 |
+
file_count="single",
|
| 387 |
+
file_types=[".pdf", ".txt"],
|
| 388 |
+
type="filepath",
|
| 389 |
+
elem_classes=[],
|
| 390 |
+
show_label=False
|
| 391 |
+
)
|
| 392 |
+
|
| 393 |
num_q = gr.Slider(4, 20, value=DEFAULT_NUM_QUESTIONS, step=1, label="عدد الأسئلة")
|
| 394 |
with gr.Accordion("خيارات PDF المصوّر (اختياري)", open=False):
|
| 395 |
trocr_model = gr.Dropdown(
|
| 396 |
choices=[
|
| 397 |
+
"مicrosoft/trocr-base-printed",
|
| 398 |
"microsoft/trocr-large-printed",
|
| 399 |
"microsoft/trocr-base-handwritten",
|
| 400 |
"microsoft/trocr-large-handwritten",
|