rsalehin commited on
Commit
9d8222a
Β·
verified Β·
1 Parent(s): a17337e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +144 -85
app.py CHANGED
@@ -6,63 +6,64 @@ from typing import List, Dict, Any
6
 
7
  class QuizApp:
8
  def __init__(self):
9
- """Initializes the QuizApp with an empty list of questions."""
10
  self.questions = []
 
11
 
12
  def parse_json_file(self, file_path: str) -> List[Dict[str, Any]]:
13
- """Parse JSON file containing quiz questions with validation."""
14
  try:
15
  with open(file_path, 'r', encoding='utf-8') as f:
16
  data = json.load(f)
17
 
18
- questions_data = []
19
- # Handle different possible JSON structures
20
  if isinstance(data, list):
21
- questions_data = data
22
- elif isinstance(data, dict) and 'questions' in data:
23
- questions_data = data['questions']
 
 
 
24
 
25
- # Validate and format each question
26
  validated_questions = []
27
- for q in questions_data:
28
  if all(key in q for key in ['question', 'options', 'answer']):
29
- options = q['options'] if isinstance(q['options'], list) else [q['options']]
30
- # ADDED: Ensure there are at least 2 options for a valid MCQ
31
- if len(options) >= 2:
32
- validated_questions.append({
33
- 'question': q['question'],
34
- 'options': options,
35
- 'answer': q['answer']
36
- })
37
 
38
  return validated_questions
39
  except Exception as e:
40
  raise Exception(f"Error parsing JSON file: {str(e)}")
41
 
42
  def parse_xml_file(self, file_path: str) -> List[Dict[str, Any]]:
43
- """Parse XML file containing quiz questions with validation."""
44
  try:
45
  tree = ET.parse(file_path)
46
  root = tree.getroot()
47
 
48
  questions = []
49
 
50
- # Find all question elements, trying common tag names
51
  question_elements = root.findall('.//question') or root.findall('.//item')
52
 
53
  for q_elem in question_elements:
54
- question_text_elem = q_elem.find('text') or q_elem.find('question')
55
  options_elem = q_elem.find('options') or q_elem.find('choices')
56
  answer_elem = q_elem.find('answer') or q_elem.find('correct')
57
 
58
- if question_text_elem is not None and options_elem is not None and answer_elem is not None:
59
- options = [
60
- option.text.strip() for option in (options_elem.findall('option') or options_elem.findall('choice')) if option.text
61
- ]
 
62
 
63
  if len(options) >= 2: # At least 2 options required
64
  questions.append({
65
- 'question': question_text_elem.text.strip(),
66
  'options': options,
67
  'answer': answer_elem.text.strip()
68
  })
@@ -72,7 +73,7 @@ class QuizApp:
72
  raise Exception(f"Error parsing XML file: {str(e)}")
73
 
74
  def load_quiz_file(self, file) -> str:
75
- """Load and parse quiz file from Gradio upload component."""
76
  if file is None:
77
  return "Please upload a file first."
78
 
@@ -88,7 +89,7 @@ class QuizApp:
88
  return "Unsupported file format. Please upload a JSON or XML file."
89
 
90
  if not self.questions:
91
- return "No valid questions found in the file. Please check the file format and content."
92
 
93
  return f"βœ… Successfully loaded {len(self.questions)} questions! Click 'Start Quiz' to begin."
94
 
@@ -96,12 +97,13 @@ class QuizApp:
96
  return f"❌ Error loading file: {str(e)}"
97
 
98
  def generate_quiz_html(self) -> str:
99
- """Generate HTML for the quiz interface."""
100
  if not self.questions:
101
  return "<p>No questions loaded. Please upload a quiz file first.</p>"
102
 
103
- # This function generates a self-contained HTML page with embedded CSS and JavaScript
104
- # to run the quiz. It passes the quiz data safely as a JSON string.
 
