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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +67 -63
app.py CHANGED
@@ -1,6 +1,5 @@
1
  # -*- coding: utf-8 -*-
2
- # واجهة صفحتين: إدخال بسيط + صفحة أسئلة مع Submit لكل سؤال
3
-
4
  import os, json, uuid, random, unicodedata
5
  from dataclasses import dataclass
6
  from pathlib import Path
@@ -17,7 +16,7 @@ import gradio as gr
17
  random.seed(42)
18
  DEFAULT_NUM_QUESTIONS = 6
19
  DEFAULT_TROCR_MODEL = "microsoft/trocr-base-printed"
20
- DEFAULT_TROCR_ZOOM = 2.6
21
 
22
  # ------------------ OCR (تحميل كسول) ------------------
23
  _OCR = {}
@@ -34,8 +33,10 @@ def extract_text_with_pypdf(path: str) -> str:
34
  reader = PdfReader(path)
35
  out = []
36
  for p in reader.pages:
37
- try: t = p.extract_text() or ""
38
- except Exception: t = ""
 
 
39
  out.append(t)
40
  return "\n".join(out).strip()
41
 
@@ -68,7 +69,8 @@ def is_good(t: str, min_chars=250, min_alpha=0.15) -> bool:
68
  def file_to_text(path: str, model_id=DEFAULT_TROCR_MODEL, zoom=DEFAULT_TROCR_ZOOM) -> Tuple[str,str]:
69
  ext = Path(path).suffix.lower()
70
  if ext == ".txt":
71
- with open(path,"r",encoding="utf-8",errors="ignore") as f: return f.read(), "plain text"
 
72
  raw = extract_text_with_pypdf(path)
73
  if is_good(raw): return raw, "embedded (pypdf)"
74
  return extract_text_with_ocr(path, model_id, zoom), "OCR (TrOCR)"
@@ -176,11 +178,11 @@ 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 صفحة الأسئلة (Submit لكل سؤال) ------------------
180
  def render_quiz_html(records: List[dict]) -> str:
181
  parts=[]
182
  for i, rec in enumerate(records, start=1):
183
- qid = rec["id"]
184
  qtxt = rec["question"]
185
  cor = next((o["id"] for o in rec["options"] if o["is_correct"]), "")
186
  opts_html=[]
@@ -207,50 +209,9 @@ def render_quiz_html(records: List[dict]) -> str:
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');
218
- const qid = card.getAttribute('data-qid');
219
- const correct = card.getAttribute('data-correct');
220
- const note = document.getElementById('n_'+qid);
221
- const badge = document.getElementById('b_'+qid);
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;
233
- const chosenLabel = chosenInput.closest('.opt');
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 = '';
247
- }});
248
- }});
249
- </script>
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:
@@ -260,11 +221,11 @@ def build_quiz(text_area, file_path, n, model_id, zoom):
260
  else:
261
  raw, _ = file_to_text(file_path, model_id=model_id, zoom=float(zoom))
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;
@@ -277,7 +238,6 @@ h2.top{color:#eaeaf2;margin:6px 0 16px}
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}
@@ -308,14 +268,55 @@ textarea{min-height:120px}
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"])
@@ -332,18 +333,21 @@ with gr.Blocks(title="Question Generator", css=CSS) as demo:
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__":
 
1
  # -*- coding: utf-8 -*-
2
+ # صفحتان: إدخال بسيط + صفحة أسئلة مع Submit لكل سؤال
 
3
  import os, json, uuid, random, unicodedata
4
  from dataclasses import dataclass
5
  from pathlib import Path
 
16
  random.seed(42)
17
  DEFAULT_NUM_QUESTIONS = 6
18
  DEFAULT_TROCR_MODEL = "microsoft/trocr-base-printed"
19
+ DEFAULT_TROCR_ZOOM = 2.6
20
 
21
  # ------------------ OCR (تحميل كسول) ------------------
22
  _OCR = {}
 
33
  reader = PdfReader(path)
34
  out = []
35
  for p in reader.pages:
36
+ try:
37
+ t = p.extract_text() or ""
38
+ except Exception:
39
+ t = ""
40
  out.append(t)
41
  return "\n".join(out).strip()
42
 
 
69
  def file_to_text(path: str, model_id=DEFAULT_TROCR_MODEL, zoom=DEFAULT_TROCR_ZOOM) -> Tuple[str,str]:
70
  ext = Path(path).suffix.lower()
71
  if ext == ".txt":
72
+ with open(path,"r",encoding="utf-8",errors="ignore") as f:
73
+ 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)"
 
178
  recs.append({"id":it.id,"question":it.question.strip(),"options":opts})
179
  return recs
180
 
181
+ # ------------------ صفحة الأسئلة (HTML فقط) ------------------
182
  def render_quiz_html(records: List[dict]) -> str:
183
  parts=[]
184
  for i, rec in enumerate(records, start=1):
185
+ qid = rec["id"]
186
  qtxt = rec["question"]
187
  cor = next((o["id"] for o in rec["options"] if o["is_correct"]), "")
