Spaces:
Sleeping
Sleeping
| import json | |
| from typing import List, Any, Dict, Tuple, Union | |
| import gradio as gr | |
| from huggingface_hub import InferenceClient | |
| MAX_QUESTIONS = 20 | |
| def parse_quiz_json(content: str) -> Union[List[Dict[str, Any]], Dict[str, Any]]: | |
| """Parse the quiz JSON content robustly.""" | |
| try: | |
| if content.startswith("{") or content.startswith("["): | |
| quiz = json.loads(content) | |
| else: | |
| quiz = json.loads(content.lstrip().removeprefix("```json").removesuffix("```")) | |
| if isinstance(quiz, dict) and "questions" in quiz: | |
| return quiz["questions"] | |
| return quiz | |
| except Exception: | |
| try: | |
| quiz = json.loads(json.loads(content)) | |
| if isinstance(quiz, dict) and "questions" in quiz: | |
| return quiz["questions"] | |
| return quiz | |
| except Exception as e: | |
| return {"error": f"Could not parse JSON: {e}", "raw_output": content} | |
| def generate_quiz( | |
| topic: str, num_questions: int, q_type: str | |
| ) -> Tuple[List[dict[str, Any]], List[str]]: | |
| """Generate quiz questions and correct answers.""" | |
| if not topic: | |
| updates = [ | |
| gr.update(visible=True, value="Please enter a topic."), | |
| *[gr.update(visible=False, value="", interactive=False) for _ in range((MAX_QUESTIONS * 2) - 1)] | |
| ] | |
| return updates, [] | |
| system_msg = "You are an expert quiz creator." | |
| user_msg = ( | |
| f"Create a quiz containing {num_questions} {q_type} questions based on the following topic:\n" | |
| f"{topic}\n" | |
| "Respond with a JSON array of objects, each containing a question, options, and the answer.\n" | |
| ) | |
| if q_type == "Multiple Choice": | |
| user_msg += "Each question should have 4 answer choices, unlabeled, and one correct answer." | |
| client = InferenceClient() | |
| content = "" | |
| # Stream the response from the model | |
| for chunk in client.chat.completions.create( | |
| model="openai/gpt-oss-20b", | |
| messages=[ | |
| {"role": "system", "content": system_msg}, | |
| {"role": "user", "content": user_msg} | |
| ], | |
| max_tokens=1500, | |
| temperature=0.7, | |
| stream=True, | |
| # response_format={ | |
| # "type": "json_schema", | |
| # "json_schema": { | |
| # "name": "questions", | |
| # "description": "A list of questions, answer choices, and the correct answer", | |
| # "schema": { | |
| # "type": "array", | |
| # "items": { | |
| # "type": "object", | |
| # "properties": { | |
| # "question": {"type": "string"}, | |
| # "options": { | |
| # "type": "array", | |
| # "items": {"type": "string"} | |
| # }, | |
| # "answer": {"type": "string"} | |
| # }, | |
| # } | |
| # }, | |
| # "strict": True | |
| # } | |
| # }, | |
| ): | |
| if chunk.choices and chunk.choices[0].delta and chunk.choices[0].delta.content: | |
| content += chunk.choices[0].delta.content | |
| quiz = parse_quiz_json(content) | |
| updates = [] | |
| correct_answers = [] | |
| for i in range(MAX_QUESTIONS): | |
| if isinstance(quiz, dict) and "error" in quiz: | |
| if i == 0: | |
| updates.append(gr.update(visible=True, value="Oops! An error occured. Please try again.")) | |
| else: | |
| updates.append(gr.update(visible=False, value="", interactive=False)) | |
| updates.append(gr.update(visible=False, choices=[], value=None, interactive=False)) | |
| elif isinstance(quiz, list) and i < len(quiz): | |
| question = quiz[i].get("question", "No question provided") | |
| options = ["True", "False"] if q_type == "True/False" else quiz[i].get("options", []) | |
| answer = quiz[i].get("answer", "No answer provided") | |
| correct_answers.append(answer) | |
| updates.append(gr.update(visible=True, value=f"{question}")) | |
| updates.append(gr.update(visible=True, choices=options, interactive=True, value=None)) | |
| else: | |
| updates.append(gr.update(visible=False, value="", interactive=False)) | |
| updates.append(gr.update(visible=False, choices=[], value=None, interactive=False)) | |
| return updates, correct_answers | |
| def score_quiz(*args) -> str: | |
| """Score the quiz based on user answers and correct answers.""" | |
| *user_answers, correct_answers = args | |
| if not correct_answers or not isinstance(correct_answers, list): | |
| return "No quiz generated yet. Please generate a quiz first." | |
| score = 0 | |
| total = 0 | |
| for i, user_ans in enumerate(user_answers): | |
| if i < len(correct_answers) and user_ans is not None: | |
| if user_ans == correct_answers[i]: | |
| score += 1 | |
| total += 1 | |
| return f"Your score: {score} / {total}" | |
| with gr.Blocks() as demo: | |
| gr.Markdown("# AI Quiz Generator") | |
| gr.Markdown("### Instructions") | |
| gr.Markdown( | |
| """1. Enter a topic for the quiz in the textbox above. | |
| 2. Select the number of questions you want. | |
| 3. Choose the type of questions (Multiple Choice or True/False). | |
| 4. Click 'Generate Quiz' to create your quiz. | |
| 5. The generated quiz will appear in the output box. | |
| 6. After answering, click 'Submit Answers' to see your score.""" | |
| ) | |
| topic_input = gr.Textbox(label="Quiz Topic / Prompt", lines=4, placeholder="Enter the topic for the quiz") | |
| num_questions_input = gr.Slider(minimum=1, maximum=MAX_QUESTIONS, step=1, label="Number of Questions", value=5) | |
| q_type_input = gr.Radio(choices=["Multiple Choice", "True/False"], label="Question Type", value="Multiple Choice") | |
| generate_button = gr.Button("Generate Quiz") | |
| quiz_components = [] | |
| for i in range(MAX_QUESTIONS): | |
| quiz_components.append(gr.Textbox(label=f"Q{i+1}", visible=False, interactive=False)) | |
| quiz_components.append(gr.Radio(choices=[], label=f"Options", visible=False)) | |
| score_output = gr.Textbox(label="Score", visible=True, interactive=False) | |
| submit_button = gr.Button("Submit Answers") | |
| correct_answers_state = gr.State([]) | |
| def generate_and_store(*args): | |
| updates, correct_answers = generate_quiz(*args) | |
| return (*updates, correct_answers, 0) | |
| generate_button.click( | |
| fn=generate_and_store, | |
| inputs=[topic_input, num_questions_input, q_type_input], | |
| outputs=[*quiz_components, correct_answers_state, score_output], | |
| ) | |
| radio_outputs = [quiz_components[i] for i in range(1, len(quiz_components), 2)] | |
| def score_with_state(*args): | |
| *user_answers, correct_answers = args | |
| return score_quiz(*user_answers, correct_answers) | |
| submit_button.click( | |
| fn=score_with_state, | |
| inputs=[*radio_outputs, correct_answers_state], | |
| outputs=[score_output], | |
| ) | |
| with gr.Row(): | |
| generate_button | |
| submit_button | |
| score_output | |
| if __name__ == "__main__": | |
| demo.launch() |