smart-quiz-ui / gradio_ui.py
NZLouislu's picture
Fix issues to add question numbers.
8d44ca7
import os
import random
import traceback
import torch
import gradio as gr
import html
from quiz_logic.generator import (
generate_questions,
quiz_data,
current_question_index,
score,
user_answer,
set_user_answer,
reset_user_answer,
increment_score,
increment_index,
)
def get_current_question_ui():
"""Get the UI updates for the current question with proper numbering"""
if not quiz_data or current_question_index[0] >= len(quiz_data):
return (
gr.update(value="Quiz finished!"),
gr.update(choices=[], value=None, interactive=False, visible=False),
gr.update(value="", visible=False),
gr.update(visible=False),
gr.update(value=f"πŸŽ‰ Quiz finished! Your score: {score[0]}/{len(quiz_data)}", visible=True)
)
q = quiz_data[current_question_index[0]]
question_number = current_question_index[0] + 1
total_questions = len(quiz_data)
question_display = f"""### Question {question_number} of {total_questions}
**{question_number}.** {q['question']}"""
return (
gr.update(value=question_display, visible=True),
gr.update(choices=q["options"], value=None, interactive=True, visible=True),
gr.update(value="", visible=False),
gr.update(visible=False),
gr.update(value="", visible=False)
)
def next_question_ui():
"""Move to next question and return UI updates"""
increment_index()
return get_current_question_ui()
def build_gradio_ui():
def reset_and_start_quiz(topic, n_questions, difficulty):
quiz_data.clear()
current_question_index[0] = 0
score[0] = 0
user_answer[0] = None
success = generate_questions(topic, int(n_questions), difficulty)
if not success:
return [
gr.update(visible=False),
gr.update(value=""),
gr.update(choices=[], visible=False),
gr.update(value="", visible=False),
gr.update(visible=False),
gr.update(visible=False),
gr.update(visible=True, value='<div style="color:#b00020;font-weight:600">⚠️ Failed to generate questions. Please try again.</div>'),
gr.update(interactive=True, value="Start Quiz")
]
question_update, options_update, feedback_update, next_btn_update, score_display_update = get_current_question_ui()
return [
gr.update(visible=True),
question_update,
options_update,
feedback_update,
next_btn_update,
score_display_update,
gr.update(visible=False),
gr.update(interactive=True, value="Start Quiz")
]
with gr.Blocks(title="Smart Quiz Maker", theme=gr.themes.Soft()) as demo:
gr.Markdown("""
# 🧠 Smart Quiz Maker
Test your knowledge with high-quality AI-generated multiple choice questions!
""")
with gr.Row():
topic = gr.Textbox(
label="Quiz Topic",
placeholder="Enter any topic (e.g. Python, Machine Learning, History, Science)",
value="Python"
)
n_questions = gr.Radio(
label="Number of Questions",
choices=[3, 5, 10],
value=5
)
difficulty = gr.Radio(
label="Difficulty",
choices=["easy", "medium", "hard"],
value="medium"
)
with gr.Column(visible=False) as loading_box:
loading_html = gr.HTML(
'''<div style="display:flex;align-items:center;justify-content:center;gap:12px;padding:20px;background:#f8f9fa;border-radius:8px;margin:10px 0">
<div style="width:32px;height:32px;border:4px solid #e6e6e6;border-top-color:#1f6feb;border-radius:50%;animation:spin 1s linear infinite"></div>
<div style="font-weight:600;font-size:16px">πŸ” Generating high-quality quiz questions...</div>
</div>
<style>@keyframes spin{to{transform:rotate(360deg)}}</style>'''
)
start_btn = gr.Button("πŸš€ Start Quiz", variant="primary", size="lg")
with gr.Column(visible=False) as quiz_container:
question = gr.Markdown()
options = gr.Radio(
choices=[],
visible=True,
label="Select your answer:"
)
feedback = gr.HTML(visible=False)
with gr.Row():
next_btn = gr.Button("Next Question", visible=False, variant="secondary")
score_display = gr.Markdown(visible=False)
start_btn.click(
fn=lambda: [gr.update(visible=True), gr.update(interactive=False, value="⏳ Generating...")],
outputs=[loading_box, start_btn],
).then(
fn=reset_and_start_quiz,
inputs=[topic, n_questions, difficulty],
outputs=[
quiz_container, question, options,
feedback, next_btn, score_display,
loading_box, start_btn
]
)
def show_final_score():
percentage = round((score[0] / len(quiz_data)) * 100) if quiz_data else 0
if percentage >= 90:
grade = "πŸ† Excellent!"
color = "#16a34a"
elif percentage >= 70:
grade = "πŸ‘ Good job!"
color = "#2563eb"
elif percentage >= 50:
grade = "πŸ“š Keep practicing!"
color = "#f59e0b"
else:
grade = "πŸ’ͺ Try again!"
color = "#dc2626"
score_html = f'''
<div style="text-align:center;padding:30px;background:linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);border-radius:12px;margin:20px 0">
<h2 style="color:{color};margin-bottom:20px">{grade}</h2>
<div style="font-size:48px;font-weight:bold;color:{color};margin-bottom:10px">{score[0]}/{len(quiz_data)}</div>
<div style="font-size:24px;color:#6c757d;margin-bottom:20px">{percentage}% Correct</div>
<div style="font-size:16px;color:#6c757d">Click "πŸš€ Start Quiz" to try again with new questions!</div>
</div>
'''
return [
"",
gr.update(visible=False),
gr.update(value="", visible=False),
gr.update(visible=False),
gr.update(visible=True, value=score_html)
]
def next_and_show():
question_update, options_update, feedback_update, next_btn_update, score_display_update = next_question_ui()
# Check if quiz is finished by looking at the value in the update dict
question_value = question_update.get('value', '')
if "Quiz finished" in str(question_value):
return show_final_score()
else:
return [
question_update,
options_update,
feedback_update,
next_btn_update,
score_display_update
]
next_btn.click(
fn=next_and_show,
outputs=[question, options, feedback, next_btn, score_display]
)
def select_and_feedback(option):
if option is None:
return [
gr.update(interactive=True),
gr.update(value="", visible=False),
gr.update(visible=False)
]
try:
qobj = quiz_data[current_question_index[0]]
correct_answer = qobj.get("answer") or qobj.get("correct_answer") or qobj.get("correct")
explanation = qobj.get("explanation", "")
is_correct = str(option).strip().lower() == str(correct_answer).strip().lower()
if is_correct:
score[0] += 1
def esc(s):
return html.escape(str(s)) if s is not None else ""
if is_correct:
feedback_html = f'''
<div style="margin:20px 0; padding:20px; border-radius:12px; background:linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%); box-shadow:0 4px 6px rgba(0,0,0,0.1); border-left:6px solid #16a34a">
<div style="display:flex; gap:20px; align-items:flex-start">
<div style="font-size:52px; line-height:1; color:#16a34a">βœ…</div>
<div style="flex:1">
<div style="font-size:24px; font-weight:700; color:#15803d; margin-bottom:12px">Correct!</div>
<div style="font-size:16px; color:#166534; margin-bottom:8px">Your answer: <strong>{esc(option)}</strong></div>
{f'<div style="font-size:14px; color:#065f46; background:#f0fdf4; padding:12px; border-radius:6px; margin-top:12px"><strong>Explanation:</strong> {esc(explanation)}</div>' if explanation else ""}
</div>
</div>
</div>
'''
else:
feedback_html = f'''
<div style="margin:20px 0; padding:20px; border-radius:12px; background:linear-gradient(135deg, #fef2f2 0%, #fee2e2 100%); box-shadow:0 4px 6px rgba(0,0,0,0.1); border-left:6px solid #dc2626">
<div style="display:flex; gap:20px; align-items:flex-start">
<div style="font-size:52px; line-height:1; color:#dc2626">❌</div>
<div style="flex:1">
<div style="font-size:24px; font-weight:700; color:#dc2626; margin-bottom:12px">Incorrect</div>
<div style="font-size:16px; color:#7f1d1d; margin-bottom:8px">Your answer: <strong>{esc(option)}</strong></div>
<div style="font-size:16px; font-weight:600; color:#15803d; margin-bottom:8px">Correct answer: <strong>{esc(correct_answer)}</strong></div>
{f'<div style="font-size:14px; color:#7f1d1d; background:#fef2f2; padding:12px; border-radius:6px; margin-top:12px"><strong>Explanation:</strong> {esc(explanation)}</div>' if explanation else ""}
</div>
</div>
</div>
'''
is_last_question = (current_question_index[0] == len(quiz_data) - 1)
next_label = "🎯 View Results" if is_last_question else "➑️ Next Question"
return [
gr.update(interactive=False, value=option),
gr.update(value=feedback_html, visible=True),
gr.update(visible=True, value=next_label)
]
except Exception as e:
print(f"Error in feedback generation: {e}")
return [
gr.update(interactive=False, value=option),
gr.update(value="<div style='color: orange; padding: 15px; text-align: center;'>⚠️ Feedback not available</div>", visible=True),
gr.update(visible=True, value="Next Question")
]
options.change(
fn=select_and_feedback,
inputs=[options],
outputs=[options, feedback, next_btn]
)
return demo