188
  opts_html=[]
 
209
  </div>
210
  </div>
211
  """)
212
+ return f"""<div id="quiz" class="quiz-wrap">{''.join(parts)}</div>"""
213
+
214
+ # ------------------ توليد الامتحان وتبديل الصفحات ------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  def build_quiz(text_area, file_path, n, model_id, zoom):
216
  text_area = (text_area or "").strip()
217
  if not text_area and not file_path:
 
221
  else:
222
  raw, _ = file_to_text(file_path, model_id=model_id, zoom=float(zoom))
223
  cleaned = postprocess(raw)
224
+ items = make_mcqs(cleaned, n=int(n))
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;
 
238
  .panel{background:var(--panel);border:1px solid var(--border);border-radius:14px;padding:16px;box-shadow:0 16px 38px rgba(0,0,0,.35)}
239
  .small{opacity:.9;color:#d9dee8}
240
  .upload-like{border:2px dashed #3b3f52;background:#121318;border-radius:12px;padding:10px;color:#cfd5e3}
 
241
  .button-primary>button{background:linear-gradient(180deg,var(--accent),var(--accent2));border:none;color:#0b0d10;font-weight:800}
242
  .button-primary>button:hover{filter:brightness(.95)}
243
  textarea{min-height:120px}
 
268
  .q-note.warn{color:#ffd1d6}
269
  """
270
 
271
+ # ------------------ JS: ربط أزرار Submit بعد الرندر ------------------
272
+ ATTACH_LISTENERS_JS = """
273
+ () => {
274
+ const bind = (btn) => {
275
+ if (btn.dataset.bound === '1') return;
276
+ btn.dataset.bound = '1';
277
+ btn.addEventListener('click', (e) => {
278
+ const card = e.target.closest('.q-card');
279
+ const qid = card.getAttribute('data-qid');
280
+ const correct= card.getAttribute('data-correct');
281
+ const note = document.getElementById('n_'+qid);
282
+ const badge = document.getElementById('b_'+qid);
283
+ const chosen = card.querySelector('input[type="radio"]:checked');
284
+
285
+ if (!chosen) {
286
+ if (note){ note.textContent = 'اختر إجابة أولاً'; note.className='q-note warn'; }
287
+ return;
288
+ }
289
+ // نظّف ألوان سابقة
290
+ card.querySelectorAll('.opt').forEach(l => l.classList.remove('ok','err'));
291
+
292
+ const chosenLabel = chosen.closest('.opt');
293
+ if (chosen.value === correct) {
294
+ chosenLabel.classList.add('ok');
295
+ if (badge){ badge.hidden=false; badge.className='q-badge ok'; badge.textContent='Correct!'; }
296
+ } else {
297
+ chosenLabel.classList.add('err');
298
+ if (badge){ badge.hidden=false; badge.className='q-badge err'; badge.textContent='Incorrect.'; }
299
+ }
300
+ // أقفل السؤال
301
+ card.querySelectorAll('input[type="radio"]').forEach(i => i.disabled = true);
302
+ e.target.disabled = true;
303
+ if (note) note.textContent = '';
304
+ });
305
+ };
306
+
307
+ document.querySelectorAll('.q-card .q-submit').forEach(bind);
308
+ return [];
309
+ }
310
+ """
311
+
312
+ # ------------------ واجهة Gradio ------------------
313
  with gr.Blocks(title="Question Generator", css=CSS) as demo:
314
  gr.Markdown("<h2 class='top'>Question Generator</h2>")
315
 
316
+ # الصفحة 1
317
  page1 = gr.Group(visible=True, elem_classes=["panel"])
318
  with page1:
319
+ gr.Markdown("اختر **أحد** الخيارين ثم اضغط الزر.", elem_classes=["small"])
320
  text_area = gr.Textbox(lines=6, placeholder="ألصق نصك هنا...", label="لصق نص")
321
  file_comp = gr.File(label="أو ارفع ملف (PDF / TXT)", file_count="single",
322
  file_types=[".pdf",".txt"], type="filepath", elem_classes=["upload-like"])
 
333
  )
334
  trocr_zoom = gr.Slider(2.0, 3.5, value=DEFAULT_TROCR_ZOOM, step=0.1, label="Zoom OCR")
335
  btn_build = gr.Button("generate quistion", elem_classes=["button-primary"])
336
+ warn = gr.Markdown("", elem_classes=["small"])
337
 
338
+ # الصفحة 2
339
  page2 = gr.Group(visible=False)
340
  with page2:
341
  quiz_html = gr.HTML("")
342
 
343
+ # بناء الامتحان + إظهار الصفحة 2
344
  btn_build.click(
345
  build_quiz,
346
  inputs=[text_area, file_comp, num_q, trocr_model, trocr_zoom],
347
+ outputs=[quiz_html, page1, page2, warn]
348
+ ).then( # بعد عرض الـHTML اربط أزرار Submit
349
+ None, inputs=None, outputs=[],
350
+ js=ATTACH_LISTENERS_JS
351
  )
352
 
353
  if __name__ == "__main__":