feat: FLUX image generation moved to End Session modal as session visual summary
Browse files- app.py +53 -37
- 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(
|
| 350 |
-
if not
|
| 351 |
-
return None
|
| 352 |
-
low = feedback.strip().lower()
|
| 353 |
-
if low.startswith("erreur") or low.startswith("β "):
|
| 354 |
return None
|
| 355 |
try:
|
| 356 |
-
|
| 357 |
-
print(f"[generate_image_fn] generating illustration for: {
|
| 358 |
-
return generate_concept_image(
|
| 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 |
-
|
| 382 |
};
|
| 383 |
const $ = id => document.getElementById(id);
|
| 384 |
|
|
@@ -430,7 +429,9 @@ BRIDGE_JS = """() => {
|
|
| 430 |
return true;
|
| 431 |
}
|
| 432 |
function clickGradioBtn(sel) {
|
| 433 |
-
const
|
|
|
|
|
|
|
| 434 |
if (btn) { btn.click(); return true; } return false;
|
| 435 |
}
|
| 436 |
|
|
@@ -482,19 +483,39 @@ BRIDGE_JS = """() => {
|
|
| 482 |
return verdict;
|
| 483 |
}
|
| 484 |
|
| 485 |
-
// ββ
|
| 486 |
-
function
|
| 487 |
-
|
| 488 |
-
|
| 489 |
}
|
| 490 |
|
| 491 |
-
function
|
| 492 |
-
const
|
| 493 |
-
if (!
|
| 494 |
-
|
| 495 |
-
|
| 496 |
-
|
| 497 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
| 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 |
-
)
|
|
|
|
|
|
|
| 859 |
generate_image_fn,
|
| 860 |
-
inputs=[
|
| 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 |
-
/* ββ
|
| 285 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
#
|
| 292 |
-
width: 100%; max-width:
|
| 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>
|