EdBoy2202 commited on
Commit
ad2cf05
·
verified ·
1 Parent(s): dc7af08

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +256 -224
app.py CHANGED
@@ -3,235 +3,267 @@ import google.generativeai as genai
3
  import os
4
  import re # For improved parsing
5
 
6
- # Set up Streamlit page
7
- st.title("Interactive Multiple Choice Quiz Generator")
8
- st.markdown("Powered by Gemini API")
9
 
10
- # API Key Input from User
11
- gemini_api_key = st.text_input("Enter your Gemini API Key:", type="password")
 
12
 
13
- if not gemini_api_key:
14
- st.warning("Please enter your Gemini API key to generate a quiz.")
15
- st.stop()
16
- else:
17
- genai.configure(api_key=gemini_api_key)
18
- model = genai.GenerativeModel('gemini-pro')
19
-
20
- # Security Warning
 
 
 
 
 
 
 
 
21
  st.markdown("<font color='red'>**Warning:** Directly entering your API key is less secure than using Streamlit Secrets or environment variables. For production, use Secrets.</font>", unsafe_allow_html=True)
22
 
23
- # Initialize session state for quiz data and progress
24
- if 'quiz_data' not in st.session_state:
25
- st.session_state.quiz_data = None
26
- if 'current_question_index' not in st.session_state:
27
- st.session_state.current_question_index = 0
28
- if 'user_answers' not in st.session_state:
29
- st.session_state.user_answers = []
30
- if 'quiz_completed' not in st.session_state:
31
- st.session_state.quiz_completed = False
32
- if 'score' not in st.session_state:
33
- st.session_state.score = 0
34
-
35
-
36
- def parse_quiz_content(quiz_content):
37
- """Parses the quiz content and answer key into a structured format, handling title."""
38
- questions = []
39
- answer_key_dict = {}
40
-
41
- quiz_parts = quiz_content.split("Answer Key:")
42
- if len(quiz_parts) != 2:
43
- st.warning("Could not reliably separate quiz questions and answer key. Parsing might be imperfect.")
44
- return None, None
45
-
46
- question_section = quiz_parts[0].strip()
47
- answer_key_section = quiz_parts[1].strip()
48
-
49
- question_lines = question_section.strip().split('\n')
50
- current_question = None
51
- for line in question_lines:
52
- line = line.strip()
53
- if not line:
54
- continue # Skip empty lines
55
-
56
- if re.match(r'^\d+\.\s', line): # Start of a new question
57
- if current_question: # Save previous question if exists
58
- questions.append(current_question)
59
- current_question = {'options': {}} # Start new question
60
- current_question['question'] = re.sub(r'\*\*|\*', '', line).strip() # Question text, remove bold
61
- elif re.match(r'^[A-D]\)\s', line) or re.match(r'^[A-D]\.\s', line): # Option line (handle both ')' and '.' after letter)
62
- if current_question:
63
- option_letter = line[0]
64
- option_text = line[2:].strip() # Remove "A) " or "A. "
65
- current_question['options'][option_letter] = option_text
66
- elif re.match(r'^\*\*.*?\*\*$', line) or re.match(r'^\*.*?\*$', line) : # Check for title again, just in case after split. Remove bold title if found in lines.
67
- pass # Ignore title lines within question section if any.
68
-
69
- if current_question: # Append the last question
70
- questions.append(current_question)
71
-
72
-
73
- # --- Answer Key Parsing (No change needed) ---
74
- answer_lines = answer_key_section.strip().split('\n')
75
- for line in answer_lines:
76
- line = line.strip()
77
- match = re.match(r'(\d+)\.\s*([A-D])', line)
78
- if match:
79
- question_num = int(match.group(1)) - 1
80
- correct_answer = match.group(2)
81
- answer_key_dict[question_num] = correct_answer
82
-
83
- # Basic validation
84
- if not questions or not answer_key_dict:
85
- st.error("Error parsing quiz content. Please try again or check the generated format.")
86
- return None, None
87
- if len(questions) != len(answer_key_dict):
88
- st.warning(f"Number of questions parsed ({len(questions)}) does not match number of answers in answer key ({len(answer_key_dict)}). Parsing might be incomplete.")
89
-
90
-
91
- # Combine parsed questions and answer key into quiz_data
92
- quiz_data_list = []
93
- for i, q_data in enumerate(questions):
94
- correct_answer = answer_key_dict.get(i)
95
- if correct_answer:
96
- quiz_data_list.append({
97
- 'question': q_data['question'],
98
- 'options': q_data['options'],
99
- 'correct_answer': correct_answer
100
- })
101
- else:
102
- st.warning(f"Could not find correct answer for question {i+1} in the answer key.")
103
  return None, None
