Ryadg commited on
Commit
eb2f556
Β·
1 Parent(s): 5ff15a8

feat: FLUX image generation moved to End Session modal as session visual summary

Browse files
Files changed (2) hide show
  1. app.py +53 -37
  2. ui/index.html +19 -10
app.py CHANGED
@@ -160,7 +160,9 @@ body { background: #0A0F1E !important; margin: 0 !important; }
160
  #hidden-chunk-output,
161
  #hidden-mcq-trigger,
162
  #hidden-mcq-output,
163
- #hidden-language-box {
 
 
164
  position: fixed !important;
165
  top: -9999px !important;
166
  left: -9999px !important;
@@ -346,16 +348,13 @@ def submit_answer(question, chunk, student_answer, correct, total, language):
346
 
347
 
348
  @spaces.GPU(duration=120)
349
- def generate_image_fn(question, feedback):
350
- if not question or not feedback:
351
- return None
352
- low = feedback.strip().lower()
353
- if low.startswith("erreur") or low.startswith("⚠"):
354
  return None
355
  try:
356
- concept = question.strip().replace("\n", " ")[:200]
357
- print(f"[generate_image_fn] generating illustration for: {concept[:80]!r}")
358
- return generate_concept_image(concept)
359
  except Exception as e:
360
  import traceback; traceback.print_exc()
361
  print(f"[generate_image_fn] failed: {e}")
@@ -378,7 +377,7 @@ BRIDGE_JS = """() => {
378
  mode: 'open',
379
  mcqData: null,
380
  waitingMCQ: false, prevMCQ: '',
381
- lastConceptImg: '',
382
  };
383
  const $ = id => document.getElementById(id);
384
 
@@ -430,7 +429,9 @@ BRIDGE_JS = """() => {
430
  return true;
431
  }
432
  function clickGradioBtn(sel) {
433
- const btn = document.querySelector(sel + ' button');
 
 
434
  if (btn) { btn.click(); return true; } return false;
435
  }
436
 
@@ -482,19 +483,39 @@ BRIDGE_JS = """() => {
482
  return verdict;
483
  }
484
 
485
- // ── Concept image ──────────────────────────────────────────────────────────
486
- function hideConceptImage() {
487
- const wrap = $('concept-image-display');
488
- if (wrap) { wrap.classList.add('hidden'); wrap.classList.remove('fade-in-up'); }
489
  }
490
 
491
- function showConceptImage(src) {
492
- const wrap = $('concept-image-display'), img = $('concept-image');
493
- if (!wrap || !img) return;
494
- img.src = src;
495
- wrap.classList.remove('hidden');
496
- void wrap.offsetWidth; // restart fade-in animation
497
- wrap.classList.add('fade-in-up');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
498
  }
499
 
500
  // ── Question display ───────────────────────────────────────────────────────
@@ -533,7 +554,6 @@ BRIDGE_JS = """() => {
533
  $('empty-state')?.classList.remove('hidden');
534
  $('question-area')?.classList.add('hidden');
535
  $('feedback-card')?.classList.add('hidden');
536
- hideConceptImage();
537
  }
538
 
539
  // ── MCQ display ────────────────────────────────────────────────────────────
@@ -614,7 +634,6 @@ BRIDGE_JS = """() => {
614
  if (es) es.classList.add('hidden');
615
  if (qa) qa.classList.remove('hidden');
616
  if (eb) eb.classList.remove('hidden');
617
- hideConceptImage();
618
  if (S.mode === 'mcq') {
619
  S.waitingMCQ = true;
620
  S.prevMCQ = document.querySelector('#hidden-mcq-output textarea')?.value || '';
@@ -662,6 +681,7 @@ BRIDGE_JS = """() => {
662
  return '<div class="modal-history-item"><span class="hist-badge '+vc+'">'+(vc==='correct'?'βœ“':vc==='partial'?'~':'βœ—')+'</span><span class="hist-q">'+h.question+'</span></div>';
663
  }).join('');
664
  }
 
665
  modal.classList.remove('hidden');
666
  }
667
 
