Leen172 commited on
Commit
35b1e43
·
verified ·
1 Parent(s): 69094d6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +37 -53
app.py CHANGED
@@ -1,5 +1,5 @@
1
  # -*- coding: utf-8 -*-
2
- # app.py — واجهة داكنة حديثة: توليد أسئلة ➜ عرض كل الأسئلة دفعة واحدة Submit للنتيجة
3
 
4
  import os, json, uuid, random, unicodedata
5
  from dataclasses import dataclass
@@ -91,7 +91,7 @@ def norm_ar(t:str)->str:
91
  t = re2.sub(AR_DIAC, "", t)
92
  t = re2.sub(r"[إأآا]", "ا", t)
93
  t = re2.sub(r"[يى]", "ي", t)
94
- t = re2.sub(r"\s+", " ", t)
95
  t = re2.sub(r'(\p{L})\1{2,}', r'\1', t)
96
  t = re2.sub(r'(\p{L})\1', r'\1', t)
97
  return t.strip()
@@ -198,7 +198,6 @@ def init_state(records):
198
 
199
  # ---------- HTML للواجهة/الاختبار ----------
200
  def render_quiz_html(records: List[dict]) -> str:
201
- # يبني HTML فيه كل الأسئلة دفعة واحدة (راديو لكل سؤال)
202
  parts = []
203
  for i, rec in enumerate(records, start=1):
204
  qid = rec["id"]
@@ -206,9 +205,7 @@ def render_quiz_html(records: List[dict]) -> str:
206
  opts = rec["options"]
207
  opts_html = []
208
  for o in opts:
209
- lid = o["id"]
210
- txt = o["text"]
211
- # name = q_<id>, value = letter
212
  opts_html.append(f"""
213
  <label class="opt">
214
  <input type="radio" name="q_{qid}" value="{lid}" />
@@ -216,23 +213,16 @@ def render_quiz_html(records: List[dict]) -> str:
216
  <span class="opt-text">{txt}</span>
217
  </label>
218
  """)
219
- card = f"""
220
  <div class="q-card" data-qid="{qid}">
221
  <div class="q-title">السؤال {i}:</div>
222
  <div class="q-text">{qtxt}</div>
223
  <div class="opts">{''.join(opts_html)}</div>
224
  </div>
225
- """
226
- parts.append(card)
227
 
228
- html = f"""
229
- <div class="quiz-wrap">
230
- {''.join(parts)}
231
- </div>
232
- """
233
- return html
234
-
235
- # ---------- معالجة الإدخال (نص أو ملف) ----------
236
  def build_quiz(text_area, file_path, n, model_id, zoom):
237
  text_area = (text_area or "").strip()
238
  if not text_area and not file_path:
@@ -289,27 +279,26 @@ def grade(state, answers_json):
289
 
290
  return score_md, mistakes_md
291
 
292
- # ---------- الثيم (CSS داكن حديث) ----------
293
  CSS = """
294
  :root{
295
  --bg:#0f0f0f; --panel:#1a1a1a; --card:#1b1b1b; --muted:#9aa0a6;
296
  --text:#f5efe6; --accent:#ff7d2d; --accent2:#ff9a55; --border:#2a2a2a;
297
  }
298
  body{direction:rtl; font-family:system-ui,'Cairo','IBM Plex Arabic',sans-serif; background:var(--bg);}
299
- .gradio-container{max-width:1024px;margin:0 auto;padding:8px 8px 40px;}
300
  .top-title{color:#e9ded6;margin:8px 0 16px 0}
301
-
302
  .panel{background:var(--panel);border:1px solid var(--border);border-radius:16px;padding:16px;box-shadow:0 20px 45px rgba(0,0,0,.4)}
303
  .small{opacity:.85;color:#ddd}
304
 
305
- .button-primary > button{
306
- background:linear-gradient(180deg,var(--accent2),var(--accent));
307
- border:none;color:#161616;font-weight:700;
308
- }
309
  .button-primary > button:hover{filter:brightness(.95)}
310
  .upload-like{border:2px dashed #ff9a5555;background:#141414;border-radius:12px;padding:10px;color:#ddd}
311
 
312
- /* حقل النص أصغر */
 
 
 
313
  textarea{min-height:140px}
314
 
315
  /* بطاقة السؤال */
@@ -321,8 +310,6 @@ textarea{min-height:140px}
321
  .opt input{accent-color:var(--accent)}
322
  .opt-letter{display:inline-flex;width:28px;height:28px;border-radius:8px;background:#222;align-items:center;justify-content:center;font-weight:800;color:#f1f1f1}
323
  .opt-text{color:#eaeaea}
324
-
325
- /* قسم النتيجة */
326
  .result-card{background:#121212;border:1px solid #2a2a2a;border-radius:16px;padding:16px;margin-top:18px}
327
  """
328
 
@@ -330,35 +317,30 @@ textarea{min-height:140px}
330
  with gr.Blocks(title="Question Generator", css=CSS) as demo:
331
  gr.Markdown("<h2 class='top-title'>Question Generator</h2>")
332
 