104
 
105
- return quiz_data_list, answer_key_dict
106
-
107
-
108
- def display_question():
109
- """Displays the current question and options for interactive quiz."""
110
- if st.session_state.quiz_data is None:
111
- st.error("Quiz data not loaded yet. Generate a quiz first.")
112
- return
113
-
114
- if st.session_state.current_question_index < len(st.session_state.quiz_data):
115
- question_data = st.session_state.quiz_data[st.session_state.current_question_index]
116
- question_number = st.session_state.current_question_index + 1
117
- st.markdown(f"**Question {question_number}:** {question_data['question']}")
118
-
119
- options_list = [f"{key}. {value}" for key, value in question_data['options'].items()]
120
- user_choice = st.radio("Choose an answer:", options_list, key=f"q_{question_number}") # Unique key for radio buttons
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
- if st.button("Submit Answer", key=f"submit_q_{question_number}"): # Unique key for submit button
123
- selected_option_letter = user_choice.split('.')[0] # Extract A, B, C, or D
124
- st.session_state.user_answers.append(selected_option_letter)
125
 
126
- if st.session_state.current_question_index < len(st.session_state.quiz_data) - 1:
127
- st.session_state.current_question_index += 1
128
- else:
129
- st.session_state.quiz_completed = True # Mark quiz as completed after last question
130
- st.rerun()
131
-
132
-
133
- def display_results():
134
- """Displays the quiz results after completion."""
135
- if st.session_state.quiz_completed:
136
- st.markdown("### Quiz Results")
137
- correct_count = 0
138
- for i in range(len(st.session_state.quiz_data)):
139
- user_answer = st.session_state.user_answers[i] if i < len(st.session_state.user_answers) else None # Handle if user didn't answer all
140
- correct_answer = st.session_state.quiz_data[i]['correct_answer']
141
- if user_answer == correct_answer:
142
- correct_count += 1
143
- result_text = f"**Question {i+1}:** ✅ Correct! (Your answer: {user_answer}, Correct answer: {correct_answer})"
144
- color = "green"
145
- else:
146
- result_text = f"**Question {i+1}:** ❌ Incorrect. (Your answer: {user_answer if user_answer else 'Not answered'}, Correct answer: {correct_answer})"
147
- color = "red"
148
- st.markdown(f"<font color='{color}'>{result_text}</font>", unsafe_allow_html=True)
149
-
150
- percentage_correct = (correct_count / len(st.session_state.quiz_data)) * 100
151
- st.markdown(f"### Final Score: {correct_count} out of {len(st.session_state.quiz_data)} correct ({percentage_correct:.2f}%)")
152
- st.session_state.score = correct_count # Store score in session state
153
-
154
-
155
- # User input for topic
156
- topic = st.text_input("Enter the topic for your quiz:")
157
-
158
- if topic:
159
- if st.button("Generate Quiz"):
160
- with st.spinner(f"Generating quiz on '{topic}'..."):
161
- try:
162
- prompt = f"""
163
- Generate a multiple-choice quiz on the topic of "{topic}".
164
- The quiz should have 5 questions.
165
-
166
- **Formatting Instructions:**
167
-
168
- 1. **Question Numbering:** Start each question with a number followed by a period (e.g., "1.").
169
- 2. **Question Text:** Immediately after the question number, write the question text.
170
- 3. **Options:**
171
- * List four options for each question, labeled A, B, C, and D.
172
- * Use the format: "A) Option text", "B) Option text", etc. (letter followed by a parenthesis, then a space, then the option text).
173
- * Place each option on a new line directly below the question.
174
- 4. **Answer Key:**
175
- * After all questions, create a separate section titled "**Answer Key:**".
176
- * In the Answer Key, list the correct answer for each question in the format: "1. Correct option letter", "2. Correct option letter", etc. (question number, period, space, correct option letter - A, B, C, or D).
177
-
178
- **Example of Desired Format:**
179
-
180
- **Quiz Title (Optional, but good to have)**
181
-
182
- 1. Question 1 text?
183
- A) Option A
184
- B) Option B
185
- C) Option C
186
- D) Option D
187
-
188
- 2. Question 2 text?
189
- A) Option A
190
- B) Option B
191
- C) Option C
192
- D) Option D
193
-
194
- ... (and so on for 5 questions) ...
195
-
196
- **Answer Key:**
197
- 1. C
198
- 2. A
199
- 3. B
200
- 4. D
201
- 5. A
202
-
203
- Please generate the quiz in this exact format.
204
- """
205
- response = model.generate_content(prompt)
206
- quiz_content = response.text
207
-
208
-
209
- parsed_quiz_data, answer_key = parse_quiz_content(quiz_content)
210
-
211
-
212
- if quiz_content: # Check if quiz_content was generated successfully (outer if)
213
- if parsed_quiz_data: # Check if parsing was successful (inner if)
214
- st.session_state.quiz_data = parsed_quiz_data
215
- st.session_state.current_question_index = 0
216
- st.session_state.user_answers = []
217
- st.session_state.quiz_completed = False
218
- st.session_state.score = 0
219
- st.success(f"Quiz on '{topic}' generated successfully! Let's begin.")
220
- else: # else associated with inner if parsed_quiz_data
221
- st.error("Failed to parse quiz content. Please try generating again.")
222
- st.session_state.quiz_data = None
223
- else: # else associated with outer if quiz_content
224
- st.error("Failed to generate quiz content. Please try again or check your API key.")
225
-
226
- except Exception as e:
227
- st.error(f"An error occurred: {e}")
228
- st.error("Please check your API key and network connection. If the problem persists, try a different topic or try again later.")
229
-
230
- # Quiz Display Logic
231
- if st.session_state.quiz_data:
232
- if not st.session_state.quiz_completed:
233
- display_question()
234
- else:
235
- display_results()
236
- elif topic and not st.session_state.quiz_data and not st.session_state.quiz_completed: # Message if topic entered but quiz not generated yet
237
- st.info("Click 'Generate Quiz' to start the quiz.")
 
