Spaces:
Sleeping
Sleeping
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 |