105
  html = f"""
106
  <div style="max-width: 800px; margin: 0 auto; padding: 20px; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;">
107
  <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; border-radius: 15px; text-align: center; margin-bottom: 30px; box-shadow: 0 10px 30px rgba(0,0,0,0.2);">
@@ -126,8 +128,9 @@ class QuizApp:
126
  option_letter = chr(65 + j) # A, B, C, D
127
  html += f"""
128
  <label style="display: block; margin-bottom: 12px; padding: 12px 15px; background: white; border: 2px solid #e1e5e9; border-radius: 8px; cursor: pointer; transition: all 0.3s ease; font-size: 1.1em;"
129
- onmouseover="this.style.borderColor='#667eea'; this.style.backgroundColor='#f0f3ff';"
130
- onmouseout="this.style.borderColor='#e1e5e9'; this.style.backgroundColor='white';">
 
131
  <input type="radio" name="q{i}" value="{option}" style="margin-right: 12px; transform: scale(1.2);">
132
  <span style="font-weight: 500; color: #667eea; margin-right: 8px;">{option_letter}.</span>
133
  <span style="color: #333;">{option}</span>
@@ -139,7 +142,7 @@ class QuizApp:
139
  </div>
140
  """
141
 
142
- html += """
143
  <div style="text-align: center; margin-top: 40px;">
144
  <button type="button" onclick="submitQuiz()"
145
  style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; padding: 15px 40px; font-size: 1.2em; border-radius: 30px; cursor: pointer; box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4); transition: all 0.3s ease; font-weight: 500;"
@@ -154,52 +157,89 @@ class QuizApp:
154
  </div>
155
 
156
  <script>
157
- const quizData = """ + json.dumps(self.questions) + """;
 
158
 
159
- function submitQuiz() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  const form = document.getElementById('quizForm');
161
- const formData = new FormData(form);
162
- const answers = {};
163
- let totalQuestions = """ + str(len(self.questions)) + """;
164
  let answeredQuestions = 0;
165
 
166
- for (let [key, value] of formData.entries()) {
167
- answers[key] = value;
168
- answeredQuestions++;
169
- }
 
 
 
 
 
 
 
170
 
171
- if (answeredQuestions < totalQuestions) {
172
- alert('⚠️ Please answer all questions before submitting!');
 
 
 
 
173
  return;
174
- }
175
 
 
176
  let score = 0;
177
  let feedback = [];
178
 
179
- quizData.forEach((question, index) => {
180
- const userAnswer = answers[`q${index}`];
181
  const correctAnswer = question.answer;
 
182
 
183
- if (userAnswer === correctAnswer) {
184
  score++;
185
- feedback.push({ question: question.question, userAnswer: userAnswer, correctAnswer: correctAnswer, isCorrect: true });
186
- } else {
187
- feedback.push({ question: question.question, userAnswer: userAnswer || 'No answer', correctAnswer: correctAnswer, isCorrect: false });
188
- }
189
- });
 
 
 
 
 
 
 
190
 
191
  displayResults(score, totalQuestions, feedback);
192
- }
193
 