333
- # لوحة الإدخال (ثابتة)
334
- with gr.Group(elem_classes=["panel"]):
335
- gr.Markdown("**ارفع ملفك أو ألصق نصًا، حدّد عدد الأسئلة، ثم اضغط توليد.**", elem_classes=["small"])
336
- with gr.Row():
337
- with gr.Column(scale=1):
338
- file_comp = gr.File(label="اختر ملف PDF أو TXT", file_count="single",
339
- file_types=[".pdf",".txt"], type="filepath", elem_classes=["upload-like"])
340
- with gr.Accordion("خيارات متقدمة (لـ PDF المصوّر)", open=False):
341
- trocr_model = gr.Dropdown(
342
- choices=[
343
- "microsoft/trocr-base-printed",
344
- "microsoft/trocr-large-printed",
345
- "microsoft/trocr-base-handwritten",
346
- "microsoft/trocr-large-handwritten",
347
- ],
348
- value=DEFAULT_TROCR_MODEL, label="نموذج TrOCR"
349
- )
350
- trocr_zoom = gr.Slider(2.0, 3.5, value=DEFAULT_TROCR_ZOOM, step=0.1, label="Zoom OCR")
351
- with gr.Column(scale=1):
352
- text_area = gr.Textbox(lines=6, placeholder="ألصق هنا مقطع نصي...", label="أدخل نصًا أو ارفع ملفًا")
353
- num_q = gr.Slider(4, 20, value=DEFAULT_NUM_QUESTIONS, step=1, label="عدد الأسئلة")
354
  btn_build = gr.Button("توليد الأسئلة", elem_classes=["button-primary"])
355
  toast = gr.Markdown("", elem_classes=["small"])
356
 
357
- # حالة عامة + مكان عرض الاختبار
358
  state = gr.State(None)
359
  quiz_html = gr.HTML("") # كل الأسئلة ستُعرض هنا دفعة واحدة
360
-
361
- # زر الإرسال + حقل سري لتجميع الإجابات + مكان النتيجة
362
  btn_submit = gr.Button("إنهاء وإرسال الإجابات", elem_classes=["button-primary"])
363
  answers_box = gr.Textbox(visible=False)
364
  score_md = gr.Markdown("")
@@ -371,9 +353,11 @@ with gr.Blocks(title="Question Generator", css=CSS) as demo:
371
  outputs=[state, quiz_html, toast]
372
  )
373
 
374
- # JS لالتقاط الإجابات من الـ HTML (جميع الأسئلة)
375
  js_collect = """
376
  function () {
 
 
377
  const data = {};
378
  document.querySelectorAll('.q-card').forEach(card => {
379
  const qid = card.getAttribute('data-qid');
@@ -384,7 +368,7 @@ with gr.Blocks(title="Question Generator", css=CSS) as demo:
384
  }
385
  """
386
 