3
  import os
4
  import re # For improved parsing
5
 
6
+ # --- Login Page ---
7
+ def login_page():
8
+ st.title("Quiz Generator Login")
9
 
10
+ api_options = ["Gemini API"] # Extend this list for more APIs in the future
11
+ selected_api = st.selectbox("Select API:", api_options)
12
+ api_key = st.text_input(f"Enter your {selected_api} Key:", type="password")
13
 
14
+ if st.button("Login"):
15
+ if not api_key:
16
+ st.error("Please enter your API key.")
17
+ else:
18
+ st.session_state.api_key = api_key
19
+ st.session_state.selected_api = selected_api
20
+ st.session_state.logged_in = True
21
+ st.success(f"Logged in with {selected_api}!")
22
+ st.rerun() # Rerun to show the quiz app
23
+
24
+ # --- Quiz Application ---
25
+ def quiz_app():
26
+ st.title("Interactive Multiple Choice Quiz Generator")
27
+ st.markdown("Powered by Gemini API") # Keeping this as title, can be dynamically updated if more APIs are added
28
+
29
+ # Security Warning - Moved here as it is part of the quiz app
30
  st.markdown("<font color='red'>**Warning:** Directly entering your API key is less secure than using Streamlit Secrets or environment variables. For production, use Secrets.</font>", unsafe_allow_html=True)
31
 
