import os from datetime import datetime import random import gradio as gr from datasets import load_dataset, Dataset from huggingface_hub import whoami EXAM_DATASET_ID = os.getenv("EXAM_DATASET_ID") or "agents-course/unit_1_quiz" EXAM_MAX_QUESTIONS = os.getenv("EXAM_MAX_QUESTIONS") or 10 EXAM_PASSING_SCORE = os.getenv("EXAM_PASSING_SCORE") or 0.7 ds = load_dataset(EXAM_DATASET_ID, split="train") # Convert dataset to a list of dicts and randomly sort quiz_data = ds.to_pandas().to_dict("records") random.shuffle(quiz_data) # Limit to max questions if specified if EXAM_MAX_QUESTIONS: quiz_data = quiz_data[: int(EXAM_MAX_QUESTIONS)] def on_user_logged_in(token: gr.OAuthToken | None): """ If the user has a valid token, hide the login button and show the Start button. Otherwise, keep the login button visible, hide Start. """ if token is not None: return gr.update(visible=False), gr.update(visible=False) else: # Not logged in, keep the login visible, hide Start return gr.update(visible=True), gr.update(visible=False) def push_results_to_hub(user_answers, token: gr.OAuthToken | None): """ Create a new dataset from user_answers and push it to the Hub. Calculates grade and checks against passing threshold. """ if token is None: gr.Warning("Please log in to Hugging Face before pushing!") return # Calculate grade correct_count = sum(1 for answer in user_answers if answer["is_correct"]) total_questions = len(user_answers) grade = correct_count / total_questions if total_questions > 0 else 0 if grade < float(EXAM_PASSING_SCORE): gr.Warning( f"Score {grade:.1%} below passing threshold of {float(EXAM_PASSING_SCORE):.1%}" ) return f"You scored {grade:.1%}. Please try again to achieve at least {float(EXAM_PASSING_SCORE):.1%}" gr.Info("Submitting answers to the Hub. Please wait...", duration=2) user_info = whoami(token=token.token) repo_id = f"{EXAM_DATASET_ID}_student_responses" submission_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") new_ds = Dataset.from_list(user_answers) new_ds = new_ds.map( lambda x: { "username": user_info["name"], "datetime": submission_time, "grade": grade, } ) new_ds.push_to_hub(repo_id) return f"Your responses have been submitted to the Hub! Final grade: {grade:.1%}" def handle_quiz(question_idx, user_answers, selected_answer, is_start): """ A single function that handles both 'Start' and 'Next' logic: - If is_start=True, skip storing an answer and show the first question. - Otherwise, store the last answer and move on. - If we've reached the end, display results. """ # Hide the start button once the first question is shown start_btn_update = gr.update(visible=False) if is_start else None # If this is the first time (start=True), begin at question_idx=0 if is_start: question_idx = 0 else: # If not the very first question, store the user's last selection if question_idx < len(quiz_data): current_q = quiz_data[question_idx] correct_reference = current_q["correct_answer"] correct_reference = f"answer_{correct_reference}".lower() is_correct = selected_answer == current_q[correct_reference] user_answers.append( { "question": current_q["question"], "selected_answer": selected_answer, "correct_answer": current_q[correct_reference], "is_correct": is_correct, "correct_reference": correct_reference, } ) question_idx += 1 # If we've reached the end, show final results if question_idx >= len(quiz_data): correct_count = sum(1 for answer in user_answers if answer["is_correct"]) grade = correct_count / len(user_answers) results_text = ( f"**Quiz Complete!**\n\n" f"Your score: {grade:.1%}\n" f"Passing score: {float(EXAM_PASSING_SCORE):.1%}\n\n" ) return ( "", # question_text becomes blank gr.update(choices=[], visible=False), f"{'✅ Passed!' if grade >= float(EXAM_PASSING_SCORE) else '❌ Did not pass'}", question_idx, user_answers, start_btn_update, gr.update(value=results_text, visible=True), # show final_markdown ) else: # Otherwise, show the next question q = quiz_data[question_idx] updated_question = f"## Question {question_idx + 1} \n### {q['question']}" return ( updated_question, gr.update( choices=[ q["answer_a"], q["answer_b"], q["answer_c"], q["answer_d"], ], value=None, visible=True, ), "Select an answer and click 'Next' to continue.", question_idx, user_answers, start_btn_update, gr.update(visible=False), # Hide final_markdown for now ) def success_message(response): # response is whatever push_results_to_hub returned return f"{response}\n\n**Success!**" with gr.Blocks() as demo: demo.title = f"Dataset Quiz for {EXAM_DATASET_ID}" # State variables question_idx = gr.State(value=0) user_answers = gr.State(value=[]) with gr.Row(variant="compact"): gr.Markdown(f"## Welcome to the {EXAM_DATASET_ID} Quiz") with gr.Row(variant="compact"): gr.Markdown( "Log in first, then click 'Start' to begin. Answer each question, click 'Next', and finally click 'Submit' to publish your results to the Hugging Face Hub." ) # We display question text with Markdown with gr.Row( variant="panel", ): question_text = gr.Markdown("") radio_choices = gr.Radio( choices=[], visible=False, label="Your Answer", scale=1.5 ) with gr.Row(variant="compact"): status_text = gr.Markdown("") with gr.Row(variant="compact"): # Final results after all questions are done final_markdown = gr.Markdown("", visible=False) next_btn = gr.Button("Next ⏭️") submit_btn = gr.Button("Submit ✅") with gr.Row(variant="compact"): login_btn = gr.LoginButton() # We'll hide the Start button until user logs in start_btn = gr.Button("Start", visible=False) # Use click() instead of login() login_btn.click(fn=on_user_logged_in, inputs=None, outputs=[login_btn, start_btn]) # Click "Start" => show first question, hide Start button start_btn.click( fn=handle_quiz, inputs=[question_idx, user_answers, radio_choices, gr.State(True)], outputs=[ question_text, radio_choices, status_text, question_idx, user_answers, start_btn, final_markdown, ], ) # Click "Next" => store selection, move on next_btn.click( fn=handle_quiz, inputs=[question_idx, user_answers, radio_choices, gr.State(False)], outputs=[ question_text, radio_choices, status_text, question_idx, user_answers, start_btn, final_markdown, ], ) submit_btn.click(fn=push_results_to_hub, inputs=[user_answers]) if __name__ == "__main__": # Note: If testing locally, you'll need to run `huggingface-cli login` or set HF_TOKEN # environment variable for the login to work locally. demo.launch()