fix: full quiz flow via BRIDGE_JS, English only, buttons DOM-visible
Browse files- app.py +242 -58
- ui/index.html +9 -40
app.py
CHANGED
|
@@ -302,65 +302,249 @@ def submit_answer(question, chunk, student_answer, correct, total, language):
|
|
| 302 |
# ---------------------------------------------------------------------------
|
| 303 |
|
| 304 |
BRIDGE_JS = """() => {
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 316 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 317 |
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
bar.textContent = text;
|
| 325 |
-
bar.style.display = 'block';
|
| 326 |
-
const isError = text.includes('\\u26a0') || text.toLowerCase().includes('erreur');
|
| 327 |
-
const isSuccess = text.includes('sections') || text.includes('extraites') || text.includes('extracted') || text.includes('\\ud83d\\udcda');
|
| 328 |
-
if (isError) {
|
| 329 |
-
bar.style.background = 'rgba(239,68,68,0.12)';
|
| 330 |
-
bar.style.border = '1px solid rgba(239,68,68,0.3)';
|
| 331 |
-
bar.style.color = '#FCA5A5';
|
| 332 |
-
} else if (isSuccess) {
|
| 333 |
-
bar.style.background = 'rgba(16,185,129,0.12)';
|
| 334 |
-
bar.style.border = '1px solid rgba(16,185,129,0.3)';
|
| 335 |
-
bar.style.color = '#6EE7B7';
|
| 336 |
-
if (!quizRevealed) {
|
| 337 |
-
quizRevealed = true;
|
| 338 |
-
const quizCard = document.getElementById('quiz-card');
|
| 339 |
-
if (quizCard) {
|
| 340 |
-
quizCard.classList.remove('hidden');
|
| 341 |
-
setTimeout(() => quizCard.scrollIntoView({ behavior: 'smooth', block: 'start' }), 400);
|
| 342 |
-
}
|
| 343 |
-
const uploadRow = document.getElementById('upload-row');
|
| 344 |
-
if (uploadRow) {
|
| 345 |
-
uploadRow.style.opacity = '0.5';
|
| 346 |
-
uploadRow.style.transform = 'scale(0.97)';
|
| 347 |
-
uploadRow.style.transition = 'all .4s ease';
|
| 348 |
-
uploadRow.style.pointerEvents = 'none';
|
| 349 |
-
}
|
| 350 |
-
}
|
| 351 |
-
} else {
|
| 352 |
-
bar.style.background = 'rgba(6,182,212,0.12)';
|
| 353 |
-
bar.style.border = '1px solid rgba(6,182,212,0.3)';
|
| 354 |
-
bar.style.color = '#67E8F9';
|
| 355 |
-
}
|
| 356 |
}
|
| 357 |
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
const
|
| 362 |
-
|
| 363 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 364 |
}"""
|
| 365 |
|
| 366 |
print("β
Starting Gradio app...")
|
|
@@ -385,10 +569,10 @@ with gr.Blocks(title="PaperProf", css=CSS) as demo:
|
|
| 385 |
|
| 386 |
with gr.Row(elem_id="hidden-row-question"):
|
| 387 |
question_box = gr.Textbox(label="β Question", interactive=False, lines=3, scale=3, elem_id="hidden-question-output")
|
| 388 |
-
new_q_btn = gr.Button("New Question", variant="secondary", scale=1,
|
| 389 |
|
| 390 |
-
answer_box = gr.Textbox(label="βοΈ Your Answer", lines=4, placeholder="Write your answer hereβ¦",
|
| 391 |
-
submit_btn = gr.Button("Submit Answer", variant="primary",
|
| 392 |
feedback_box = gr.Textbox(label="π¬ Feedback", interactive=False, lines=7, elem_id="hidden-feedback-output")
|
| 393 |
|
| 394 |
# Traduction UI quand on change la langue
|
|
|
|
| 302 |
# ---------------------------------------------------------------------------
|
| 303 |
|
| 304 |
BRIDGE_JS = """() => {
|
| 305 |
+
// ββ State ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 306 |
+
const S = {
|
| 307 |
+
correct: 0, total: 0, history: [],
|
| 308 |
+
currentQuestion: '',
|
| 309 |
+
quizRevealed: false,
|
| 310 |
+
lastStatus: '',
|
| 311 |
+
waitingQ: false, prevQuestion: '',
|
| 312 |
+
waitingFB: false, prevFeedback: '',
|
| 313 |
+
};
|
| 314 |
+
const $ = id => document.getElementById(id);
|
| 315 |
+
|
| 316 |
+
// ββ Status bar βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 317 |
+
function ensureStatusBar() {
|
| 318 |
+
if ($('gradio-status-bar')) return;
|
| 319 |
+
const anchor = $('quiz-card') || $('app');
|
| 320 |
+
if (!anchor) return;
|
| 321 |
+
const bar = document.createElement('div');
|
| 322 |
+
bar.id = 'gradio-status-bar';
|
| 323 |
+
bar.style.cssText = 'display:none;max-width:760px;margin:8px auto 0;padding:12px 20px;border-radius:10px;font-size:.9rem;font-weight:500;font-family:Inter,system-ui,sans-serif;transition:all .3s ease;';
|
| 324 |
+
anchor.parentNode.insertBefore(bar, anchor);
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
function applyStatus(text) {
|
| 328 |
+
if (text === S.lastStatus) return;
|
| 329 |
+
S.lastStatus = text;
|
| 330 |
+
ensureStatusBar();
|
| 331 |
+
const bar = $('gradio-status-bar');
|
| 332 |
+
if (!bar) return;
|
| 333 |
+
bar.textContent = text;
|
| 334 |
+
bar.style.display = 'block';
|
| 335 |
+
const isErr = text.includes('\\u26a0') || text.toLowerCase().includes('erreur');
|
| 336 |
+
const isOK = text.includes('sections') || text.includes('extracted') || text.includes('extraites');
|
| 337 |
+
if (isErr) {
|
| 338 |
+
bar.style.cssText += ';background:rgba(239,68,68,.12);border:1px solid rgba(239,68,68,.3);color:#FCA5A5';
|
| 339 |
+
} else if (isOK) {
|
| 340 |
+
bar.style.cssText += ';background:rgba(16,185,129,.12);border:1px solid rgba(16,185,129,.3);color:#6EE7B7';
|
| 341 |
+
if (!S.quizRevealed) { S.quizRevealed = true; revealQuiz(); }
|
| 342 |
+
} else {
|
| 343 |
+
bar.style.cssText += ';background:rgba(6,182,212,.12);border:1px solid rgba(6,182,212,.3);color:#67E8F9';
|
| 344 |
}
|
| 345 |
+
}
|
| 346 |
+
|
| 347 |
+
function revealQuiz() {
|
| 348 |
+
const qc = $('quiz-card');
|
| 349 |
+
if (qc) { qc.classList.remove('hidden'); setTimeout(() => qc.scrollIntoView({behavior:'smooth',block:'start'}), 400); }
|
| 350 |
+
const ur = document.querySelector('#upload-row');
|
| 351 |
+
if (ur) { ur.style.cssText += ';opacity:0;height:0;overflow:hidden;margin:0;padding:0;pointer-events:none;transition:all .4s ease'; }
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
// ββ Helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 355 |
+
function setGradioTA(sel, val) {
|
| 356 |
+
const el = document.querySelector(sel);
|
| 357 |
+
if (!el) return false;
|
| 358 |
+
Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype,'value').set.call(el, val);
|
| 359 |
+
el.dispatchEvent(new Event('input', {bubbles:true}));
|
| 360 |
+
el.dispatchEvent(new Event('change', {bubbles:true}));
|
| 361 |
+
return true;
|
| 362 |
+
}
|
| 363 |
+
function clickGradioBtn(sel) {
|
| 364 |
+
const btn = document.querySelector(sel + ' button');
|
| 365 |
+
if (btn) { btn.click(); return true; } return false;
|
| 366 |
+
}
|
| 367 |
+
|
| 368 |
+
// ββ Score ring βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 369 |
+
function updateScore() {
|
| 370 |
+
const arc = $('score-arc');
|
| 371 |
+
const label = $('score-label');
|
| 372 |
+
if (!arc || !label) return;
|
| 373 |
+
const n = S.correct, d = S.total;
|
| 374 |
+
label.textContent = d === 0 ? 'β' : n + '/' + d;
|
| 375 |
+
arc.style.strokeDashoffset = 138.2 * (1 - (d ? n/d : 0));
|
| 376 |
+
arc.style.stroke = d === 0 ? 'url(#scoreGrad)' : (n/d >= .7 ? '#10B981' : n/d >= .4 ? '#F59E0B' : '#EF4444');
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
// ββ Feedback rendering βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 380 |
+
function extractVerdict(t) {
|
| 381 |
+
const lo = t.toLowerCase(), lines = lo.split('\\n');
|
| 382 |
+
for (const l of lines) {
|
| 383 |
+
if (l.includes('verdict')) {
|
| 384 |
+
if (l.includes('partially')) return 'partial';
|
| 385 |
+
if (l.includes('incorrect')) return 'incorrect';
|
| 386 |
+
if (l.includes('correct')) return 'correct';
|
| 387 |
+
}
|
| 388 |
+
}
|
| 389 |
+
const h = lo.slice(0,200);
|
| 390 |
+
return h.includes('incorrect') ? 'incorrect' : h.includes('partially') ? 'partial' : h.includes('correct') ? 'correct' : 'unknown';
|
| 391 |
+
}
|
| 392 |
+
|
| 393 |
+
function showFeedback(text) {
|
| 394 |
+
const badge = $('verdict-badge'), fc = $('feedback-card'), body = $('feedback-body');
|
| 395 |
+
const verdict = extractVerdict(text);
|
| 396 |
+
if (badge) {
|
| 397 |
+
badge.className = 'verdict-badge ' + (verdict === 'unknown' ? '' : verdict);
|
| 398 |
+
badge.textContent = verdict === 'correct' ? 'β Correct' : verdict === 'partial' ? '~ Partially Correct' : verdict === 'incorrect' ? 'β Incorrect' : 'β Unknown';
|
| 399 |
+
}
|
| 400 |
+
if (body) {
|
| 401 |
+
const labels = ['','Verdict','What was good','What was missing','Model answer'];
|
| 402 |
+
const sections = [], re = /(\\d+)\\.\\s*([^:\\n]*)[:οΌ]?\\s*([\\s\\S]*?)(?=\\n\\d+\\.|$)/g;
|
| 403 |
+
let m;
|
| 404 |
+
while ((m = re.exec(text)) !== null) sections.push({num:+m[1], body:m[3].trim()});
|
| 405 |
+
body.innerHTML = sections.length >= 2
|
| 406 |
+
? sections.map(s => '<div class="section"><span class="section-num">'+(labels[s.num]||'Part '+s.num)+'</span><div class="section-content">'+s.body.replace(/\\n/g,'<br>')+'</div></div>').join('')
|
| 407 |
+
: '<div class="feedback-raw">'+text.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')+'</div>';
|
| 408 |
+
}
|
| 409 |
+
if (fc) { fc.classList.remove('hidden'); fc.classList.add('fade-in-up'); setTimeout(() => fc.scrollIntoView({behavior:'smooth',block:'nearest'}),100); }
|
| 410 |
+
return verdict;
|
| 411 |
+
}
|
| 412 |
+
|
| 413 |
+
// ββ Question display βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 414 |
+
function showQuestion(text) {
|
| 415 |
+
S.currentQuestion = text; S.waitingQ = false;
|
| 416 |
+
const es = $('empty-state'), qa = $('question-area'), qt = $('q-text'), ql = $('q-loading');
|
| 417 |
+
const eb = $('end-btn'), fc = $('feedback-card'), ai = $('answer-input'), cc = $('char-count');
|
| 418 |
+
const sb = $('submit-btn'), nq = $('new-q-btn');
|
| 419 |
+
if (es) es.classList.add('hidden');
|
| 420 |
+
if (qa) qa.classList.remove('hidden');
|
| 421 |
+
if (eb) eb.classList.remove('hidden');
|
| 422 |
+
if (ql) ql.classList.add('hidden');
|
| 423 |
+
if (qt) qt.textContent = text;
|
| 424 |
+
if (fc) fc.classList.add('hidden');
|
| 425 |
+
if (ai) { ai.value = ''; ai.disabled = false; }
|
| 426 |
+
if (cc) cc.textContent = '0 chars';
|
| 427 |
+
if (sb) sb.disabled = true;
|
| 428 |
+
if (nq) nq.disabled = false;
|
| 429 |
+
}
|
| 430 |
+
|
| 431 |
+
// ββ Trigger actions via hidden Gradio components βββββββββββββββββββββββββββ
|
| 432 |
+
function fetchQuestion() {
|
| 433 |
+
if (S.waitingQ) return;
|
| 434 |
+
S.waitingQ = true;
|
| 435 |
+
S.prevQuestion = document.querySelector('#hidden-question-output textarea')?.value || '';
|
| 436 |
+
const ql = $('q-loading'), qm = $('q-loading-msg'), nq = $('new-q-btn'), fc = $('feedback-card');
|
| 437 |
+
const es = $('empty-state'), qa = $('question-area'), eb = $('end-btn');
|
| 438 |
+
if (ql) ql.classList.remove('hidden');
|
| 439 |
+
if (qm) qm.textContent = S.total === 0 ? 'Generating question (model loading, ~30s first time)β¦' : 'Generating questionβ¦';
|
| 440 |
+
if (nq) nq.disabled = true;
|
| 441 |
+
if (fc) fc.classList.add('hidden');
|
| 442 |
+
if (es) es.classList.add('hidden');
|
| 443 |
+
if (qa) qa.classList.remove('hidden');
|
| 444 |
+
if (eb) eb.classList.remove('hidden');
|
| 445 |
+
clickGradioBtn('#hidden-question-btn');
|
| 446 |
+
}
|
| 447 |
+
|
| 448 |
+
function doSubmit() {
|
| 449 |
+
const ai = $('answer-input');
|
| 450 |
+
const answer = ai?.value?.trim();
|
| 451 |
+
if (!answer || !S.currentQuestion || S.waitingFB) return;
|
| 452 |
+
S.waitingFB = true;
|
| 453 |
+
S.prevFeedback = document.querySelector('#hidden-feedback-output textarea')?.value || '';
|
| 454 |
+
const sb = $('submit-btn'), st = $('submit-btn-text'), ss = $('submit-spinner'), nq = $('new-q-btn');
|
| 455 |
+
if (sb) sb.disabled = true;
|
| 456 |
+
if (st) st.textContent = 'Evaluatingβ¦';
|
| 457 |
+
if (ss) ss.classList.remove('hidden');
|
| 458 |
+
if (nq) nq.disabled = true;
|
| 459 |
+
setGradioTA('#hidden-answer-input textarea', answer);
|
| 460 |
+
setTimeout(() => clickGradioBtn('#hidden-submit-btn'), 200);
|
| 461 |
+
}
|
| 462 |
+
|
| 463 |
+
// ββ Modal ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 464 |
+
function showModal() {
|
| 465 |
+
const modal = $('modal');
|
| 466 |
+
if (!modal) return;
|
| 467 |
+
const pct = S.total > 0 ? Math.round(S.correct/S.total*100) : 0;
|
| 468 |
+
const msb = $('modal-score-big'), msl = $('modal-score-label'), mm = $('modal-message'), mh = $('modal-history');
|
| 469 |
+
if (msb) msb.textContent = S.correct+'/'+S.total;
|
| 470 |
+
if (msl) msl.textContent = S.total === 1 ? 'question answered' : 'questions answered';
|
| 471 |
+
if (mm) mm.textContent = pct>=90 ? "Outstanding! You've mastered this material. \\uD83C\\uDF1F"
|
| 472 |
+
: pct>=70 ? "Great work β solid grasp of the content. \\uD83D\\uDCAA"
|
| 473 |
+
: pct>=50 ? "Good start! Keep practising the tricky sections. \\uD83D\\uDCD6"
|
| 474 |
+
: "Keep going β each question makes you stronger. \\uD83D\\uDD25";
|
| 475 |
+
if (mh && S.history.length > 0) {
|
| 476 |
+
mh.classList.remove('hidden');
|
| 477 |
+
mh.innerHTML = S.history.slice(-8).map(h => {
|
| 478 |
+
const vc = h.verdict==='correct'?'correct':h.verdict==='partial'?'partial':'incorrect';
|
| 479 |
+
return '<div class="modal-history-item"><span class="hist-badge '+vc+'">'+(vc==='correct'?'β':vc==='partial'?'~':'β')+'</span><span class="hist-q">'+h.question+'</span></div>';
|
| 480 |
+
}).join('');
|
| 481 |
+
}
|
| 482 |
+
modal.classList.remove('hidden');
|
| 483 |
+
}
|
| 484 |
+
|
| 485 |
+
// ββ Wire HTML buttons ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 486 |
+
function wireButtons() {
|
| 487 |
+
const pairs = [
|
| 488 |
+
['start-btn', () => fetchQuestion()],
|
| 489 |
+
['new-q-btn', () => fetchQuestion()],
|
| 490 |
+
['submit-btn', () => doSubmit()],
|
| 491 |
+
['end-btn', () => { if (S.total===0) { const m=$('modal'); if(m) m.classList.remove('hidden'); } else showModal(); }],
|
| 492 |
+
['modal-close', () => {
|
| 493 |
+
const modal=$('modal'); if(modal) modal.classList.add('hidden');
|
| 494 |
+
S.correct=0; S.total=0; S.history=[]; S.currentQuestion='';
|
| 495 |
+
updateScore();
|
| 496 |
+
const ai=$('answer-input'), cc=$('char-count'), sb=$('submit-btn'), fc=$('feedback-card');
|
| 497 |
+
const es=$('empty-state'), qa=$('question-area'), eb=$('end-btn'), qc=$('quiz-card');
|
| 498 |
+
if(ai){ai.value='';} if(cc) cc.textContent='0 chars'; if(sb) sb.disabled=true;
|
| 499 |
+
if(fc) fc.classList.add('hidden'); if(es) es.classList.remove('hidden');
|
| 500 |
+
if(qa) qa.classList.add('hidden'); if(eb) eb.classList.add('hidden');
|
| 501 |
+
if(qc) qc.scrollIntoView({behavior:'smooth',block:'start'});
|
| 502 |
+
}],
|
| 503 |
+
];
|
| 504 |
+
for (const [id, fn] of pairs) {
|
| 505 |
+
const el = $(id);
|
| 506 |
+
if (el && !el._pp) { el._pp = true; el.addEventListener('click', fn); }
|
| 507 |
+
}
|
| 508 |
+
const modal = $('modal');
|
| 509 |
+
if (modal && !modal._pp) { modal._pp = true; modal.addEventListener('click', e => { if(e.target===modal) modal.classList.add('hidden'); }); }
|
| 510 |
+
const ai = $('answer-input'), sb = $('submit-btn'), cc = $('char-count');
|
| 511 |
+
if (ai && !ai._pp) {
|
| 512 |
+
ai._pp = true;
|
| 513 |
+
ai.addEventListener('input', () => {
|
| 514 |
+
if(cc) cc.textContent = ai.value.length+' chars';
|
| 515 |
+
if(sb) sb.disabled = ai.value.trim().length===0 || !S.currentQuestion;
|
| 516 |
+
});
|
| 517 |
+
}
|
| 518 |
+
}
|
| 519 |
+
|
| 520 |
+
// ββ Main polling loop ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 521 |
+
setInterval(function() {
|
| 522 |
+
wireButtons();
|
| 523 |
|
| 524 |
+
const statusEl = document.querySelector('#hidden-status-output textarea');
|
| 525 |
+
if (statusEl && statusEl.value) applyStatus(statusEl.value);
|
| 526 |
+
|
| 527 |
+
if (S.waitingQ) {
|
| 528 |
+
const qEl = document.querySelector('#hidden-question-output textarea');
|
| 529 |
+
if (qEl && qEl.value && qEl.value !== S.prevQuestion) showQuestion(qEl.value);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 530 |
}
|
| 531 |
|
| 532 |
+
if (S.waitingFB) {
|
| 533 |
+
const fbEl = document.querySelector('#hidden-feedback-output textarea');
|
| 534 |
+
if (fbEl && fbEl.value && fbEl.value !== S.prevFeedback) {
|
| 535 |
+
const text = fbEl.value;
|
| 536 |
+
S.prevFeedback = text; S.waitingFB = false;
|
| 537 |
+
const sb=$('submit-btn'), st=$('submit-btn-text'), ss=$('submit-spinner'), nq=$('new-q-btn');
|
| 538 |
+
if(st) st.textContent='Submit Answer'; if(ss) ss.classList.add('hidden');
|
| 539 |
+
if(sb) sb.disabled=false; if(nq) nq.disabled=false;
|
| 540 |
+
const verdict = showFeedback(text);
|
| 541 |
+
S.total++;
|
| 542 |
+
if (verdict==='correct') S.correct++;
|
| 543 |
+
S.history.push({question:S.currentQuestion, verdict});
|
| 544 |
+
updateScore();
|
| 545 |
+
}
|
| 546 |
+
}
|
| 547 |
+
}, 300);
|
| 548 |
}"""
|
| 549 |
|
| 550 |
print("β
Starting Gradio app...")
|
|
|
|
| 569 |
|
| 570 |
with gr.Row(elem_id="hidden-row-question"):
|
| 571 |
question_box = gr.Textbox(label="β Question", interactive=False, lines=3, scale=3, elem_id="hidden-question-output")
|
| 572 |
+
new_q_btn = gr.Button("New Question", variant="secondary", scale=1, elem_id="hidden-question-btn")
|
| 573 |
|
| 574 |
+
answer_box = gr.Textbox(label="βοΈ Your Answer", lines=4, placeholder="Write your answer hereβ¦", elem_id="hidden-answer-input")
|
| 575 |
+
submit_btn = gr.Button("Submit Answer", variant="primary", elem_id="hidden-submit-btn")
|
| 576 |
feedback_box = gr.Textbox(label="π¬ Feedback", interactive=False, lines=7, elem_id="hidden-feedback-output")
|
| 577 |
|
| 578 |
# Traduction UI quand on change la langue
|
ui/index.html
CHANGED
|
@@ -342,25 +342,6 @@
|
|
| 342 |
|
| 343 |
<!-- Controls row -->
|
| 344 |
<div class="controls-row">
|
| 345 |
-
<!-- Language toggle -->
|
| 346 |
-
<div>
|
| 347 |
-
<div class="diff-label">Language</div>
|
| 348 |
-
<div class="toggle-group" id="lang-toggle">
|
| 349 |
-
<button class="pill active" data-val="English">EN</button>
|
| 350 |
-
<button class="pill" data-val="FranΓ§ais">FR</button>
|
| 351 |
-
</div>
|
| 352 |
-
</div>
|
| 353 |
-
|
| 354 |
-
<!-- Difficulty toggle -->
|
| 355 |
-
<div>
|
| 356 |
-
<div class="diff-label">Difficulty</div>
|
| 357 |
-
<div class="toggle-group" id="diff-toggle">
|
| 358 |
-
<button class="pill" data-val="Easy">Easy</button>
|
| 359 |
-
<button class="pill active" data-val="Normal">Normal</button>
|
| 360 |
-
<button class="pill" data-val="Hard">Hard</button>
|
| 361 |
-
</div>
|
| 362 |
-
</div>
|
| 363 |
-
|
| 364 |
<!-- Score ring -->
|
| 365 |
<div class="score-wrap" style="margin-left:auto" title="Score">
|
| 366 |
<svg class="score-ring" viewBox="0 0 56 56">
|
|
@@ -452,27 +433,14 @@
|
|
| 452 |
</div>
|
| 453 |
|
| 454 |
<script>
|
| 455 |
-
//
|
| 456 |
-
//
|
| 457 |
-
//
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
total: 0,
|
| 464 |
-
language: 'English',
|
| 465 |
-
difficulty: 'Normal',
|
| 466 |
-
history: [],
|
| 467 |
-
loaded: false,
|
| 468 |
-
};
|
| 469 |
-
|
| 470 |
-
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 471 |
-
// DOM refs
|
| 472 |
-
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 473 |
-
const $ = id => document.getElementById(id);
|
| 474 |
-
|
| 475 |
-
const quizCard = $('quiz-card');
|
| 476 |
const emptyState = $('empty-state');
|
| 477 |
const questionArea = $('question-area');
|
| 478 |
const qText = $('q-text');
|
|
@@ -830,4 +798,5 @@
|
|
| 830 |
}
|
| 831 |
draw();
|
| 832 |
})();
|
|
|
|
| 833 |
</script>
|
|
|
|
| 342 |
|
| 343 |
<!-- Controls row -->
|
| 344 |
<div class="controls-row">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 345 |
<!-- Score ring -->
|
| 346 |
<div class="score-wrap" style="margin-left:auto" title="Score">
|
| 347 |
<svg class="score-ring" viewBox="0 0 56 56">
|
|
|
|
| 433 |
</div>
|
| 434 |
|
| 435 |
<script>
|
| 436 |
+
// Script intentionally left minimal β all quiz logic lives in BRIDGE_JS
|
| 437 |
+
// injected via demo.load(js=BRIDGE_JS) which actually executes.
|
| 438 |
+
// innerHTML-injected <script> tags are never run by browsers.
|
| 439 |
+
|
| 440 |
+
// (all logic removed β lives in BRIDGE_JS)
|
| 441 |
+
// placeholder to keep closing tag:
|
| 442 |
+
void 0;
|
| 443 |
+
/*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 444 |
const emptyState = $('empty-state');
|
| 445 |
const questionArea = $('question-area');
|
| 446 |
const qText = $('q-text');
|
|
|
|
| 798 |
}
|
| 799 |
draw();
|
| 800 |
})();
|
| 801 |
+
*/
|
| 802 |
</script>
|