194
- function displayResults(score, total, feedback) {
195
  const percentage = Math.round((score / total) * 100);
196
  let resultsHtml = `
197
  <div style="background: white; padding: 30px; border-radius: 15px; box-shadow: 0 5px 20px rgba(0,0,0,0.1);">
198
  <div style="text-align: center; margin-bottom: 30px;">
199
  <h2 style="color: #333; margin-bottom: 15px;">πŸŽ‰ Quiz Complete!</h2>
200
  <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 15px; display: inline-block; min-width: 200px;">
201
- <div style="font-size: 3em; font-weight: bold; margin-bottom: 10px;">${score}/${total}</div>
202
- <div style="font-size: 1.5em;">${percentage}%</div>
203
  </div>
204
  </div>
205
 
@@ -207,28 +247,28 @@ class QuizApp:
207
  <h3 style="color: #333; margin-bottom: 20px; text-align: center;">πŸ“Š Detailed Results</h3>
208
  `;
209
 
210
- feedback.forEach((item, index) => {
211
  const icon = item.isCorrect ? 'βœ…' : '❌';
212
  const bgColor = item.isCorrect ? '#e8f5e8' : '#ffe8e8';
213
  const borderColor = item.isCorrect ? '#4caf50' : '#f44336';
214
 
215
  resultsHtml += `
216
- <div style="margin-bottom: 20px; padding: 20px; background: ${bgColor}; border-left: 5px solid ${borderColor}; border-radius: 8px;">
217
- <div style="font-weight: bold; color: #333; margin-bottom: 10px;">
218
- ${icon} Question ${index + 1}
219
- </div>
220
- <div style="color: #666; margin-bottom: 10px; font-style: italic;">
221
- "${item.question}"
222
- </div>
223
- <div style="margin-bottom: 5px;">
224
- <strong>Your answer:</strong> ${item.userAnswer}
225
- </div>
226
- <div>
227
- <strong>Correct answer:</strong> ${item.correctAnswer}
228
- </div>
229
  </div>
 
 
 
 
230
  `;
231
- });
232
 
233
  resultsHtml += `
234
  </div>
@@ -246,28 +286,41 @@ class QuizApp:
246
  document.getElementById('results').style.display = 'block';
247
  document.getElementById('quizForm').style.display = 'none';
248
 
249
- document.getElementById('results').scrollIntoView({ behavior: 'smooth' });
250
- }
 
 
 
 
 
251
  </script>
252
  """
253
 
254
  return html
255
 
256
- # Initialize the quiz app instance
257
  quiz_app = QuizApp()
258
 
259
- # Create the Gradio interface using Blocks for a custom layout
260
  with gr.Blocks(
261
  title="Multiple Choice Quiz App",
262
  theme=gr.themes.Soft(),
263
  css="""
264
- .gradio-container { max-width: 1200px !important; margin: auto !important; }
265
- .upload-area { border: 2px dashed #667eea !important; border-radius: 10px !important; background: #f8f9ff !important; }
 
 
 
 
 
 
 
266
  """
267
  ) as app:
268
 
269
  gr.Markdown("""
270
  # πŸŽ“ Multiple Choice Quiz Application
 
271
  Upload your quiz file (JSON or XML format) and start taking the quiz!
272
 
273
  ### Expected File Formats:
@@ -308,7 +361,11 @@ with gr.Blocks(
308
  elem_classes=["upload-area"]
309
  )
310
 
311
- load_btn = gr.Button("πŸ“€ Load Quiz", variant="primary", size="lg")
 
 
 
 
312
 
313
  status_msg = gr.Textbox(
314
  label="Status",
@@ -316,14 +373,19 @@ with gr.Blocks(
316
  value="Please upload a quiz file to get started."
317
  )
318
 
319
- start_btn = gr.Button("πŸš€ Start Quiz", variant="secondary", size="lg", visible=False)
 
 
 
 
 
320
 
321
  with gr.Column(scale=2):
322
  quiz_html = gr.HTML(
323
  value="<div style='text-align: center; padding: 50px; color: #666;'><h3>Upload a quiz file to begin</h3></div>"
324
  )
325
 
326
- # Define event handlers
327
  def load_quiz_handler(file):
328
  result = quiz_app.load_quiz_file(file)
329
  if "Successfully loaded" in result:
@@ -334,7 +396,6 @@ with gr.Blocks(
334
  def start_quiz_handler():
335
  return quiz_app.generate_quiz_html()
336
 
337
- # Connect components to handlers
338
  load_btn.click(
339
  fn=load_quiz_handler,
340
  inputs=[file_upload],
@@ -351,7 +412,5 @@ if __name__ == "__main__":
351
  app.launch(
352
  server_name="0.0.0.0",
353
  server_port=7860,
354
- # Set share=False for safer local development.
355
- # Set to True only if you need to share publicly.
356
- share=False
357
  )
 
6
 
7
  class QuizApp:
8
  def __init__(self):
 
9
  self.questions = []
10
+ self.current_quiz = None
11
 
12
  def parse_json_file(self, file_path: str) -> List[Dict[str, Any]]:
13
+ """Parse JSON file containing quiz questions"""
14
  try:
15
  with open(file_path, 'r', encoding='utf-8') as f:
16
  data = json.load(f)
17
 
18
+ questions = []
19
+ # Handle different JSON structures
20
  if isinstance(data, list):
21
+ questions = data
22
+ elif isinstance(data, dict):
23
+ if 'questions' in data:
24
+ questions = data['questions']
25
+ else:
26
+ questions = [data]
27
 
28
+ # Validate question format
29
  validated_questions = []
30
+ for q in questions:
31
  if all(key in q for key in ['question', 'options', 'answer']):
32
+ validated_questions.append({
33
+ 'question': q['question'],
34
+ 'options': q['options'] if isinstance(q['options'], list) else [q['options']],
35
+ 'answer': q['answer']
36
+ })
 
 
 
37
 
38
  return validated_questions
39
  except Exception as e:
40
  raise Exception(f"Error parsing JSON file: {str(e)}")
41
 
42
  def parse_xml_file(self, file_path: str) -> List[Dict[str, Any]]:
43
+ """Parse XML file containing quiz questions"""
44
  try:
45
  tree = ET.parse(file_path)
46
  root = tree.getroot()
47
 
48
  questions = []
49
 
50
+ # Handle different XML structures
51
  question_elements = root.findall('.//question') or root.findall('.//item')
52
 
53
  for q_elem in question_elements:
54
+ question_text = q_elem.find('text') or q_elem.find('question')
55
  options_elem = q_elem.find('options') or q_elem.find('choices')
56
  answer_elem = q_elem.find('answer') or q_elem.find('correct')
57
 
58
+ if question_text is not None and options_elem is not None and answer_elem is not None:
59
+ options = []
60
+ for option in options_elem.findall('option') or options_elem.findall('choice'):
61
+ if option.text:
62
+ options.append(option.text.strip())
63
 
64
  if len(options) >= 2: # At least 2 options required
65
  questions.append({
66
+ 'question': question_text.text.strip(),
67
  'options': options,
68
  'answer': answer_elem.text.strip()
69
  })
 
73
  raise Exception(f"Error parsing XML file: {str(e)}")
74
 
75
  def load_quiz_file(self, file) -> str:
76
+ """Load and parse quiz file"""
77
  if file is None:
78
  return "Please upload a file first."
79
 
 
89
  return "Unsupported file format. Please upload a JSON or XML file."
90
 
91
  if not self.questions:
92
+ return "No valid questions found in the file. Please check the file format."
93
 
94
  return f"βœ… Successfully loaded {len(self.questions)} questions! Click 'Start Quiz' to begin."
95
 
 
97
  return f"❌ Error loading file: {str(e)}"
98
 
99
  def generate_quiz_html(self) -> str:
100
+ """Generate HTML for the quiz interface"""
101
  if not self.questions:
102
  return "<p>No questions loaded. Please upload a quiz file first.</p>"
103
 
104
+ # Create JSON data for JavaScript
105
+ quiz_data_json = json.dumps(self.questions).replace('"', '&quot;')
106
+
107
  html = f"""
108
  <div style="max-width: 800px; margin: 0 auto; padding: 20px; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;">
109
  <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; border-radius: 15px; text-align: center; margin-bottom: 30px; box-shadow: 0 10px 30px rgba(0,0,0,0.2);">
 
128
  option_letter = chr(65 + j) # A, B, C, D
129
  html += f"""
130
  <label style="display: block; margin-bottom: 12px; padding: 12px 15px; background: white; border: 2px solid #e1e5e9; border-radius: 8px; cursor: pointer; transition: all 0.3s ease; font-size: 1.1em;"
131
+ onmouseover="this.style.borderColor='#667eea'; this.style.backgroundColor='#f0f3ff';"
132
+ onmouseout="this.style.borderColor='#e1e5e9'; this.style.backgroundColor='white';"
133
+ onclick="selectAnswer(this)">
134
  <input type="radio" name="q{i}" value="{option}" style="margin-right: 12px; transform: scale(1.2);">
135
  <span style="font-weight: 500; color: #667eea; margin-right: 8px;">{option_letter}.</span>
136
  <span style="color: #333;">{option}</span>
 
142
  </div>
143
  """
144
 
145
+ html += f"""
146
  <div style="text-align: center; margin-top: 40px;">
147
  <button type="button" onclick="submitQuiz()"
148
  style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; padding: 15px 40px; font-size: 1.2em; border-radius: 30px; cursor: pointer; box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4); transition: all 0.3s ease; font-weight: 500;"
 
157
  </div>
158
 
159
  <script>
160
+ // Store quiz data
161
+ const quizData = JSON.parse("{quiz_data_json}".replace(/&quot;/g, '"'));
162
 
163
+ function selectAnswer(labelElement) {{
164
+ // Remove previous selections from this question
165
+ const questionDiv = labelElement.closest('div[style*="margin-bottom: 35px"]');
166
+ const labels = questionDiv.querySelectorAll('label');
167
+ labels.forEach(label => {{
168
+ label.style.borderColor = '#e1e5e9';
169
+ label.style.backgroundColor = 'white';
170
+ }});
171
+
172
+ // Highlight selected answer
173
+ labelElement.style.borderColor = '#667eea';
174
+ labelElement.style.backgroundColor = '#e8f0fe';
175
+ }}
176
+
177
+ function submitQuiz() {{
178
+ console.log('Submit button clicked');
179
+ console.log('Quiz data:', quizData);
180
+
181
  const form = document.getElementById('quizForm');
182
+ const answers = {{}};
183
+ let totalQuestions = {len(self.questions)};
 
184
  let answeredQuestions = 0;
185
 
186
+ // Collect answers
187
+ for (let i = 0; i < totalQuestions; i++) {{
188
+ const radioButtons = document.getElementsByName(`q${{i}}`);
189
+ for (let radio of radioButtons) {{
190
+ if (radio.checked) {{
191
+ answers[`q${{i}}`] = radio.value;
192
+ answeredQuestions++;
193
+ break;
194
+ }}
195
+ }}
196
+ }}
197
 
198
+ console.log('Collected answers:', answers);
199
+ console.log('Answered questions:', answeredQuestions);
200
+
201
+ // Check if all questions are answered
202
+ if (answeredQuestions < totalQuestions) {{
203
+ alert(`⚠️ Please answer all questions before submitting! You have answered ${{answeredQuestions}} out of ${{totalQuestions}} questions.`);
204
  return;
205
+ }}
206
 
207
+ // Calculate score
208
  let score = 0;
209
  let feedback = [];
210
 
211
+ quizData.forEach((question, index) => {{
212
+ const userAnswer = answers[`q${{index}}`];
213
  const correctAnswer = question.answer;
214
+ const isCorrect = userAnswer === correctAnswer;
215
 
216
+ if (isCorrect) {{
217
  score++;
218
+ }}
219
+
220
+ feedback.push({{
221
+ question: question.question,
222
+ userAnswer: userAnswer || 'No answer',
223
+ correctAnswer: correctAnswer,
224
+ isCorrect: isCorrect
225
+ }});
226
+ }});
227
+
228
+ console.log('Final score:', score);
229
+ console.log('Feedback:', feedback);
230
 
231
  displayResults(score, totalQuestions, feedback);
232
+ }}
233
 
234
+ function displayResults(score, total, feedback) {{
235
  const percentage = Math.round((score / total) * 100);
236
  let resultsHtml = `
237
  <div style="background: white; padding: 30px; border-radius: 15px; box-shadow: 0 5px 20px rgba(0,0,0,0.1);">
238
  <div style="text-align: center; margin-bottom: 30px;">
239
  <h2 style="color: #333; margin-bottom: 15px;">πŸŽ‰ Quiz Complete!</h2>
240
  <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 15px; display: inline-block; min-width: 200px;">
241
+ <div style="font-size: 3em; font-weight: bold; margin-bottom: 10px;">${{score}}/${{total}}</div>
242
+ <div style="font-size: 1.5em;">${{percentage}}%</div>
243
  </div>
244
  </div>
245
 
 
247
  <h3 style="color: #333; margin-bottom: 20px; text-align: center;">πŸ“Š Detailed Results</h3>
248
  `;
249
 
250
+ feedback.forEach((item, index) => {{
251
  const icon = item.isCorrect ? 'βœ…' : '❌';
252
  const bgColor = item.isCorrect ? '#e8f5e8' : '#ffe8e8';
253
  const borderColor = item.isCorrect ? '#4caf50' : '#f44336';
254
 
255
  resultsHtml += `
256
+ <div style="margin-bottom: 20px; padding: 20px; background: ${{bgColor}}; border-left: 5px solid ${{borderColor}}; border-radius: 8px;">
257
+ <div style="font-weight: bold; color: #333; margin-bottom: 10px;">
258
+ ${{icon}} Question ${{index + 1}}
259
+ </div>
260
+ <div style="color: #666; margin-bottom: 10px; font-style: italic;">
261
+ "${{item.question}}"
262
+ </div>
263
+ <div style="margin-bottom: 5px;">
264
+ <strong>Your answer:</strong> ${{item.userAnswer}}
 
 
 
 
265
  </div>
266
+ <div>
267
+ <strong>Correct answer:</strong> ${{item.correctAnswer}}
268
+ </div>
269
+ </div>
270
  `;
271
+ }});
272
 
273
  resultsHtml += `
274
  </div>
 
286
  document.getElementById('results').style.display = 'block';
287
  document.getElementById('quizForm').style.display = 'none';
288
 
289
+ // Scroll to results
290
+ document.getElementById('results').scrollIntoView({{ behavior: 'smooth' }});
291
+ }}
292
+
293
+ // Add some debugging
294
+ console.log('Quiz script loaded');
295
+ console.log('Quiz data available:', typeof quizData !== 'undefined');
296
  </script>
