Leen172 commited on
Commit
ffbcc2c
·
verified ·
1 Parent(s): 3701246

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +34 -37
app.py CHANGED
@@ -1,5 +1,5 @@
1
  # -*- coding: utf-8 -*-
2
- # واجهة حديثة: سؤال + زر تحقق لكل سؤال، تلوين أخضر/أحمر مباشرة، بدون نتيجة نهائية
3
 
4
  import os, json, uuid, random, unicodedata
5
  from dataclasses import dataclass
@@ -176,7 +176,7 @@ def to_records(items:List[MCQ])->List[dict]:
176
  recs.append({"id":it.id,"question":it.question.strip(),"options":opts})
177
  return recs
178
 
179
- # ------------------ HTML: كل الأسئلة + زر تحقق لكل سؤال ------------------
180
  def render_quiz_html(records: List[dict]) -> str:
181
  parts=[]
182
  for i, rec in enumerate(records, start=1):
@@ -202,18 +202,16 @@ def render_quiz_html(records: List[dict]) -> str:
202
  <div class="q-text">{qtxt}</div>
203
  <div class="opts">{''.join(opts_html)}</div>
204
  <div class="q-actions">
205
- <button class="q-submit">تحقّق</button>
206
  <span class="q-note" id="n_{qid}"></span>
207
  </div>
208
  </div>
209
  """)
210
  html = f"""
211
  <div id="quiz" class="quiz-wrap">
212
- <div class="hint">اختر إجابة لكل سؤال ثم اضغط <b>تحقّق</b>، سيتم تلوين اختيارك أخضر/أحمر ويُقفل السؤال.</div>
213
  {''.join(parts)}
214
  </div>
215
  <script>