@@ -685,7 +705,8 @@ BRIDGE_JS = """() => {
685
  if(ai){ai.value='';} if(cc) cc.textContent='0 chars'; if(sb) sb.disabled=true;
686
  if(fc) fc.classList.add('hidden'); if(es) es.classList.remove('hidden');
687
  if(qa) qa.classList.add('hidden'); if(eb) eb.classList.add('hidden');
688
- hideConceptImage();
 
689
  if(qc) qc.scrollIntoView({behavior:'smooth',block:'start'});
690
  }],
691
  ];
@@ -698,7 +719,7 @@ BRIDGE_JS = """() => {
698
  if (el && !el._pp) { el._pp = true; el.addEventListener('click', fn); }
699
  }
700
  const modal = $('modal');
701
- if (modal && !modal._pp) { modal._pp = true; modal.addEventListener('click', e => { if(e.target===modal) modal.classList.add('hidden'); }); }
702
  const ai = $('answer-input'), sb = $('submit-btn'), cc = $('char-count');
703
  if (ai && !ai._pp) {
704
  ai._pp = true;
@@ -792,15 +813,6 @@ BRIDGE_JS = """() => {
792
  }
793
  }
794
 
795
- // Concept image: Gradio writes the generated image into the hidden
796
- // gr.Image β€” surface it in the custom UI when a new src appears.
797
- const conceptEl = document.querySelector('#hidden-concept-image img');
798
- if (conceptEl && conceptEl.src && conceptEl.src !== S.lastConceptImg) {
799
- S.lastConceptImg = conceptEl.src;
800
- // Only show if feedback is still on screen (user hasn't moved on)
801
- const fc = $('feedback-card');
802
- if (fc && !fc.classList.contains('hidden')) showConceptImage(conceptEl.src);
803
- }
804
  }, 300);
805
  }"""
806
 
@@ -838,6 +850,8 @@ with gr.Blocks(title="PaperProf", css=CSS) as demo:
838
  mcq_trigger = gr.Textbox(value="0", label="mcqtrig", elem_id="hidden-mcq-trigger")
839
  mcq_output = gr.Textbox(value="", label="mcqout", elem_id="hidden-mcq-output")
840
  language_box = gr.Textbox(value="English", label="lang", elem_id="hidden-language-box")
 
 
841
 
842
  load_btn.click(
843
  load_pdf,
@@ -855,9 +869,11 @@ with gr.Blocks(title="PaperProf", css=CSS) as demo:
855
  submit_answer,
856
  inputs=[question_box, chunk_state, answer_box, correct_state, total_state, language_box],
857
  outputs=[feedback_box, correct_state, total_state, score_box],
858
- ).then(
 
 
859
  generate_image_fn,
860
- inputs=[question_box, feedback_box],
861
  outputs=[concept_image],
862
  )
863
 
 
160
  #hidden-chunk-output,
161
  #hidden-mcq-trigger,
162
  #hidden-mcq-output,
163
+ #hidden-language-box,
164
+ #hidden-session-topics,
165
+ #hidden-generate-session-image-btn {
166
  position: fixed !important;
167
  top: -9999px !important;
168
  left: -9999px !important;
 
348
 
349
 
350
  @spaces.GPU(duration=120)
351
+ def generate_image_fn(topics):
352
+ if not topics or not topics.strip():
 
 
 
353
  return None
354
  try:
355
+ prompt = f"Visual summary of a study session covering: {topics.strip()}"
356
+ print(f"[generate_image_fn] generating session illustration for: {prompt[:120]!r}")
357
+ return generate_concept_image(prompt)
358
  except Exception as e:
359
  import traceback; traceback.print_exc()
360
  print(f"[generate_image_fn] failed: {e}")
 
377
  mode: 'open',
378
  mcqData: null,
379
  waitingMCQ: false, prevMCQ: '',
380
+ imgPoll: null,
381
  };
382
  const $ = id => document.getElementById(id);
383
 
 
429
  return true;
430
  }
431
  function clickGradioBtn(sel) {
432
+ const el = document.querySelector(sel);
433
+ if (!el) return false;
434
+ const btn = el.tagName === 'BUTTON' ? el : el.querySelector('button');
435
  if (btn) { btn.click(); return true; } return false;
436
  }
437
 
 
483
  return verdict;
484
  }
485
 
486
+ // ── Session image (shown in the End Session modal) ────────────────────────
487
+ function stopSessionImagePoll() {
488
+ if (S.imgPoll) { clearInterval(S.imgPoll); S.imgPoll = null; }
489
+ $('modal-image-loading')?.classList.add('hidden');
490
  }
