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='
⚠️ Failed to generate questions. Please try again.
'),
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(
'''
🔍 Generating high-quality quiz questions...
'''
)
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'''
{grade}
{score[0]}/{len(quiz_data)}
{percentage}% Correct
Click "🚀 Start Quiz" to try again with new questions!
'''
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'''
✅
Correct!
Your answer: {esc(option)}
{f'
Explanation: {esc(explanation)}
' if explanation else ""}
'''
else:
feedback_html = f'''
❌
Incorrect
Your answer: {esc(option)}
Correct answer: {esc(correct_answer)}
{f'
Explanation: {esc(explanation)}
' if explanation else ""}
'''
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="⚠️ Feedback not available
", visible=True),
gr.update(visible=True, value="Next Question")
]
options.change(
fn=select_and_feedback,
inputs=[options],
outputs=[options, feedback, next_btn]
)
return demo