216
- // تفويض حدث للنقر على كل أزرار "تحقّق"
217
  document.querySelectorAll('.q-card .q-submit').forEach(btn => {{
218
  btn.addEventListener('click', (e) => {{
219
  const card = e.target.closest('.q-card');
@@ -224,14 +222,11 @@ def render_quiz_html(records: List[dict]) -> str:
224
  const chosenInput = card.querySelector('input[type="radio"]:checked');
225
 
226
  if (!chosenInput) {{
227
- if (note) {{
228
- note.textContent = 'اختر إجابة أولاً';
229
- note.className = 'q-note warn';
230
- }}
231
  return;
232
  }}
233
 
234
- // امسح ألوان قديمة
235
  card.querySelectorAll('.opt').forEach(l => l.classList.remove('ok','err'));
236
 
237
  const chosen = chosenInput.value;
@@ -239,13 +234,13 @@ def render_quiz_html(records: List[dict]) -> str:
239
 
240
  if (chosen === correct) {{
241
  chosenLabel.classList.add('ok');
242
- if (badge) {{ badge.hidden = false; badge.className='q-badge ok'; badge.textContent='Correct!'; }}
243
  }} else {{
244
  chosenLabel.classList.add('err');
245
- if (badge) {{ badge.hidden = false; badge.className='q-badge err'; badge.textContent='Incorrect.'; }}
246
  }}
247
 
248
- // قفل السؤال بعد التحقق
249
  card.querySelectorAll('input[type="radio"]').forEach(i => i.disabled = true);
250
  e.target.disabled = true;
251
  if (note) note.textContent = '';
@@ -255,11 +250,11 @@ def render_quiz_html(records: List[dict]) -> str:
255
  """
256
  return html
257
 
258
- # ------------------ بناء الامتحان ------------------
259
  def build_quiz(text_area, file_path, n, model_id, zoom):
260
  text_area = (text_area or "").strip()
261
  if not text_area and not file_path:
262
- return "", "🛈 أدخل نصًا أو ارفع ملفًا."
263
  if text_area:
264
  raw = text_area
265
  else:
@@ -267,9 +262,9 @@ def build_quiz(text_area, file_path, n, model_id, zoom):
267
  cleaned = postprocess(raw)
268
  items = make_mcqs(cleaned, n=int(n))
269
  recs = to_records(items)
270
- return render_quiz_html(recs), f"تم توليد {len(recs)} سؤالًا. لكل سؤال زر تحقّق مستقل."
271
 
272
- # ------------------ الثيم ------------------
273
  CSS = """
274
  :root{
275
  --bg:#0e0e11; --panel:#15161a; --card:#1a1b20; --muted:#a7b0be;
@@ -278,17 +273,16 @@ CSS = """
278
  body{direction:rtl; font-family:system-ui,'Cairo','IBM Plex Arabic',sans-serif; background:var(--bg);}
279
  .gradio-container{max-width:980px;margin:0 auto;padding:12px 12px 40px;}
280
  h2.top{color:#eaeaf2;margin:6px 0 16px}
281
- .panel{background:var(--panel);border:1px solid var(--border);border-radius:14px;padding:14px;box-shadow:0 16px 38px rgba(0,0,0,.35)}
 
282
  .small{opacity:.9;color:#d9dee8}
 
283
 
284
  .button-primary>button{background:linear-gradient(180deg,var(--accent),var(--accent2));border:none;color:#0b0d10;font-weight:800}
285
  .button-primary>button:hover{filter:brightness(.95)}
286
- .upload-like{border:2px dashed #3b3f52;background:#121318;border-radius:12px;padding:10px;color:#cfd5e3}
287
-
288
  textarea{min-height:120px}
289
 
290
- /* الامتحان */
291
- .hint{color:#cfd5e3;background:#0f1116;border:1px solid #2a2d3a;border-radius:10px;padding:10px;margin:10px 0}
292
  .q-card{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:14px;margin:12px 0}
293
  .q-header{display:flex;gap:10px;align-items:center;justify-content:space-between;margin-bottom:6px}
294
  .q-title{color:#eaeaf2;font-weight:800}
@@ -302,32 +296,31 @@ textarea{min-height:120px}
302
  .opt input{accent-color:var(--accent2)}
303
  .opt-letter{display:inline-flex;width:28px;height:28px;border-radius:8px;background:#0f1116;border:1px solid #2a2d3a;align-items:center;justify-content:center;font-weight:800;color:#dfe6f7}
304
  .opt-text{color:#eaeaf2}
305
-
306
- /* التلوين بعد التحقق */
307
  .opt.ok{background:#0f2f22;border-color:#1b6a52}
308
  .opt.err{background:#3a0d14;border-color:#6a1e2b}
309
 
310
  .q-actions{display:flex;gap:10px;align-items:center;margin-top:10px}
311
  .q-actions .q-submit{
312
- background:linear-gradient(180deg,var(--accent),var(--accent2));
313
- border:none;color:#0b0d10;font-weight:800;border-radius:10px;padding:8px 14px;cursor:pointer;
314
  }
315
  .q-actions .q-submit:disabled{opacity:.5;cursor:not-allowed}
316
  .q-note{color:#ffd1d6}
317
  .q-note.warn{color:#ffd1d6}
318
  """
319
 
320
- # ------------------ واجهة Gradio ------------------
321
  with gr.Blocks(title="Question Generator", css=CSS) as demo:
322
  gr.Markdown("<h2 class='top'>Question Generator</h2>")
323
 
324
- with gr.Group(elem_classes=["panel"]):
325
- gr.Markdown("**أدخل نصًا أو ارفع ملفًا، حدّد عدد الأسئلة، ثم اضغط توليد.**", elem_classes=["small"])
326
- text_area = gr.Textbox(lines=6, placeholder="ألصق هنا مقطع نصي...", label="أدخل نصًا")
327
- num_q = gr.Slider(4, 20, value=DEFAULT_NUM_QUESTIONS, step=1, label="عدد الأسئلة")
328
- file_comp = gr.File(label="أو اختر ملف PDF/TXT", file_count="single",
 
329
  file_types=[".pdf",".txt"], type="filepath", elem_classes=["upload-like"])
330
- with gr.Accordion("خيارات متقدمة (PDF مصوّر)", open=False):
 
331
  trocr_model = gr.Dropdown(
332
  choices=[
333
  "microsoft/trocr-base-printed",
@@ -338,15 +331,19 @@ with gr.Blocks(title="Question Generator", css=CSS) as demo:
338
  value=DEFAULT_TROCR_MODEL, label="نموذج TrOCR"
339
  )
340
  trocr_zoom = gr.Slider(2.0, 3.5, value=DEFAULT_TROCR_ZOOM, step=0.1, label="Zoom OCR")
341
- btn_build = gr.Button("توليد الأسئلة", elem_classes=["button-primary"])
342
- toast = gr.Markdown("", elem_classes=["small"])
343
 
344
- quiz_html = gr.HTML("") # أسئلة + زر تحقق لكل سؤال
 
 
 
345
 
 
346
  btn_build.click(
347
  build_quiz,
348
  inputs=[text_area, file_comp, num_q, trocr_model, trocr_zoom],
349
- outputs=[quiz_html, toast]
350
  )
351
 
352
  if __name__ == "__main__":
 
1
  # -*- coding: utf-8 -*-
2
+ # واجهة صفحتين: إدخال بسيط + صفحة أسئلة مع Submit لكل سؤال
3
 
4
  import os, json, uuid, random, unicodedata
5
  from dataclasses import dataclass
 
176
  recs.append({"id":it.id,"question":it.question.strip(),"options":opts})
177
  return recs
178
 
179
+ # ------------------ HTML صفحة الأسئلة (Submit لكل سؤال) ------------------
180
  def render_quiz_html(records: List[dict]) -> str:
181
  parts=[]
182
  for i, rec in enumerate(records, start=1):
 
202
  <div class="q-text">{qtxt}</div>
203
  <div class="opts">{''.join(opts_html)}</div>
204
  <div class="q-actions">
205
+ <button class="q-submit">Submit</button>
206
  <span class="q-note" id="n_{qid}"></span>
207
  </div>
208
  </div>
209
  """)
210
  html = f"""
211
  <div id="quiz" class="quiz-wrap">
 
212
  {''.join(parts)}
213
  </div>
214
  <script>
 
215
  document.querySelectorAll('.q-card .q-submit').forEach(btn => {{
216
  btn.addEventListener('click', (e) => {{
217
  const card = e.target.closest('.q-card');
 
222
  const chosenInput = card.querySelector('input[type="radio"]:checked');
223
 
224
  if (!chosenInput) {{
225
+ if (note) {{ note.textContent = 'اختر إجابة أولاً'; note.className='q-note warn'; }}
 
 
 
226
  return;
227
  }}
228
 
229
+ // تنظيف ألوان سابقة
230
  card.querySelectorAll('.opt').forEach(l => l.classList.remove('ok','err'));
231
 
232
  const chosen = chosenInput.value;
 
234
 
235
  if (chosen === correct) {{
236
  chosenLabel.classList.add('ok');
237
+ if (badge) {{ badge.hidden=false; badge.className='q-badge ok'; badge.textContent='Correct!'; }}
238
  }} else {{
239
  chosenLabel.classList.add('err');
240
+ if (badge) {{ badge.hidden=false; badge.className='q-badge err'; badge.textContent='Incorrect.'; }}
241
  }}
242
 
243
+ // قفل هذا السؤال فقط
244
  card.querySelectorAll('input[type="radio"]').forEach(i => i.disabled = true);
245
  e.target.disabled = true;
246
  if (note) note.textContent = '';
 
250
  """
251
  return html
252
 
253
+ # ------------------ بناء الامتحان (من الصفحة الأولى للثانية) ------------------
254
  def build_quiz(text_area, file_path, n, model_id, zoom):
255
  text_area = (text_area or "").strip()
256
  if not text_area and not file_path:
257
+ return "", gr.update(visible=True), gr.update(visible=False), "🛈 الصق نصًا أو ارفع ملفًا أولًا."
258
  if text_area:
259
  raw = text_area
260
  else:
 
262
  cleaned = postprocess(raw)
263
  items = make_mcqs(cleaned, n=int(n))
264
  recs = to_records(items)
265
+ return render_quiz_html(recs), gr.update(visible=False), gr.update(visible=True), ""
266
 
267
+ # ============================== الثيم ==============================
268
  CSS = """
269
  :root{
270
  --bg:#0e0e11; --panel:#15161a; --card:#1a1b20; --muted:#a7b0be;
 
273
  body{direction:rtl; font-family:system-ui,'Cairo','IBM Plex Arabic',sans-serif; background:var(--bg);}
274
  .gradio-container{max-width:980px;margin:0 auto;padding:12px 12px 40px;}
275
  h2.top{color:#eaeaf2;margin:6px 0 16px}
276
+
277
+ .panel{background:var(--panel);border:1px solid var(--border);border-radius:14px;padding:16px;box-shadow:0 16px 38px rgba(0,0,0,.35)}
278
  .small{opacity:.9;color:#d9dee8}
279
+ .upload-like{border:2px dashed #3b3f52;background:#121318;border-radius:12px;padding:10px;color:#cfd5e3}
280
 
281
  .button-primary>button{background:linear-gradient(180deg,var(--accent),var(--accent2));border:none;color:#0b0d10;font-weight:800}
282
  .button-primary>button:hover{filter:brightness(.95)}
 
 
283
  textarea{min-height:120px}
284
 
285
+ /* صفحة الأسئلة */
 
286
  .q-card{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:14px;margin:12px 0}
287
  .q-header{display:flex;gap:10px;align-items:center;justify-content:space-between;margin-bottom:6px}
288
  .q-title{color:#eaeaf2;font-weight:800}
 
296
  .opt input{accent-color:var(--accent2)}
297
  .opt-letter{display:inline-flex;width:28px;height:28px;border-radius:8px;background:#0f1116;border:1px solid #2a2d3a;align-items:center;justify-content:center;font-weight:800;color:#dfe6f7}
298
  .opt-text{color:#eaeaf2}
 
 
299
  .opt.ok{background:#0f2f22;border-color:#1b6a52}
300
  .opt.err{background:#3a0d14;border-color:#6a1e2b}
301
 
302
  .q-actions{display:flex;gap:10px;align-items:center;margin-top:10px}
303
  .q-actions .q-submit{
304
+ background:#2dd4bf;border:none;color:#0b0d10;font-weight:800;border-radius:10px;padding:8px 14px;cursor:pointer;
 
305
  }
306
  .q-actions .q-submit:disabled{opacity:.5;cursor:not-allowed}
307
  .q-note{color:#ffd1d6}
308
  .q-note.warn{color:#ffd1d6}
309
  """
310
 
311
+ # ============================== الواجهة ==============================
312
  with gr.Blocks(title="Question Generator", css=CSS) as demo:
313
  gr.Markdown("<h2 class='top'>Question Generator</h2>")
314
 
315
+ # الصفحة 1: إدخال بسيط (خياران فقط + زر واحد)
316
+ page1 = gr.Group(visible=True, elem_classes=["panel"])
317
+ with page1:
318
+ gr.Markdown("اختر **أحد** الخيارين فقط ثم اضغط الزر.", elem_classes=["small"])
319
+ text_area = gr.Textbox(lines=6, placeholder="ألصق نصك هنا...", label="لصق نص")
320
+ file_comp = gr.File(label="أو ارفع ملف (PDF / TXT)", file_count="single",
321
  file_types=[".pdf",".txt"], type="filepath", elem_classes=["upload-like"])
322
+ num_q = gr.Slider(4, 20, value=DEFAULT_NUM_QUESTIONS, step=1, label="عدد الأسئلة")
323
+ with gr.Accordion("خيارات PDF المصوّر (اختياري)", open=False):
324
  trocr_model = gr.Dropdown(
325
  choices=[
326
  "microsoft/trocr-base-printed",
 
331
  value=DEFAULT_TROCR_MODEL, label="نموذج TrOCR"
332
  )
333
  trocr_zoom = gr.Slider(2.0, 3.5, value=DEFAULT_TROCR_ZOOM, step=0.1, label="Zoom OCR")
334
+ btn_build = gr.Button("generate quistion", elem_classes=["button-primary"])
335
+ msg = gr.Markdown("", elem_classes=["small"])
336
 
337
+ # الصفحة 2: الأسئلة
338
+ page2 = gr.Group(visible=False)
339
+ with page2:
340
+ quiz_html = gr.HTML("")
341
 
342
+ # حدث الانتقال
343
  btn_build.click(
344
  build_quiz,
345
  inputs=[text_area, file_comp, num_q, trocr_model, trocr_zoom],
346
+ outputs=[quiz_html, page1, page2, msg]
347
  )
348
 
349
  if __name__ == "__main__":