297
  """
298
 
299
  return html
300
 
301
+ # Initialize the quiz app
302
  quiz_app = QuizApp()
303
 
304
+ # Create Gradio interface
305
  with gr.Blocks(
306
  title="Multiple Choice Quiz App",
307
  theme=gr.themes.Soft(),
308
  css="""
309
+ .gradio-container {
310
+ max-width: 1200px !important;
311
+ margin: auto !important;
312
+ }
313
+ .upload-area {
314
+ border: 2px dashed #667eea !important;
315
+ border-radius: 10px !important;
316
+ background: #f8f9ff !important;
317
+ }
318
  """
319
  ) as app:
320
 
321
  gr.Markdown("""
322
  # πŸŽ“ Multiple Choice Quiz Application
323
+
324
  Upload your quiz file (JSON or XML format) and start taking the quiz!
325
 
326
  ### Expected File Formats:
 
361
  elem_classes=["upload-area"]
362
  )
363
 
364
+ load_btn = gr.Button(
365
+ "πŸ“€ Load Quiz",
366
+ variant="primary",
367
+ size="lg"
368
+ )
369
 
370
  status_msg = gr.Textbox(
371
  label="Status",
 
373
  value="Please upload a quiz file to get started."
374
  )
375
 
376
+ start_btn = gr.Button(
377
+ "πŸš€ Start Quiz",
378
+ variant="secondary",
379
+ size="lg",
380
+ visible=False
381
+ )
382
 
383
  with gr.Column(scale=2):
384
  quiz_html = gr.HTML(
385
  value="<div style='text-align: center; padding: 50px; color: #666;'><h3>Upload a quiz file to begin</h3></div>"
386
  )
387
 
388
+ # Event handlers
389
  def load_quiz_handler(file):
390
  result = quiz_app.load_quiz_file(file)
391
  if "Successfully loaded" in result:
 
396
  def start_quiz_handler():
397
  return quiz_app.generate_quiz_html()
398
 
 
399
  load_btn.click(
400
  fn=load_quiz_handler,
401
  inputs=[file_upload],
 
412
  app.launch(
413
  server_name="0.0.0.0",
414
  server_port=7860,
415
+ share=True
 
 
416
  )