jpruzcuen commited on
Commit
f8d9c9f
·
1 Parent(s): 0daf932

updated quiz ui

Browse files
Files changed (2) hide show
  1. app.py +44 -72
  2. quiz.py +22 -83
app.py CHANGED
@@ -1,9 +1,11 @@
1
  import re
2
  import gradio as gr
3
- from quiz import create_quiz, parse_quiz, start_quiz, submit_and_next, restart_quiz
 
4
  from chatbot import chat
5
  from llama_cpp import Llama
6
  from huggingface_hub import hf_hub_download
 
7
 
8
  print("Downloading model...")
9
  model_path = hf_hub_download(
@@ -25,6 +27,7 @@ raw_quiz = create_quiz(llm)
25
  parsed_quiz = parse_quiz(raw_quiz)
26
 
27
 
 
28
  with gr.Blocks(title="TAI: AI Teacher Assistant") as demo:
29
  gr.Markdown("""
30
  # TAI: Your AI Teacher Assistant
@@ -41,7 +44,7 @@ with gr.Blocks(title="TAI: AI Teacher Assistant") as demo:
41
  with gr.Row():
42
 
43
  # Left column: chat
44
- with gr.Column(scale=1):
45
  chatbot = gr.ChatInterface(
46
  fn=lambda message, history: chat(llm, message, history),
47
  examples=[
@@ -53,86 +56,55 @@ with gr.Blocks(title="TAI: AI Teacher Assistant") as demo:
53
  )
54
 
55
  # Right column: quiz
56
- with gr.Column(scale=2):
57
  gr.Markdown("## Test Yourself")
58
- # Visible elements
59
- progress = gr.Markdown(f"**Progress:** 0/0", elem_id="quiz-progress")
60
- question_md = gr.Markdown("", elem_id="quiz-question")
61
- options = gr.Radio(choices=[], label="Choose an answer", type="value")
62
- next_btn = gr.Button("Next")
63
  start_btn = gr.Button("Start Quiz", variant="primary")
64
- restart_btn = gr.Button("Restart Quiz")
65
- result_md = gr.Markdown("", visible=False)
66
-
67
- # State holders
68
- quiz_raw_state = gr.State(raw_quiz) # raw quiz text
69
- quiz_parsed_state = gr.State(parsed_quiz) # parsed list of questions
70
- idx_state = gr.State(0) # current index
71
- score_state = gr.State(0) # current score
72
- finished_state = gr.State(False) # finished flag
73
-
74
- # Start Quiz -> show first question (no model call)
75
- def on_start(quiz_raw, quiz_parsed):
76
- payload, idx, score = start_quiz(quiz_raw, quiz_parsed)
77
- # return question_md, options choices, idx, score, progress, result_md_visible
78
- return payload["question_md"], payload["options"], gr.update(value=idx), gr.update(value=score), payload["progress"], gr.update(visible=False), gr.update(visible=True)
79
- # Note: last two are for hiding/showing result_md (kept simple)
80
 
81
- start_btn.click(
82
- fn=lambda qr, qp: start_quiz(qr, qp)[0], # we only want the payload (question data) but mapping below
83
- inputs=[quiz_raw_state, quiz_parsed_state],
84
- outputs=[question_md]
85
- )
86
 
87
- # After start, we also need to set options, progress, idx, score and ensure result is hidden.
88
- def start_full(quiz_raw, quiz_parsed):
89
- payload, idx, score = start_quiz(quiz_raw, quiz_parsed)
90
- return payload["question_md"], payload["options"], idx, score, payload["progress"], "", False
 
 
91
 
92
- start_btn.click(
93
- fn=start_full,
94
- inputs=[quiz_raw_state, quiz_parsed_state],
95
- outputs=[question_md, options, idx_state, score_state, progress, result_md, finished_state]
96
- )
97
 
98
- # Next button: check selected and move forward
99
- def on_next(selected, quiz_parsed, idx, score):
100
- md_or_q, opts, new_idx, new_score, prog, finished = submit_and_next(selected, quiz_parsed, idx, score)
101
- if finished:
102
- # show final result
103
- return md_or_q, [], new_idx, new_score, prog, True
104
- else:
105
- return md_or_q, opts, new_idx, new_score, prog, False
106
-
107
- next_btn.click(
108
- fn=on_next,
109
- inputs=[options, quiz_parsed_state, idx_state, score_state],
110
- outputs=[question_md, options, idx_state, score_state, progress, finished_state]
111
- )
112
 
113
- # When finished_state becomes True, show the result markdown and hide options
114
- def show_result_if_finished(finished_flag, current_question_md):
115
- if finished_flag:
116
- # current_question_md will be the result text returned earlier
117
- return gr.update(value=current_question_md), gr.update(visible=True), gr.update(visible=False)
118
- else:
119
- return gr.update(value=""), gr.update(visible=False), gr.update(visible=True)
120
-
121
- finished_state.change(
122
- fn=show_result_if_finished,
123
- inputs=[finished_state, question_md],
124
- outputs=[result_md, result_md, options] # show result_md & hide options when finished
125
  )
126
 
127
- # Restart quiz (without regenerating)
128
- def on_restart():
129
- q_md, q_opts, idx, score, prog = restart_quiz(raw_quiz, parsed_quiz)
130
- return q_md, q_opts, idx, score, prog, "", False
131
-
132
- restart_btn.click(
133
- fn=on_restart,
 
 
 
 
 
 
 
 
 
 
 
134
  inputs=[],
135
- outputs=[question_md, options, idx_state, score_state, progress, result_md, finished_state]
136
  )
137
 
138
 
 
1
  import re
2
  import gradio as gr
3
+ #from quiz import create_quiz, parse_quiz, start_quiz, submit_and_next, restart_quiz
4
+ from quiz import *
5
  from chatbot import chat
6
  from llama_cpp import Llama
7
  from huggingface_hub import hf_hub_download
8
+ from functools import partial
9
 
10
  print("Downloading model...")
11
  model_path = hf_hub_download(
 
27
  parsed_quiz = parse_quiz(raw_quiz)
28
 
29
 
30
+
31
  with gr.Blocks(title="TAI: AI Teacher Assistant") as demo:
32
  gr.Markdown("""
33
  # TAI: Your AI Teacher Assistant
 
44
  with gr.Row():
45
 
46
  # Left column: chat
47
+ with gr.Column(scale=2):
48
  chatbot = gr.ChatInterface(
49
  fn=lambda message, history: chat(llm, message, history),
50
  examples=[
 
56
  )
57
 
58
  # Right column: quiz
59
+ with gr.Column(scale=1):
60
  gr.Markdown("## Test Yourself")
61
+
 
 
 
 
62
  start_btn = gr.Button("Start Quiz", variant="primary")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
+ question_md = gr.Markdown("")
65
+ feedback_md = gr.Markdown("")
66
+ progress_md = gr.Markdown("")
 
 
67
 
68
+ # Answer buttons
69
+ btn_A = gr.Button("A")
70
+ btn_B = gr.Button("B")
71
+ btn_C = gr.Button("C")
72
+ btn_D = gr.Button("D")
73
+ retry_btn = gr.Button("Retry")
74
 
75
+ # States
76
+ idx_state = gr.State(0)
77
+ score_state = gr.State(0)
 
 
78
 
79
+ # Start quiz
80
+ start_fn = partial(start_quiz, parsed_quiz)
 
 
 
 
 
 
 
 
 
 
 
 
81
 
82
+ start_btn.click(
83
+ fn=start_fn,
84
+ inputs=[],
85
+ outputs=[question_md, idx_state, score_state, feedback_md, progress_md, start_btn]
 
 
 
 
 
 
 
 
86
  )
87
 
88
+ # Answer button clicks
89
+ answer_fn_A = partial(answer_question, parsed_quiz, "A")
90
+ answer_fn_B = partial(answer_question, parsed_quiz, "B")
91
+ answer_fn_C = partial(answer_question, parsed_quiz, "C")
92
+ answer_fn_D = partial(answer_question, parsed_quiz, "D")
93
+
94
+ btn_A.click(fn=answer_fn_A, inputs=[idx_state, score_state],
95
+ outputs=[question_md, idx_state, score_state, feedback_md, progress_md])
96
+ btn_B.click(fn=answer_fn_B, inputs=[idx_state, score_state],
97
+ outputs=[question_md, idx_state, score_state, feedback_md, progress_md])
98
+ btn_C.click(fn=answer_fn_C, inputs=[idx_state, score_state],
99
+ outputs=[question_md, idx_state, score_state, feedback_md, progress_md])
100
+ btn_D.click(fn=answer_fn_D, inputs=[idx_state, score_state],
101
+ outputs=[question_md, idx_state, score_state, feedback_md, progress_md])
102
+
103
+ # Retry button
104
+ retry_btn.click(
105
+ fn=retry_quiz,
106
  inputs=[],
107
+ outputs=[question_md, idx_state, score_state, feedback_md, progress_md, start_btn]
108
  )
109
 
110
 
quiz.py CHANGED
@@ -68,91 +68,30 @@ def parse_quiz(text):
68
 
69
  return parsed
70
 
71
-
72
  def start_quiz(quiz_raw, quiz_parsed):
73
- """Return first question payload and initialize index & score."""
74
- # If parse failed or zero questions, show raw text
75
- if not quiz_parsed:
76
- return {
77
- "question_md": "⚠️ Could not parse generated quiz. Showing raw output below:\n\n" + quiz_raw,
78
- "options": [],
79
- "progress": "0/0"
80
- }, 0, 0 # index, score
81
-
82
  idx = 0
83
  q = quiz_parsed[idx]
84
- opts = q["options"]
85
- # If options are empty, present the whole block as markdown
86
- if not opts:
87
- return {
88
- "question_md": f"**Q {idx+1}.** {q['q']}",
89
- "options": [],
90
- "progress": f"{idx+1}/{len(quiz_parsed)}"
91
- }, idx, 0
92
-
93
- # present options as list of strings
94
  return {
95
- "question_md": f"**Q {idx+1}.** {q['q']}",
96
- "options": [f"{letter}) {opt}" for letter, opt in zip(['A','B','C','D'], opts)],
97
- "progress": f"{idx+1}/{len(quiz_parsed)}"
98
- }, idx, 0
99
-
100
- def show_question(quiz_parsed, idx):
101
- """Return the question (md), options list, progress for a given index."""
102
- if not quiz_parsed or idx < 0 or idx >= len(quiz_parsed):
103
- return "No question", [], "0/0"
104
- q = quiz_parsed[idx]
105
- opts = q["options"]
106
- md = f"**Q {idx+1}.** {q['q']}"
107
- if not opts:
108
- return md, [], f"{idx+1}/{len(quiz_parsed)}"
109
- return md, [f"{letter}) {opt}" for letter, opt in zip(['A','B','C','D'], opts)], f"{idx+1}/{len(quiz_parsed)}"
110
-
111
- def submit_and_next(selected, quiz_parsed, idx, score):
112
- """
113
- Process user's selected option for question idx, return:
114
- - question text for next idx or results message if finished
115
- - options list for next question
116
- - updated index, updated score
117
- - status/progress string
118
- """
119
- # Validate
120
- if not quiz_parsed:
121
- return "No quiz parsed.", [], idx, score, "0/0", False
122
-
123
- # Check current answer
124
- if 0 <= idx < len(quiz_parsed):
125
- current = quiz_parsed[idx]
126
- correct = current.get("answer")
127
- # normalize selected like "A) text" -> "A"
128
- sel_letter = None
129
- if selected:
130
- m = re.match(r'\s*([A-D])[\)\.]', selected)
131
- if m:
132
- sel_letter = m.group(1).upper()
133
- # If there is a correct letter available, compare
134
- if correct and sel_letter:
135
- if sel_letter == correct:
136
- score += 1
137
-
138
- # Move to next question
139
- idx += 1
140
- if idx >= len(quiz_parsed):
141
- # Quiz finished
142
- result_md = f"### 🧾 Quiz complete!\n\nYour score: **{score} / {len(quiz_parsed)}**"
143
- # Optionally show correct answers summary
144
- answers_lines = []
145
- for i, q in enumerate(quiz_parsed):
146
- ans = q.get("answer") or "?"
147
- answers_lines.append(f"{i+1}. {ans}")
148
- result_md += "\n\n**Correct answers:**\n\n" + "\n".join(answers_lines)
149
- return result_md, [], idx, score, f"{len(quiz_parsed)}/{len(quiz_parsed)}", True
150
-
151
- # Otherwise return next question
152
- md, opts, progress = show_question(quiz_parsed, idx)
153
- return md, opts, idx, score, progress, False
154
 
155
- def restart_quiz(raw_quiz, parsed_quiz):
156
- """Reset to the first question, keep the same parsed quiz."""
157
- payload, idx, score = start_quiz(raw_quiz, parsed_quiz)
158
- return payload["question_md"], payload["options"], idx, score, payload["progress"]
 
68
 
69
  return parsed
70
 
 
71
  def start_quiz(quiz_raw, quiz_parsed):
72
+ """Initialize quiz state, return first question display."""
 
 
 
 
 
 
 
 
73
  idx = 0
74
  q = quiz_parsed[idx]
 
 
 
 
 
 
 
 
 
 
75
  return {
76
+ "question_text": q["q"],
77
+ "feedback": "",
78
+ "progress": f"{idx+1}/{len(quiz_parsed)}",
79
+ "show_start": False
80
+ }, idx, 0 # idx, score
81
+
82
+ def answer_question(parsed_quiz, selected, idx, score):
83
+ current = parsed_quiz[idx]
84
+ correct = current["answer"]
85
+ if selected.upper() == correct:
86
+ score += 1
87
+ idx += 1
88
+ if idx >= len(parsed_quiz):
89
+ return "Quiz Complete!", idx, score, "✅ Correct!", f"{len(parsed_quiz)}/{len(parsed_quiz)}"
90
+ q = parsed_quiz[idx]
91
+ return q["q"], idx, score, "✅ Correct!", f"{idx+1}/{len(parsed_quiz)}"
92
+ else:
93
+ return current["q"], idx, score, "❌ Incorrect, try again.", f"{idx+1}/{len(parsed_quiz)}"
94
+
95
+ def retry_quiz():
96
+ return start_quiz()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97