387
- # عند Submit: نجمع الإجابات بالـJS ثم نقيّمها في الباك إند
388
  btn_submit.click(
389
  None, inputs=None, outputs=[answers_box], js=js_collect
390
  ).then(
 
1
  # -*- coding: utf-8 -*-
2
+ # app.py — ثيم داكن ثابت + كل الأسئلة دفعة واحدة + Submit لعرض النتيجة والأخطاء
3
 
4
  import os, json, uuid, random, unicodedata
5
  from dataclasses import dataclass
 
91
  t = re2.sub(AR_DIAC, "", t)
92
  t = re2.sub(r"[إأآا]", "ا", t)
93
  t = re2.sub(r"[يى]", "ي", t)
94
+ t = re2.sub(r"\س+", " ", t) if False else re2.sub(r"\s+", " ", t)
95
  t = re2.sub(r'(\p{L})\1{2,}', r'\1', t)
96
  t = re2.sub(r'(\p{L})\1', r'\1', t)
97
  return t.strip()
 
198
 
199
  # ---------- HTML للواجهة/الاختبار ----------
200
  def render_quiz_html(records: List[dict]) -> str:
 
201
  parts = []
202
  for i, rec in enumerate(records, start=1):
203
  qid = rec["id"]
 
205
  opts = rec["options"]
206
  opts_html = []
207
  for o in opts:
208
+ lid = o["id"]; txt = o["text"]
 
 
209
  opts_html.append(f"""
210
  <label class="opt">
211
  <input type="radio" name="q_{qid}" value="{lid}" />
 
213
  <span class="opt-text">{txt}</span>
214
  </label>
215
  """)
216
+ parts.append(f"""
217
  <div class="q-card" data-qid="{qid}">
218
  <div class="q-title">السؤال {i}:</div>
219
  <div class="q-text">{qtxt}</div>
220
  <div class="opts">{''.join(opts_html)}</div>
221
  </div>
222
+ """)
223
+ return f"""<div id="quiz" class="quiz-wrap">{''.join(parts)}</div>"""
224
 
225
+ # ---------- معالجة الإدخال ----------
 
 
 
 
 
 
 
226
  def build_quiz(text_area, file_path, n, model_id, zoom):
227
  text_area = (text_area or "").strip()
228
  if not text_area and not file_path:
 
279
 
280
  return score_md, mistakes_md
281
 
282
+ # ---------- الثيم (CSS داكن ثابت) ----------
283
  CSS = """
284
  :root{
285
  --bg:#0f0f0f; --panel:#1a1a1a; --card:#1b1b1b; --muted:#9aa0a6;
286
  --text:#f5efe6; --accent:#ff7d2d; --accent2:#ff9a55; --border:#2a2a2a;
287
  }
288
  body{direction:rtl; font-family:system-ui,'Cairo','IBM Plex Arabic',sans-serif; background:var(--bg);}
289
+ .gradio-container{max-width:980px;margin:0 auto;padding:8px 8px 40px;}
290
  .top-title{color:#e9ded6;margin:8px 0 16px 0}
 
291
  .panel{background:var(--panel);border:1px solid var(--border);border-radius:16px;padding:16px;box-shadow:0 20px 45px rgba(0,0,0,.4)}
292
  .small{opacity:.85;color:#ddd}
293
 
294
+ .button-primary > button{background:linear-gradient(180deg,var(--accent2),var(--accent));border:none;color:#161616;font-weight:700;}
 
 
 
295
  .button-primary > button:hover{filter:brightness(.95)}
296
  .upload-like{border:2px dashed #ff9a5555;background:#141414;border-radius:12px;padding:10px;color:#ddd}
297
 
298
+ /* ترتيب عمودي ثابت */
299
+ .input-stack > div {margin-bottom:12px}
300
+
301
+ /* حقل النص أصغر وثابت */
302
  textarea{min-height:140px}
303
 
304
  /* بطاقة السؤال */
 
310
  .opt input{accent-color:var(--accent)}
311
  .opt-letter{display:inline-flex;width:28px;height:28px;border-radius:8px;background:#222;align-items:center;justify-content:center;font-weight:800;color:#f1f1f1}
312
  .opt-text{color:#eaeaea}
 
 
313
  .result-card{background:#121212;border:1px solid #2a2a2a;border-radius:16px;padding:16px;margin-top:18px}
314
  """
315
 
 
317
  with gr.Blocks(title="Question Generator", css=CSS) as demo:
318
  gr.Markdown("<h2 class='top-title'>Question Generator</h2>")
319
 
320
+ # لوحة إدخال عمودية ثابتة (لا تتقلب)
321
+ with gr.Group(elem_classes=["panel","input-stack"]):
322
+ gr.Markdown("**أدخل نصًا أو ارفع ملفًا، حدّد عدد الأسئلة ثم اضغط توليد.**", elem_classes=["small"])
323
+ text_area = gr.Textbox(lines=6, placeholder="ألصق هنا مقطع نصي...", label="أدخل نصًا أو ارفع ملفًا")
324
+ num_q = gr.Slider(4, 20, value=DEFAULT_NUM_QUESTIONS, step=1, label="عدد الأسئلة")
325
+ file_comp = gr.File(label="اختر ملف PDF أو TXT", file_count="single",
326
+ file_types=[".pdf",".txt"], type="filepath", elem_classes=["upload-like"])
327
+ with gr.Accordion("خيارات متقدمة (لـ PDF المصوّر)", open=False):
328
+ trocr_model = gr.Dropdown(
329
+ choices=[
330
+ "microsoft/trocr-base-printed",
331
+ "microsoft/trocr-large-printed",
332
+ "microsoft/trocr-base-handwritten",
333
+ "microsoft/trocr-large-handwritten",
334
+ ],
335
+ value=DEFAULT_TROCR_MODEL, label="نموذج TrOCR"
336
+ )
337
+ trocr_zoom = gr.Slider(2.0, 3.5, value=DEFAULT_TROCR_ZOOM, step=0.1, label="Zoom OCR")
 
 
 
338
  btn_build = gr.Button("توليد الأسئلة", elem_classes=["button-primary"])
339
  toast = gr.Markdown("", elem_classes=["small"])
340
 
341
+ # حالة عامة + مكان عرض الاختبار + إرساله
342
  state = gr.State(None)
343
  quiz_html = gr.HTML("") # كل الأسئلة ستُعرض هنا دفعة واحدة
 
 
344
  btn_submit = gr.Button("إنهاء وإرسال الإجابات", elem_classes=["button-primary"])
345
  answers_box = gr.Textbox(visible=False)
346
  score_md = gr.Markdown("")
 
353
  outputs=[state, quiz_html, toast]
354
  )
355
 
356
+ # JS لالتقاط الإجابات + سكرول للأسئلة بعد التوليد
357
  js_collect = """
358
  function () {
359
+ const quiz = document.getElementById('quiz');
360
+ if (quiz) { quiz.scrollIntoView({behavior:'smooth', block:'start'}); }
361
  const data = {};
362
  document.querySelectorAll('.q-card').forEach(card => {
363
  const qid = card.getAttribute('data-qid');
 
368
  }
369
  """
370
 
371
+ # Submit: نجمع الإجابات بالـJS ثم نقيّمها
372
  btn_submit.click(
373
  None, inputs=None, outputs=[answers_box], js=js_collect
374
  ).then(