DevTail0r commited on
Commit
b0bc3f2
Β·
verified Β·
1 Parent(s): 949a7be

Update services/quiz_service.py

Browse files
Files changed (1) hide show
  1. services/quiz_service.py +96 -127
services/quiz_service.py CHANGED
@@ -104,150 +104,119 @@ def _validate_questions(questions: list[dict]) -> list[dict]:
104
 
105
  def _render_quiz_html(questions: list[dict], title: str) -> str:
106
  """
107
- Render quiz as pure HTML/CSS using the checkbox hack.
108
- Each question has a hidden checkbox; clicking 'Show Answer' reveals
109
- the correct answer and explanation via CSS :checked selector.
110
- No JavaScript required β€” works inside Gradio's gr.HTML sandbox.
 
 
 
 
 
 
111
  """
112
- quiz_id = f"q{uuid.uuid4().hex[:8]}"
113
 
114
- css = f"""
115
- <style>
116
- .{quiz_id}-quiz {{
117
- font-family: inherit;
118
- max-width: 700px;
119
- }}
120
- .{quiz_id}-title {{
121
- font-size: 1.05rem;
122
- font-weight: 700;
123
- color: #e0e0f0;
124
- margin-bottom: 20px;
125
- padding-bottom: 10px;
126
  border-bottom: 1px solid rgba(255,255,255,0.08);
127
  }}
128
- .{quiz_id}-qblock {{
129
  background: rgba(255,255,255,0.03);
130
  border: 1px solid rgba(255,255,255,0.08);
131
- border-radius: 12px;
132
- padding: 18px 20px;
133
- margin-bottom: 14px;
134
- }}
135
- .{quiz_id}-qnum {{
136
- font-size: 0.72rem;
137
- color: #606080;
138
- text-transform: uppercase;
139
- letter-spacing: 0.05em;
140
- margin-bottom: 6px;
141
  }}
142
- .{quiz_id}-qtext {{
143
- font-size: 0.93rem;
144
- font-weight: 600;
145
- color: #d0d0e8;
146
- margin-bottom: 14px;
147
- line-height: 1.5;
148
  }}
149
- .{quiz_id}-options {{
150
- display: flex;
151
- flex-direction: column;
152
- gap: 7px;
153
- margin-bottom: 12px;
154
  }}
155
- .{quiz_id}-option {{
156
- padding: 9px 14px;
 
 
 
 
 
157
  background: rgba(255,255,255,0.02);
158
- border: 1px solid rgba(255,255,255,0.08);
159
- border-radius: 8px;
160
- font-size: 0.87rem;
161
- color: #a0a0c0;
162
- line-height: 1.4;
163
- }}
164
- .{quiz_id}-toggle {{
165
- display: none;
166
- }}
167
- .{quiz_id}-reveal-btn {{
168
- display: inline-block;
169
- padding: 7px 18px;
170
- background: rgba(102,126,234,0.10);
171
- border: 1px solid rgba(102,126,234,0.25);
172
- border-radius: 20px;
173
- font-size: 0.82rem;
174
- color: #a5b4fc;
175
- cursor: pointer;
176
- user-select: none;
177
- transition: background 0.15s;
178
- }}
179
- .{quiz_id}-reveal-btn:hover {{
180
- background: rgba(102,126,234,0.20);
181
- }}
182
- .{quiz_id}-answer {{
183
- display: none;
184
- margin-top: 12px;
185
- padding: 12px 16px;
186
- background: rgba(34,197,94,0.07);
187
- border: 1px solid rgba(34,197,94,0.20);
188
- border-radius: 10px;
189
- }}
190
- .{quiz_id}-toggle:checked ~ .{quiz_id}-answer {{
191
- display: block;
192
- }}
193
- .{quiz_id}-toggle:checked ~ label.{quiz_id}-reveal-btn {{
194
- display: none;
195
- }}
196
- .{quiz_id}-toggle:checked ~ .{quiz_id}-options .{quiz_id}-correct-option {{
197
- border-color: rgba(34,197,94,0.40);
198
- background: rgba(34,197,94,0.08);
199
- color: #86efac;
200
- }}
201
- .{quiz_id}-correct-label {{
202
- font-size: 0.82rem;
203
- color: #86efac;
204
- font-weight: 700;
205
- margin-bottom: 5px;
206
- }}
207
- .{quiz_id}-explanation {{
208
- font-size: 0.83rem;
209
- color: #7090a0;
210
- line-height: 1.5;
211
  }}
212
- .{quiz_id}-toggle:checked ~ .{quiz_id}-options-wrap .{quiz_id}-correct-option {{
213
- border-color: rgba(34,197,94,0.40);
214
- background: rgba(34,197,94,0.08);
215
- color: #86efac;
 
 
 
 
 
216
  }}
217
  </style>
218
  """