491
 
492
+ function startSessionImage() {
493
+ const sec = $('modal-session-image'), loading = $('modal-image-loading'), img = $('session-image');
494
+ if (!sec || !loading || !img) return;
495
+ stopSessionImagePoll();
496
+ sec.classList.add('hidden'); sec.classList.remove('fade-in-up');
497
+ if (S.history.length === 0) return;
498
+ const topics = S.history.slice(0, 3).map(h => h.question).join(', ');
499
+ if (!setGradioTA('#hidden-session-topics textarea', topics)) return;
500
+ const baseline = document.querySelector('#hidden-concept-image img')?.src || '';
501
+ loading.classList.remove('hidden');
502
+ // Small delay so Gradio registers the topics input before the click
503
+ setTimeout(() => {
504
+ if (!clickGradioBtn('#hidden-generate-session-image-btn')) { stopSessionImagePoll(); return; }
505
+ const started = Date.now();
506
+ S.imgPoll = setInterval(() => {
507
+ const el = document.querySelector('#hidden-concept-image img');
508
+ if (el && el.src && el.src !== baseline) {
509
+ stopSessionImagePoll();
510
+ img.src = el.src;
511
+ sec.classList.remove('hidden');
512
+ void sec.offsetWidth; // restart fade-in animation
513
+ sec.classList.add('fade-in-up');
514
+ } else if (Date.now() - started > 120000) {
515
+ stopSessionImagePoll(); // give up quietly after 2 min
516
+ }
517
+ }, 500);
518
+ }, 200);
519
  }
520
 
521
  // ── Question display ───────────────────────────────────────────────────────
 
554
  $('empty-state')?.classList.remove('hidden');
555
  $('question-area')?.classList.add('hidden');
556
  $('feedback-card')?.classList.add('hidden');
 
557
  }
558
 
559
  // ── MCQ display ────────────────────────────────────────────────────────────
 
634
  if (es) es.classList.add('hidden');
635
  if (qa) qa.classList.remove('hidden');
636
  if (eb) eb.classList.remove('hidden');
 
637
  if (S.mode === 'mcq') {
638
  S.waitingMCQ = true;
639
  S.prevMCQ = document.querySelector('#hidden-mcq-output textarea')?.value || '';
 
681
  return '<div class="modal-history-item"><span class="hist-badge '+vc+'">'+(vc==='correct'?'βœ“':vc==='partial'?'~':'βœ—')+'</span><span class="hist-q">'+h.question+'</span></div>';
682
  }).join('');
683
  }
684
+ startSessionImage();
685
  modal.classList.remove('hidden');
686
  }
687
 
 
705
  if(ai){ai.value='';} if(cc) cc.textContent='0 chars'; if(sb) sb.disabled=true;
706
  if(fc) fc.classList.add('hidden'); if(es) es.classList.remove('hidden');
707
  if(qa) qa.classList.add('hidden'); if(eb) eb.classList.add('hidden');
708
+ stopSessionImagePoll();
709
+ $('modal-session-image')?.classList.add('hidden');
710
  if(qc) qc.scrollIntoView({behavior:'smooth',block:'start'});
711
  }],
712
  ];
 
719
  if (el && !el._pp) { el._pp = true; el.addEventListener('click', fn); }
720
  }
721
  const modal = $('modal');
722
+ if (modal && !modal._pp) { modal._pp = true; modal.addEventListener('click', e => { if(e.target===modal) { modal.classList.add('hidden'); stopSessionImagePoll(); } }); }
723
  const ai = $('answer-input'), sb = $('submit-btn'), cc = $('char-count');
724
  if (ai && !ai._pp) {
725
  ai._pp = true;
 
813
  }
814
  }
815
 
 
 
 
 
 
 
 
 
 
816
  }, 300);
