Leen172 commited on
Commit
ff8b320
·
verified ·
1 Parent(s): 5e790a4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +29 -69
app.py CHANGED
@@ -1,5 +1,5 @@
1
  # -*- coding: utf-8 -*-
2
- # صفحتان ثابتتان + Submit لكل سؤال يعمل فعليًا + واجهة إدخال واسعة وثابتة الأبعاد (بدون gr.Box)
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*\د+)\s*$", ln): continue
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"\[\د+\]", " ", t)
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)>=ن: 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,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:1180px; margin:0 auto; padding:16px 16px 44px;} /* أوسع من قبل */
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
- /* استبدال gr.Box بـ gr.Group(elem_id='drop_wrap') والتنسيق حسب الـID */
252
- #drop_wrap{
253
- position:relative;
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:#text;font-size:1.06rem;line-height:1.8;margin:8px 0 12px}
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
- # ✅ استبدال gr.Box بـ gr.Group(elem_id="drop_wrap")
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(