32
+ # Initialize session state for quiz data and progress (if not already initialized during login)
33
+ if 'quiz_data' not in st.session_state:
34
+ st.session_state.quiz_data = None
35
+ if 'current_question_index' not in st.session_state:
36
+ st.session_state.current_question_index = 0
37
+ if 'user_answers' not in st.session_state:
38
+ st.session_state.user_answers = []
39
+ if 'quiz_completed' not in st.session_state:
40
+ st.session_state.quiz_completed = False
41
+ if 'score' not in st.session_state:
42
+ st.session_state.score = 0
43
+
44
+ # API Configuration based on selection - Moved inside quiz_app after login
45
+ if st.session_state.selected_api == "Gemini API":
46
+ try:
47
+ genai.configure(api_key=st.session_state.api_key)
48
+ model = genai.GenerativeModel('gemini-pro')
49
+ except Exception as e:
50
+ st.error(f"Error configuring Gemini API: {e}")
51
+ st.error("Please check your API key and network connection.")
52
+ st.session_state.logged_in = False # Force logout if API config fails
53
+ del st.session_state.api_key
54
+ del st.session_state.selected_api
55
+ st.rerun() # Go back to login page
56
+ return # Exit quiz_app
57
+
58
+
59
+ def parse_quiz_content(quiz_content):
60
+ """Parses the quiz content and answer key into a structured format, handling title."""
61
+ questions = []
62
+ answer_key_dict = {}
63
+
64
+ quiz_parts = quiz_content.split("Answer Key:")
65
+ if len(quiz_parts) != 2:
66
+ st.warning("Could not reliably separate quiz questions and answer key. Parsing might be imperfect.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  return None, None
68
 
69
+ question_section = quiz_parts[0].strip()
70
+ answer_key_section = quiz_parts[1].strip()
71
+
72
+ question_lines = question_section.strip().split('\n')
73
+ current_question = None
74
+ for line in question_lines:
75
+ line = line.strip()
76
+ if not line:
77
+ continue # Skip empty lines
78
+
79
+ if re.match(r'^\d+\.\s', line): # Start of a new question
80
+ if current_question: # Save previous question if exists
81
+ questions.append(current_question)
82
+ current_question = {'options': {}} # Start new question
83
+ current_question['question'] = re.sub(r'\*\*|\*', '', line).strip() # Question text, remove bold
84
+ elif re.match(r'^[A-D]\)\s', line) or re.match(r'^[A-D]\.\s', line): # Option line (handle both ')' and '.' after letter)
85
+ if current_question:
86
+ option_letter = line[0]
87
+ option_text = line[2:].strip() # Remove "A) " or "A. "
88
+ current_question['options'][option_letter] = option_text
89
+ elif re.match(r'^\*\*.*?\*\*$', line) or re.match(r'^\*.*?\*$', line) : # Check for title again, just in case after split. Remove bold title if found in lines.
90
+ pass # Ignore title lines within question section if any.
91
+
92
+ if current_question: # Append the last question
93
+ questions.append(current_question)
94
+
95
+
96
+ # --- Answer Key Parsing (No change needed) ---
97
+ answer_lines = answer_key_section.strip().split('\n')
98
+ for line in answer_lines:
99
+ line = line.strip()
100
+ match = re.match(r'(\d+)\.\s*([A-D])', line)
101
+ if match:
102
+ question_num = int(match.group(1)) - 1
103
+ correct_answer = match.group(2)
104
+ answer_key_dict[question_num] = correct_answer
105
+
106
+ # Basic validation
107
+ if not questions or not answer_key_dict:
108
+ st.error("Error parsing quiz content. Please try again or check the generated format.")
109
+ return None, None
110
+ if len(questions) != len(answer_key_dict):
111
+ st.warning(f"Number of questions parsed ({len(questions)}) does not match number of answers in answer key ({len(answer_key_dict)}). Parsing might be incomplete.")
112
+
113
+
114
+ # Combine parsed questions and answer key into quiz_data
115
+ quiz_data_list = []
116
+ for i, q_data in enumerate(questions):
117
+ correct_answer = answer_key_dict.get(i)
118
+ if correct_answer:
119
+ quiz_data_list.append({
120
+ 'question': q_data['question'],
121
+ 'options': q_data['options'],
122
+ 'correct_answer': correct_answer
123
+ })
124
+ else:
125
+ st.warning(f"Could not find correct answer for question {i+1} in the answer key.")
126
+ return None, None
127
+
128
+ return quiz_data_list, answer_key_dict
129
+
130
+
131
+ def display_question():
132
+ """Displays the current question and options for interactive quiz."""
133
+ if st.session_state.quiz_data is None:
134
+ st.error("Quiz data not loaded yet. Generate a quiz first.")
135
+ return
136
+
137
+ if st.session_state.current_question_index < len(st.session_state.quiz_data):
138
+ question_data = st.session_state.quiz_data[st.session_state.current_question_index]
139
+ question_number = st.session_state.current_question_index + 1
140
+ st.markdown(f"**Question {question_number}:** {question_data['question']}")
141
+
142
+ options_list = [f"{key}. {value}" for key, value in question_data['options'].items()]
143
+ user_choice = st.radio("Choose an answer:", options_list, key=f"q_{question_number}") # Unique key for radio buttons
144
+
145
+ if st.button("Submit Answer", key=f"submit_q_{question_number}"): # Unique key for submit button
146
+ selected_option_letter = user_choice.split('.')[0] # Extract A, B, C, or D
147
+ st.session_state.user_answers.append(selected_option_letter)
148
+
149
+ if st.session_state.current_question_index < len(st.session_state.quiz_data) - 1:
150
+ st.session_state.current_question_index += 1
151
+ else:
152
+ st.session_state.quiz_completed = True # Mark quiz as completed after last question
153
+ st.rerun()
154
+
155
+
156
+ def display_results():
157
+ """Displays the quiz results after completion."""
158
+ if st.session_state.quiz_completed:
159
+ st.markdown("### Quiz Results")
160
+ correct_count = 0
161
+ for i in range(len(st.session_state.quiz_data)):
162
+ user_answer = st.session_state.user_answers[i] if i < len(st.session_state.user_answers) else None # Handle if user didn't answer all
163
+ correct_answer = st.session_state.quiz_data[i]['correct_answer']
164
+ if user_answer == correct_answer:
165
+ correct_count += 1
166
+ result_text = f"**Question {i+1}:** ✅ Correct! (Your answer: {user_answer}, Correct answer: {correct_answer})"
167
+ color = "green"
168
+ else:
169
+ result_text = f"**Question {i+1}:** ❌ Incorrect. (Your answer: {user_answer if user_answer else 'Not answered'}, Correct answer: {correct_answer})"
170
+ color = "red"
171
+ st.markdown(f"<font color='{color}'>{result_text}</font>", unsafe_allow_html=True)
172
+
173
+ percentage_correct = (correct_count / len(st.session_state.quiz_data)) * 100
174
+ st.markdown(f"### Final Score: {correct_count} out of {len(st.session_state.quiz_data)} correct ({percentage_correct:.2f}%)")
175
+ st.session_state.score = correct_count # Store score in session state
176
+
177
+
178
+ # User input for topic
179
+ topic = st.text_input("Enter the topic for your quiz:")
180
+
181
+ if topic:
182
+ if st.button("Generate Quiz"):
183
+ with st.spinner(f"Generating quiz on '{topic}'..."):
184
+ try:
185
+ prompt = f"""
186
+ Generate a multiple-choice quiz on the topic of "{topic}".
187
+ The quiz should have 5 questions.
188
+
189
+ **Formatting Instructions:**
190
+
191
+ 1. **Question Numbering:** Start each question with a number followed by a period (e.g., "1.").
192
+ 2. **Question Text:** Immediately after the question number, write the question text.
193
+ 3. **Options:**
194
+ * List four options for each question, labeled A, B, C, and D.
195
+ * Use the format: "A) Option text", "B) Option text", etc. (letter followed by a parenthesis, then a space, then the option text).
196
+ * Place each option on a new line directly below the question.
197
+ 4. **Answer Key:**
198
+ * After all questions, create a separate section titled "**Answer Key:**".
199
+ * In the Answer Key, list the correct answer for each question in the format: "1. Correct option letter", "2. Correct option letter", etc. (question number, period, space, correct option letter - A, B, C, or D).
200
+
201
+ **Example of Desired Format:**
202
+
203
+ **Quiz Title (Optional, but good to have)**
204
+
205
+ 1. Question 1 text?
206
+ A) Option A
207
+ B) Option B
208
+ C) Option C
209
+ D) Option D
210
+
211
+ 2. Question 2 text?
212
+ A) Option A
213
+ B) Option B
214
+ C) Option C
215
+ D) Option D
216
+
217
+ ... (and so on for 5 questions) ...
218
+
219
+ **Answer Key:**
220
+ 1. C
221
+ 2. A
222
+ 3. B
223
+ 4. D
224
+ 5. A
225
+
226
+ Please generate the quiz in this exact format.
227
+ """
228
+ response = model.generate_content(prompt)
229
+ quiz_content = response.text
230
+
231
+
232
+ parsed_quiz_data, answer_key = parse_quiz_content(quiz_content)
233
+
234
+
235
+ if quiz_content: # Check if quiz_content was generated successfully (outer if)
236
+ if parsed_quiz_data: # Check if parsing was successful (inner if)
237
+ st.session_state.quiz_data = parsed_quiz_data
238
+ st.session_state.current_question_index = 0
239
+ st.session_state.user_answers = []
240
+ st.session_state.quiz_completed = False
241
+ st.session_state.score = 0
242
+ st.success(f"Quiz on '{topic}' generated successfully! Let's begin.")
243
+ else: # else associated with inner if parsed_quiz_data
244
+ st.error("Failed to parse quiz content. Please try generating again.")
245
+ st.session_state.quiz_data = None
246
+ else: # else associated with outer if quiz_content
247
+ st.error("Failed to generate quiz content. Please try again or check your API key.")
248
+
249
+ except Exception as e:
250
+ st.error(f"An error occurred: {e}")
251
+ st.error("Please check your API key and network connection. If the problem persists, try a different topic or try again later.")
252
+
253
+ # Quiz Display Logic
254
+ if st.session_state.quiz_data:
255
+ if not st.session_state.quiz_completed:
256
+ display_question()
257
+ else:
258
+ display_results()
259
+ elif topic and not st.session_state.quiz_data and not st.session_state.quiz_completed: # Message if topic entered but quiz not generated yet
260
+ st.info("Click 'Generate Quiz' to start the quiz.")
261
 
262
+ # --- Main App Flow Control ---
263
+ if 'logged_in' not in st.session_state:
264
+ st.session_state.logged_in = False
265
 
266
+ if not st.session_state.logged_in:
267
+ login_page()
268
+ else:
269
+ quiz_app()