817
  }"""
818
 
 
850
  mcq_trigger = gr.Textbox(value="0", label="mcqtrig", elem_id="hidden-mcq-trigger")
851
  mcq_output = gr.Textbox(value="", label="mcqout", elem_id="hidden-mcq-output")
852
  language_box = gr.Textbox(value="English", label="lang", elem_id="hidden-language-box")
853
+ session_topics = gr.Textbox(value="", label="topics", elem_id="hidden-session-topics")
854
+ session_img_btn = gr.Button("gen session image", elem_id="hidden-generate-session-image-btn")
855
 
856
  load_btn.click(
857
  load_pdf,
 
869
  submit_answer,
870
  inputs=[question_box, chunk_state, answer_box, correct_state, total_state, language_box],
871
  outputs=[feedback_box, correct_state, total_state, score_box],
872
+ )
873
+
874
+ session_img_btn.click(
875
  generate_image_fn,
876
+ inputs=[session_topics],
877
  outputs=[concept_image],
878
  )
879
 
ui/index.html CHANGED
@@ -281,15 +281,22 @@
281
  .feedback-body .section-content { color: var(--text); }
282
  .feedback-raw { white-space: pre-wrap; }
283
 
284
- /* ── Concept image card ───────────────────────────────────────────────── */
285
- #concept-image-display { text-align: center; }
 
 
 
 
 
 
 
286
  .concept-image-title {
287
  font-size: .72rem; font-weight: 700; letter-spacing: .1em;
288
  text-transform: uppercase; color: var(--cyan-l);
289
  margin-bottom: 14px; text-align: left;
290
  }
291
- #concept-image {
292
- width: 100%; max-width: 512px; display: inline-block;
293
  border-radius: var(--r-sm); border: 1px solid var(--border);
294
  box-shadow: 0 8px 24px rgba(0,0,0,.4);
295
  }
@@ -551,12 +558,6 @@
551
  <div class="mcq-explanations hidden" id="mcq-explanations"></div>
552
  </div>
553
 
554
- <!-- ── Concept image card ────────────────────────────────────────────── -->
555
- <div class="card hidden" id="concept-image-display">
556
- <div class="concept-image-title">Visual Summary</div>
557
- <img id="concept-image" alt="Visual summary of this concept" />
558
- <p class="concept-image-caption">Visual summary of this concept</p>
559
- </div>
560
  </div>
561
 
562
  <!-- ── Session modal ───────────────────────────────────────────────────── -->
@@ -568,6 +569,14 @@
568
  <div class="modal-score-label" id="modal-score-label">questions answered</div>
569
  <p class="modal-message" id="modal-message"></p>
570
  <div class="modal-history hidden" id="modal-history"></div>
 
 
 
 
 
 
 
 
571
  <button class="btn-primary" id="modal-close" style="width:100%;justify-content:center">
572
  Study Again
573
  </button>
 
281
  .feedback-body .section-content { color: var(--text); }
282
  .feedback-raw { white-space: pre-wrap; }
283
 
284
+ /* ── Session image (modal) ────────────────────────────────────────────── */
285
+ #modal-session-image {
286
+ margin-bottom: 24px; padding: 18px;
287
+ background: rgba(255,255,255,.04);
288
+ border: 1px solid var(--border);
289
+ border-radius: var(--r-sm);
290
+ backdrop-filter: blur(12px);
291
+ text-align: center;
292
+ }
293
  .concept-image-title {
294
  font-size: .72rem; font-weight: 700; letter-spacing: .1em;
295
  text-transform: uppercase; color: var(--cyan-l);
296
  margin-bottom: 14px; text-align: left;
297
  }
298
+ #session-image {
299
+ width: 100%; max-width: 320px; display: inline-block;
300
  border-radius: var(--r-sm); border: 1px solid var(--border);
301
  box-shadow: 0 8px 24px rgba(0,0,0,.4);
302
  }
 
558
  <div class="mcq-explanations hidden" id="mcq-explanations"></div>
559
  </div>
560
 
 
 
 
 
 
 
561
  </div>
562
 
563
  <!-- ── Session modal ───────────────────────────────────────────────────── -->
 
569
  <div class="modal-score-label" id="modal-score-label">questions answered</div>
570
  <p class="modal-message" id="modal-message"></p>
571
  <div class="modal-history hidden" id="modal-history"></div>
572
+ <div class="loading-msg hidden" id="modal-image-loading">
573
+ <span class="spinner"></span> Generating your visual summary…
574
+ </div>
575
+ <div class="hidden" id="modal-session-image">
576
+ <div class="concept-image-title">Visual Summary</div>
577
+ <img id="session-image" alt="Visual summary of your session" />
578
+ <p class="concept-image-caption">Visual summary of your session</p>
579
+ </div>
580
  <button class="btn-primary" id="modal-close" style="width:100%;justify-content:center">
581
  Study Again
582
  </button>