Update app.py
Browse files
app.py
CHANGED
@@ -1,38 +1,3 @@
|
|
1 |
-
import streamlit as st
|
2 |
-
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."""
|
38 |
questions = []
|
@@ -43,26 +8,40 @@ def parse_quiz_content(quiz_content):
|
|
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]
|
47 |
-
answer_key_section = quiz_parts[1]
|
|
|
|
|
|
|
|
|
48 |
|
49 |
-
# Parse questions and options using regex (more robust)
|
50 |
-
question_blocks = re.split(r'\n(?=\d+\.)', question_section.strip()) # Split by newline followed by number and dot
|
51 |
for block in question_blocks:
|
52 |
-
|
|
|
53 |
continue # Skip empty blocks
|
54 |
-
|
55 |
-
|
|
|
|
|
|
|
56 |
options = {}
|
57 |
-
for line in lines[1:]:
|
58 |
line = line.strip()
|
59 |
-
if
|
|
|
|
|
|
|
|
|
60 |
option_letter = line[0]
|
61 |
option_text = line[2:].strip()
|
62 |
options[option_letter] = option_text
|
63 |
-
questions.append({'question': question_text, 'options': options})
|
64 |
|
65 |
-
|
|
|
|
|
|
|
|
|
|
|
66 |
answer_lines = answer_key_section.strip().split('\n')
|
67 |
for line in answer_lines:
|
68 |
line = line.strip()
|
@@ -72,15 +51,15 @@ def parse_quiz_content(quiz_content):
|
|
72 |
correct_answer = match.group(2)
|
73 |
answer_key_dict[question_num] = correct_answer
|
74 |
|
75 |
-
# Basic validation
|
76 |
if not questions or not answer_key_dict:
|
77 |
st.error("Error parsing quiz content. Please try again or check the generated format.")
|
78 |
return None, None
|
79 |
if len(questions) != len(answer_key_dict):
|
80 |
-
st.warning("Number of questions
|
81 |
|
82 |
|
83 |
-
# Combine parsed questions and answer key into quiz_data
|
84 |
quiz_data_list = []
|
85 |
for i, q_data in enumerate(questions):
|
86 |
correct_answer = answer_key_dict.get(i)
|
@@ -94,108 +73,4 @@ def parse_quiz_content(quiz_content):
|
|
94 |
st.warning(f"Could not find correct answer for question {i+1} in the answer key.")
|
95 |
return None, None # Inconsistent data, better to stop
|
96 |
|
97 |
-
return quiz_data_list, answer_key_dict
|
98 |
-
|
99 |
-
|
100 |
-
def display_question():
|
101 |
-
"""Displays the current question and options for interactive quiz."""
|
102 |
-
if st.session_state.quiz_data is None:
|
103 |
-
st.error("Quiz data not loaded yet. Generate a quiz first.")
|
104 |
-
return
|
105 |
-
|
106 |
-
if st.session_state.current_question_index < len(st.session_state.quiz_data):
|
107 |
-
question_data = st.session_state.quiz_data[st.session_state.current_question_index]
|
108 |
-
question_number = st.session_state.current_question_index + 1
|
109 |
-
st.markdown(f"**Question {question_number}:** {question_data['question']}")
|
110 |
-
|
111 |
-
options_list = [f"{key}. {value}" for key, value in question_data['options'].items()]
|
112 |
-
user_choice = st.radio("Choose an answer:", options_list, key=f"q_{question_number}") # Unique key for radio buttons
|
113 |
-
|
114 |
-
if st.button("Submit Answer", key=f"submit_q_{question_number}"): # Unique key for submit button
|
115 |
-
selected_option_letter = user_choice.split('.')[0] # Extract A, B, C, or D
|
116 |
-
st.session_state.user_answers.append(selected_option_letter)
|
117 |
-
|
118 |
-
if st.session_state.current_question_index < len(st.session_state.quiz_data) - 1:
|
119 |
-
st.session_state.current_question_index += 1
|
120 |
-
else:
|
121 |
-
st.session_state.quiz_completed = True # Mark quiz as completed after last question
|
122 |
-
st.rerun()
|
123 |
-
|
124 |
-
|
125 |
-
def display_results():
|
126 |
-
"""Displays the quiz results after completion."""
|
127 |
-
if st.session_state.quiz_completed:
|
128 |
-
st.markdown("### Quiz Results")
|
129 |
-
correct_count = 0
|
130 |
-
for i in range(len(st.session_state.quiz_data)):
|
131 |
-
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
|
132 |
-
correct_answer = st.session_state.quiz_data[i]['correct_answer']
|
133 |
-
if user_answer == correct_answer:
|
134 |
-
correct_count += 1
|
135 |
-
result_text = f"**Question {i+1}:** ✅ Correct! (Your answer: {user_answer}, Correct answer: {correct_answer})"
|
136 |
-
color = "green"
|
137 |
-
else:
|
138 |
-
result_text = f"**Question {i+1}:** ❌ Incorrect. (Your answer: {user_answer if user_answer else 'Not answered'}, Correct answer: {correct_answer})"
|
139 |
-
color = "red"
|
140 |
-
st.markdown(f"<font color='{color}'>{result_text}</font>", unsafe_allow_html=True)
|
141 |
-
|
142 |
-
percentage_correct = (correct_count / len(st.session_state.quiz_data)) * 100
|
143 |
-
st.markdown(f"### Final Score: {correct_count} out of {len(st.session_state.quiz_data)} correct ({percentage_correct:.2f}%)")
|
144 |
-
st.session_state.score = correct_count # Store score in session state
|
145 |
-
|
146 |
-
|
147 |
-
# User input for topic
|
148 |
-
topic = st.text_input("Enter the topic for your quiz:")
|
149 |
-
|
150 |
-
if topic:
|
151 |
-
if st.button("Generate Quiz"):
|
152 |
-
with st.spinner(f"Generating quiz on '{topic}'..."):
|
153 |
-
try:
|
154 |
-
prompt = f"""
|
155 |
-
Generate a multiple-choice quiz on the topic of "{topic}".
|
156 |
-
The quiz should have 5 questions.
|
157 |
-
For each question, provide four plausible options labeled A, B, C, and D.
|
158 |
-
Clearly indicate the correct answer for each question at the end in a separate section called "Answer Key".
|
159 |
-
Format the quiz clearly for easy reading.
|
160 |
-
"""
|
161 |
-
response = model.generate_content(prompt)
|
162 |
-
quiz_content = response.text
|
163 |
-
|
164 |
-
# --- DEBUGGING PRINTS ---
|
165 |
-
st.write("### Raw Quiz Content from Gemini:")
|
166 |
-
st.code(quiz_content) # Display raw content in a code block for readability
|
167 |
-
|
168 |
-
parsed_quiz_data, answer_key = parse_quiz_content(quiz_content)
|
169 |
-
|
170 |
-
st.write("### Parsed Quiz Data:")
|
171 |
-
st.write(parsed_quiz_data) # Display the parsed data structure
|
172 |
-
|
173 |
-
# --- END DEBUGGING PRINTS ---
|
174 |
-
|
175 |
-
|
176 |
-
if quiz_content: # Check if quiz_content was generated successfully (outer if)
|
177 |
-
if parsed_quiz_data: # Check if parsing was successful (inner if)
|
178 |
-
st.session_state.quiz_data = parsed_quiz_data
|
179 |
-
st.session_state.current_question_index = 0
|
180 |
-
st.session_state.user_answers = []
|
181 |
-
st.session_state.quiz_completed = False
|
182 |
-
st.session_state.score = 0
|
183 |
-
st.success(f"Quiz on '{topic}' generated successfully! Let's begin.")
|
184 |
-
else: # else associated with inner if parsed_quiz_data
|
185 |
-
st.error("Failed to parse quiz content. Please try generating again.")
|
186 |
-
st.session_state.quiz_data = None
|
187 |
-
else: # else associated with outer if quiz_content
|
188 |
-
st.error("Failed to generate quiz content. Please try again or check your API key.")
|
189 |
-
|
190 |
-
except Exception as e:
|
191 |
-
st.error(f"An error occurred: {e}")
|
192 |
-
st.error("Please check your API key and network connection. If the problem persists, try a different topic or try again later.")
|
193 |
-
|
194 |
-
# Quiz Display Logic
|
195 |
-
if st.session_state.quiz_data:
|
196 |
-
if not st.session_state.quiz_completed:
|
197 |
-
display_question()
|
198 |
-
else:
|
199 |
-
display_results()
|
200 |
-
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
|
201 |
-
st.info("Click 'Generate Quiz' to start the quiz.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
def parse_quiz_content(quiz_content):
|
2 |
"""Parses the quiz content and answer key into a structured format."""
|
3 |
questions = []
|
|
|
8 |
st.warning("Could not reliably separate quiz questions and answer key. Parsing might be imperfect.")
|
9 |
return None, None
|
10 |
|
11 |
+
question_section = quiz_parts[0].strip() # Strip whitespace from question section
|
12 |
+
answer_key_section = quiz_parts[1].strip() # Strip whitespace from answer key section
|
13 |
+
|
14 |
+
# --- Improved Question Block Splitting ---
|
15 |
+
# Split question section by lines starting with a number followed by a dot and a space (e.g., "1. ")
|
16 |
+
question_blocks = re.split(r'\n(?=\d+\.\s)', question_section)
|
17 |
|
|
|
|
|
18 |
for block in question_blocks:
|
19 |
+
block = block.strip() # Strip whitespace for each block
|
20 |
+
if not block:
|
21 |
continue # Skip empty blocks
|
22 |
+
|
23 |
+
# --- Improved Question and Options Parsing within each block ---
|
24 |
+
lines = block.split('\n')
|
25 |
+
question_text = lines[0].strip() # First line is the question
|
26 |
+
|
27 |
options = {}
|
28 |
+
for line in lines[1:]: # Start from the second line to parse options
|
29 |
line = line.strip()
|
30 |
+
if re.match(r'^[A-D]\)\s', line): # Match options starting with A), B), C), D) and a space
|
31 |
+
option_letter = line[0]
|
32 |
+
option_text = line[2:].strip() # Take text after the "letter) "
|
33 |
+
options[option_letter] = option_text
|
34 |
+
elif re.match(r'^[A-D]\.\s', line): # Also handle options starting with A., B., C., D. and a space (if Gemini uses this format sometimes)
|
35 |
option_letter = line[0]
|
36 |
option_text = line[2:].strip()
|
37 |
options[option_letter] = option_text
|
|
|
38 |
|
39 |
+
|
40 |
+
if question_text: # Only append if we have a question text (to avoid empty questions from title etc.)
|
41 |
+
questions.append({'question': question_text, 'options': options})
|
42 |
+
|
43 |
+
|
44 |
+
# --- Answer Key Parsing (No significant change needed, but strip whitespace) ---
|
45 |
answer_lines = answer_key_section.strip().split('\n')
|
46 |
for line in answer_lines:
|
47 |
line = line.strip()
|
|
|
51 |
correct_answer = match.group(2)
|
52 |
answer_key_dict[question_num] = correct_answer
|
53 |
|
54 |
+
# Basic validation (similar to before)
|
55 |
if not questions or not answer_key_dict:
|
56 |
st.error("Error parsing quiz content. Please try again or check the generated format.")
|
57 |
return None, None
|
58 |
if len(questions) != len(answer_key_dict):
|
59 |
+
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.")
|
60 |
|
61 |
|
62 |
+
# Combine parsed questions and answer key into quiz_data (similar to before)
|
63 |
quiz_data_list = []
|
64 |
for i, q_data in enumerate(questions):
|
65 |
correct_answer = answer_key_dict.get(i)
|
|
|
73 |
st.warning(f"Could not find correct answer for question {i+1} in the answer key.")
|
74 |
return None, None # Inconsistent data, better to stop
|
75 |
|
76 |
+
return quiz_data_list, answer_key_dict
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|