219
 
220
- body = f'<div class="{quiz_id}-quiz">\n'
221
- body += f'<div class="{quiz_id}-title">πŸ“ {title}</div>\n'
222
 
223
  for i, q in enumerate(questions):
224
- cb_id = f"{quiz_id}_cb{i}"
225
- correct_letter = q["answer"]
226
- correct_text = q["options"][ord(correct_letter) - 65][3:] # text after "A) "
227
-
228
- body += f'<div class="{quiz_id}-qblock">\n'
229
- body += f' <div class="{quiz_id}-qnum">Question {i+1} of {len(questions)}</div>\n'
230
- body += f' <div class="{quiz_id}-qtext">{q["question"]}</div>\n'
231
-
232
- # Checkbox MUST be placed before the elements it controls via ~ sibling selector
233
- body += f' <input type="checkbox" class="{quiz_id}-toggle" id="{cb_id}">\n'
234
-
235
- # Options wrapper β€” correct-option class is inert until :checked activates it via CSS
236
- body += f' <div class="{quiz_id}-options">\n'
237
- for opt in q["options"]:
238
- letter = opt[0]
239
- extra = f' {quiz_id}-correct-option' if letter == correct_letter else ''
240
- body += f' <div class="{quiz_id}-option{extra}">{opt}</div>\n'
241
- body += f' </div>\n'
242
-
243
- # Show Answer button β€” hidden after click via CSS
244
- body += f' <label for="{cb_id}" class="{quiz_id}-reveal-btn">πŸ‘ Show Answer</label>\n'
245
-
246
- # Answer panel β€” hidden until :checked
247
- body += f' <div class="{quiz_id}-answer">\n'
248
- body += f' <div class="{quiz_id}-correct-label">βœ… Correct Answer: {correct_letter}) {correct_text}</div>\n'
249
- body += f' <div class="{quiz_id}-explanation">{q["explanation"]}</div>\n'
250
- body += f' </div>\n'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
  body += f'</div>\n'
252
 
253
  body += '</div>\n'
 
104
 
105
  def _render_quiz_html(questions: list[dict], title: str) -> str:
106
  """
107
+ Pure CSS/HTML interactive quiz using radio buttons.
108
+
109
+ Mechanic:
110
+ - Each option is a <label> for a hidden <input type="radio">
111
+ - Clicking an option selects that radio
112
+ - CSS :checked selectors immediately highlight:
113
+ * Selected correct answer β†’ green
114
+ * Selected wrong answer β†’ red + correct answer reveals green
115
+ * Explanation panel appears on any selection
116
+ - No JavaScript needed β€” works in Gradio gr.HTML sandbox
117
  """
118
+ qid = f"q{uuid.uuid4().hex[:8]}"
119
 
120
+ css = f"""<style>
121
+ .{qid}-wrap {{ font-family: inherit; max-width: 700px; }}
122
+ .{qid}-title {{
123
+ font-size: 1.05rem; font-weight: 700; color: #e0e0f0;
124
+ margin-bottom: 20px; padding-bottom: 10px;
 
 
 
 
 
 
 
125
  border-bottom: 1px solid rgba(255,255,255,0.08);
126
  }}
127
+ .{qid}-qblock {{
128
  background: rgba(255,255,255,0.03);
129
  border: 1px solid rgba(255,255,255,0.08);
130
+ border-radius: 12px; padding: 18px 20px; margin-bottom: 14px;
 
 
 
 
 
 
 
 
 
131
  }}
132
+ .{qid}-qnum {{
133
+ font-size: 0.72rem; color: #505068; text-transform: uppercase;
134
+ letter-spacing: 0.05em; margin-bottom: 6px;
 
 
 
135
  }}
136
+ .{qid}-qtext {{
137
+ font-size: 0.93rem; font-weight: 600; color: #d0d0e8;
138
+ margin-bottom: 14px; line-height: 1.5;
 
 
139
  }}
140
+
141
+ /* Hide all radio inputs */
142
+ .{qid}-radio {{ display: none; }}
143
+
144
+ /* Option labels β€” look like cards */
145
+ .{qid}-opt {{
146
+ display: block; padding: 10px 15px; margin-bottom: 7px;
147
  background: rgba(255,255,255,0.02);
148
+ border: 1px solid rgba(255,255,255,0.09);
149
+ border-radius: 8px; font-size: 0.87rem; color: #a0a0c0;
150
+ cursor: pointer; line-height: 1.4;
151
+ transition: border-color 0.12s, background 0.12s;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  }}
153
+ .{qid}-opt:hover {{ background: rgba(102,126,234,0.07); border-color: rgba(102,126,234,0.25); color: #c0c0e0; }}
154
+
155
+ /* Explanation β€” hidden until any radio in the group is checked */
156
+ .{qid}-exp {{
157
+ display: none; margin-top: 10px; padding: 11px 15px;
158
+ background: rgba(102,126,234,0.06);
159
+ border-left: 3px solid rgba(102,126,234,0.35);
160
+ border-radius: 0 8px 8px 0;
161
+ font-size: 0.83rem; color: #8888b0; line-height: 1.5;
162
  }}
163
  </style>
164
  """
