File size: 15,792 Bytes
7461958 1830afe 7461958 a263c13 ad2cf05 738bef1 7461958 985cb05 1830afe dc5787b ad2cf05 7461958 1830afe dc5787b 1830afe dc5787b 1830afe ad2cf05 1830afe ad2cf05 1830afe ad2cf05 738bef1 ad2cf05 56f04a7 ad2cf05 1830afe ad2cf05 1830afe ad2cf05 eccc273 ff40b9b ad2cf05 17b329c ad2cf05 3a94602 ad2cf05 2428246 b948b56 ad2cf05 b948b56 8c624e7 b948b56 34b6ece b948b56 ad2cf05 56f04a7 1661880 56f04a7 ad2cf05 56f04a7 7461958 ad2cf05 7461958 ad2cf05 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 |
import streamlit as st
import google.generativeai as genai
import openai
import os
import re # For improved parsing
st.set_page_config(initial_sidebar_state="collapsed")
# --- Login Page ---
def login_page():
# st.title("API Selection")
api_options = ["Gemini API", "OpenAI API"]
selected_api = st.selectbox("### Select API:", api_options)
api_key = st.text_input(f"Enter your {selected_api} Key:", type="password")
if selected_api == "Gemini API":
model_options = ["gemini-pro"] # Add more Gemini models if available
selected_model = st.selectbox("### Select Gemini Model:", model_options, index=0) # Default to first model
elif selected_api == "OpenAI API":
model_options = ["gpt-3.5-turbo", "gpt-4"] # Add more OpenAI models as needed
selected_model = st.selectbox("### Select OpenAI Model:", model_options, index=0) # Default to first model
else:
selected_model = None # Should not happen, but for safety
if st.button("Login"):
if not api_key:
st.error("Please enter your API key.")
else:
st.session_state.api_key = api_key
st.session_state.selected_api = selected_api
if selected_api == "Gemini API":
st.session_state.gemini_model = selected_model
st.session_state.openai_model = None # Clear OpenAI model if Gemini is selected
elif selected_api == "OpenAI API":
st.session_state.openai_model = selected_model
st.session_state.gemini_model = None # Clear Gemini model if OpenAI is selected
st.session_state.logged_in = True
st.success(f"Logged in with {selected_api} using model: {selected_model}!")
st.rerun() # Rerun to show the quiz app
# --- Quiz Application ---
def quiz_app():
st.title("QuizLM 🧠")
# Initialize session state for quiz data and progress (if not already initialized during login)
if 'quiz_data' not in st.session_state:
st.session_state.quiz_data = None
if 'current_question_index' not in st.session_state:
st.session_state.current_question_index = 0
if 'user_answers' not in st.session_state:
st.session_state.user_answers = []
if 'quiz_completed' not in st.session_state:
st.session_state.quiz_completed = False
if 'score' not in st.session_state:
st.session_state.score = 0
if 'quiz_started' not in st.session_state: # Track if quiz generation has started
st.session_state.quiz_started = False
# API Configuration based on selection - Moved inside quiz_app after login
if st.session_state.selected_api == "Gemini API":
try:
genai.configure(api_key=st.session_state.api_key)
model = genai.GenerativeModel(st.session_state.gemini_model)
except Exception as e:
st.error(f"Error configuring Gemini API: {e}")
st.error("Please check your API key and network connection.")
st.session_state.logged_in = False # Force logout if API config fails
del st.session_state.api_key
del st.session_state.selected_api
st.rerun() # Go back to login page
return # Exit quiz_app
elif st.session_state.selected_api == "OpenAI API":
try:
openai.api_key = st.session_state.api_key
model_name = st.session_state.openai_model # Model name already selected in login
except Exception as e:
st.error(f"Error configuring OpenAI API: {e}")
st.error("Please check your API key and network connection.")
st.session_state.logged_in = False # Force logout if API config fails
del st.session_state.api_key
del st.session_state.selected_api
st.rerun() # Go back to login page
return # Exit quiz_app
def parse_quiz_content(quiz_content):
"""Parses the quiz content and answer key into a structured format, handling title."""
questions = []
answer_key_dict = {}
quiz_parts = quiz_content.split("Answer Key:")
if len(quiz_parts) != 2:
st.warning("Could not reliably separate quiz questions and answer key. Parsing might be imperfect.")
return None, None
question_section = quiz_parts[0].strip()
answer_key_section = quiz_parts[1].strip()
question_lines = question_section.strip().split('\n')
current_question = None
for line in question_lines:
line = line.strip()
if not line:
continue # Skip empty lines
if re.match(r'^\d+\.\s', line): # Start of a new question
if current_question: # Save previous question if exists
questions.append(current_question)
current_question = {'options': {}} # Start new question
current_question['question'] = re.sub(r'\*\*|\*', '', line).strip() # Question text, remove bold
elif re.match(r'^[A-D]\)\s', line) or re.match(r'^[A-D]\.\s', line): # Option line (handle both ')' and '.' after letter)
if current_question:
option_letter = line[0]
option_text = line[2:].strip() # Remove "A) " or "A. "
current_question['options'][option_letter] = option_text
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.
pass # Ignore title lines within question section if any.
if current_question: # Append the last question
questions.append(current_question)
# --- Answer Key Parsing (No change needed) ---
answer_lines = answer_key_section.strip().split('\n')
for line in answer_lines:
line = line.strip()
match = re.match(r'(\d+)\.\s*([A-D])', line)
if match:
question_num = int(match.group(1)) - 1
correct_answer = match.group(2)
answer_key_dict[question_num] = correct_answer
# Basic validation
if not questions or not answer_key_dict:
st.error("Error parsing quiz content. Please try again or check the generated format.")
return None, None
if len(questions) != len(answer_key_dict):
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.")
# Combine parsed questions and answer key into quiz_data
quiz_data_list = []
for i, q_data in enumerate(questions):
correct_answer = answer_key_dict.get(i)
if correct_answer:
quiz_data_list.append({
'question': q_data['question'],
'options': q_data['options'],
'correct_answer': correct_answer
})
else:
st.warning(f"Could not find correct answer for question {i+1} in the answer key.")
return None, None
return quiz_data_list, answer_key_dict
def display_question():
"""Displays the current question and options for interactive quiz."""
if st.session_state.quiz_data is None:
st.error("Quiz data not loaded yet. Generate a quiz first.")
return
if st.session_state.current_question_index < len(st.session_state.quiz_data):
question_data = st.session_state.quiz_data[st.session_state.current_question_index]
question_number = st.session_state.current_question_index + 1
st.subheader(f"{question_data['question']}")
options_list = [f"{key}. {value}" for key, value in question_data['options'].items()]
user_choice = st.radio("Choose an answer:", options_list, key=f"q_{question_number}") # Unique key for radio buttons
if st.button("Submit Answer", key=f"submit_q_{question_number}"): # Unique key for submit button
selected_option_letter = user_choice.split('.')[0] # Extract A, B, C, or D
st.session_state.user_answers.append(selected_option_letter)
if st.session_state.current_question_index < len(st.session_state.quiz_data) - 1:
st.session_state.current_question_index += 1
else:
st.session_state.quiz_completed = True # Mark quiz as completed after last question
st.rerun()
def display_results():
"""Displays the quiz results after completion."""
if st.session_state.quiz_completed:
st.balloons()
st.markdown("### Quiz Results")
correct_count = 0
for i in range(len(st.session_state.quiz_data)):
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
correct_answer = st.session_state.quiz_data[i]['correct_answer']
if user_answer == correct_answer:
correct_count += 1
result_text = f"**Question {i+1}:** ✅ Correct! (Your answer: {user_answer}, Correct answer: {correct_answer})"
color = "green"
else:
result_text = f"**Question {i+1}:** ❌ Incorrect. (Your answer: {user_answer if user_answer else 'Not answered'}, Correct answer: {correct_answer})"
color = "red"
st.markdown(f"<font color='{color}'>{result_text}</font>", unsafe_allow_html=True)
st.divider()
percentage_correct = (correct_count / len(st.session_state.quiz_data)) * 100
if percentage_correct > 75:
result_color = "#9AD8E1"
else:
result_color = "#E098B7"
st.metric(label=f"**Final Score**", value=f"{correct_count} out of {len(st.session_state.quiz_data)}", delta=f"{percentage_correct:.2f}%", delta_color=result_color)
# st.markdown(f"### Final Score: {correct_count} out of {len(st.session_state.quiz_data)} correct ({percentage_correct:.2f}%)")
st.session_state.score = correct_count # Store score in session state
# User input for topic - Conditional placement
if not st.session_state.quiz_started:
topic = st.text_input("Enter the topic for your quiz:")
else:
topic = st.sidebar.text_input("Quiz Topic", value=st.session_state.get('quiz_topic', '')) # Move to sidebar after quiz starts, keep topic value
if topic and not st.session_state.quiz_started: # Only process topic and button if quiz hasn't started yet for this topic
if st.button("Generate Quiz"):
st.session_state.quiz_started = True # Set quiz started to true when generate button is clicked
st.session_state.quiz_topic = topic # Store topic in session state to keep it in sidebar input
with st.spinner(f"Generating quiz on '{topic}'..."):
try:
prompt = f"""
Generate a multiple-choice quiz on the topic of "{topic}".
The quiz should have 5 questions.
**Formatting Instructions:**
1. **Question Numbering:** Start each question with a number followed by a period (e.g., "1.").
2. **Question Text:** Immediately after the question number, write the question text.
3. **Options:**
* List four options for each question, labeled A, B, C, and D.
* Use the format: "A) Option text", "B) Option text", etc. (letter followed by a parenthesis, then a space, then the option text).
* Place each option on a new line directly below the question.
4. **Answer Key:**
* After all questions, create a separate section titled "**Answer Key:**".
* 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).
**Example of Desired Format:**
**Quiz Title (Optional, but good to have)**
1. Question 1 text?
A) Option A
B) Option B
C) Option C
D) Option D
2. Question 2 text?
A) Option A
B) Option B
C) Option C
D) Option D
... (and so on for 5 questions) ...
**Answer Key:**
1. C
2. A
3. B
4. D
5. A
Please generate the quiz in this exact format.
"""
if st.session_state.selected_api == "Gemini API":
response = model.generate_content(prompt)
quiz_content = response.text
elif st.session_state.selected_api == "OpenAI API":
response = openai.ChatCompletion.create(
model=st.session_state.openai_model,
messages=[{"role": "user", "content": prompt}]
)
quiz_content = response.choices[0].message.content
parsed_quiz_data, answer_key = parse_quiz_content(quiz_content)
if quiz_content: # Check if quiz_content was generated successfully (outer if)
if parsed_quiz_data: # Check if parsing was successful (inner if)
st.session_state.quiz_data = parsed_quiz_data
st.session_state.current_question_index = 0
st.session_state.user_answers = []
st.session_state.quiz_completed = False
st.session_state.score = 0
st.success(f"Quiz on '{topic}' generated successfully! Let's begin.")
else: # else associated with inner if parsed_quiz_data
st.error("Failed to parse quiz content. Please try generating again.")
st.session_state.quiz_data = None
else: # else associated with outer if quiz_content
st.error("Failed to generate quiz content. Please try again or check your API key.")
except Exception as e:
st.error(f"An error occurred: {e}")
st.error("Please check your API key and network connection. If the problem persists, try a different topic or try again later.")
st.rerun() # Rerun to move topic input to sidebar
# Quiz Display Logic
if st.session_state.quiz_data:
if not st.session_state.quiz_completed:
display_question()
else:
display_results()
elif topic and not st.session_state.quiz_data and not st.session_state.quiz_completed and not st.session_state.quiz_started: # Message if topic entered but quiz not generated yet and quiz not started
st.info("Click 'Generate Quiz' to start the quiz.")
# --- Main App Flow Control ---
if 'logged_in' not in st.session_state:
st.session_state.logged_in = False
if not st.session_state.logged_in:
login_page()
else:
quiz_app() |