File size: 7,085 Bytes
7e947e2
 
 
9444dc0
7e947e2
 
 
 
9444dc0
7e947e2
9444dc0
 
 
 
7e947e2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9444dc0
7e947e2
 
 
 
 
 
 
 
 
 
 
9444dc0
7e947e2
 
9444dc0
7e947e2
9444dc0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7e947e2
9444dc0
7e947e2
 
 
 
 
 
9ccf303
7e947e2
 
 
 
 
 
 
 
9444dc0
7e947e2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
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()