165
 
166
+ body = f'<div class="{qid}-wrap"><div class="{qid}-title">πŸ“ {title}</div>\n'
 
167
 
168
  for i, q in enumerate(questions):
169
+ correct = q["answer"] # "A", "B", "C", or "D"
170
+ correct_idx = ord(correct) - 65 # 0-3
171
+ correct_text = q["options"][correct_idx][3:]
172
+ letters = ["A", "B", "C", "D"]
173
+
174
+ body += f'<div class="{qid}-qblock">\n'
175
+ body += f' <div class="{qid}-qnum">Question {i+1} of {len(questions)}</div>\n'
176
+ body += f' <div class="{qid}-qtext">{q["question"]}</div>\n'
177
+
178
+ # Emit all 4 radio inputs FIRST (before labels) so CSS siblings work
179
+ for letter in letters:
180
+ radio_id = f"{qid}_q{i}_{letter}"
181
+ body += f' <input type="radio" class="{qid}-radio" name="{qid}_q{i}" id="{radio_id}" value="{letter}">\n'
182
+
183
+ # Now emit labels (option cards) + CSS rules inline per question
184
+ # We use a <style> block per question to generate the specific selectors
185
+ # because we need to reference the correct letter dynamically
186
+ body += f' <style>\n'
187
+
188
+ for letter in letters:
189
+ radio_id = f"{qid}_q{i}_{letter}"
190
+ correct_radio_id = f"{qid}_q{i}_{correct}"
191
+
192
+ if letter == correct:
193
+ # Selecting the correct answer β†’ green
194
+ body += f' #{radio_id}:checked ~ label[for="{radio_id}"] {{\n'
195
+ body += f' background: rgba(34,197,94,0.12); border-color: rgba(34,197,94,0.45); color: #86efac;\n'
196
+ body += f' }}\n'
197
+ else:
198
+ # Selecting a wrong answer β†’ that option turns red
199
+ body += f' #{radio_id}:checked ~ label[for="{radio_id}"] {{\n'
200
+ body += f' background: rgba(239,68,68,0.10); border-color: rgba(239,68,68,0.35); color: #fca5a5;\n'
201
+ body += f' }}\n'
202
+ # AND the correct answer reveals green
203
+ body += f' #{radio_id}:checked ~ label[for="{correct_radio_id}"] {{\n'
204
+ body += f' background: rgba(34,197,94,0.12); border-color: rgba(34,197,94,0.45); color: #86efac;\n'
205
+ body += f' }}\n'
206
+
207
+ # Any selection reveals the explanation
208
+ body += f' #{radio_id}:checked ~ .{qid}-exp {{ display: block; }}\n'
209
+
210
+ body += f' </style>\n'
211
+
212
+ # Option labels
213
+ for letter in letters:
214
+ radio_id = f"{qid}_q{i}_{letter}"
215
+ opt_text = q["options"][ord(letter) - 65]
216
+ body += f' <label for="{radio_id}" class="{qid}-opt">{opt_text}</label>\n'
217
+
218
+ # Explanation (revealed by CSS on any selection)
219
+ body += f' <div class="{qid}-exp">πŸ’‘ <strong>Explanation:</strong> {q["explanation"]}</div>\n'
220
  body += f'</div>\n'
221
 
222
  body += '</div>\n'