|
import streamlit as st |
|
import google.generativeai as genai |
|
import os |
|
import re |
|
|
|
|
|
st.title("Interactive Multiple Choice Quiz Generator") |
|
st.markdown("Powered by Gemini API") |
|
|
|
|
|
gemini_api_key = st.text_input("Enter your Gemini API Key:", type="password") |
|
|
|
if not gemini_api_key: |
|
st.warning("Please enter your Gemini API key to generate a quiz.") |
|
st.stop() |
|
else: |
|
genai.configure(api_key=gemini_api_key) |
|
model = genai.GenerativeModel('gemini-pro') |
|
|
|
|
|
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) |
|
|
|
|
|
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 |
|
|
|
|
|
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() |
|
|
|
|
|
blocks = re.split(r'\n\n+', question_section) |
|
|
|
|
|
st.write("### Debugging: Question Blocks after Splitting:") |
|
st.write(blocks) |
|
|
|
|
|
if blocks: |
|
first_block = blocks[0].strip() |
|
if not re.search(r'Question\s+\d+:', first_block, re.IGNORECASE): |
|
blocks = blocks[1:] |
|
|
|
|
|
|
|
question_number = 1 |
|
|
|
for block in blocks: |
|
block = block.strip() |
|
if not block: |
|
continue |
|
|
|
lines = block.split('\n') |
|
question_text = "" |
|
options = {} |
|
parsing_question_text = True |
|
|
|
for line_index, line in enumerate(lines): |
|
line = line.strip() |
|
if not line: |
|
continue |
|
|
|
if parsing_question_text: |
|
if re.match(r'^Question\s+\d+:\s*(.+)', line, re.IGNORECASE): |
|
match = re.match(r'^Question\s+\d+:\s*(.+)', line, re.IGNORECASE) |
|
if match: |
|
question_text = match.group(1).strip() |
|
parsing_question_text = False |
|
else: |
|
question_text = "" |
|
parsing_question_text = False |
|
else: |
|
|
|
question_text = line |
|
parsing_question_text = False |
|
|
|
|
|
elif re.match(r'^[A-D]\)\s(.+)', line): |
|
match = re.match(r'^([A-D])\)\s(.+)', line) |
|
if match: |
|
option_letter = match.group(1) |
|
option_text = match.group(2).strip() |
|
options[option_letter] = option_text |
|
|
|
|
|
st.write(f"#### Debugging: Block {question_number} - Parsed Data:") |
|
st.write(f"- Question Text: '{question_text}'") |
|
st.write(f"- Options: {options}") |
|
|
|
|
|
if question_text: |
|
questions.append({'question': question_text, 'options': options}) |
|
else: |
|
st.warning(f"Warning: Could not parse question text for block (potential issue in question {question_number}). Block content:\n{block}") |
|
|
|
question_number += 1 |
|
|
|
|
|
|
|
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 |
|
|
|
|
|
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.") |
|
|
|
|
|
|
|
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.markdown(f"**Question {question_number}:** {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}") |
|
|
|
if st.button("Submit Answer", key=f"submit_q_{question_number}"): |
|
selected_option_letter = user_choice.split('.')[0] |
|
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 |
|
st.rerun() |
|
|
|
|
|
def display_results(): |
|
"""Displays the quiz results after completion.""" |
|
if st.session_state.quiz_completed: |
|
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 |
|
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) |
|
|
|
percentage_correct = (correct_count / len(st.session_state.quiz_data)) * 100 |
|
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 |
|
|
|
|
|
|
|
topic = st.text_input("Enter the topic for your quiz:") |
|
|
|
if topic: |
|
if st.button("Generate Quiz"): |
|
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. |
|
Label each question as "Question 1:", "Question 2:", etc. |
|
Provide four plausible options for each question, labeled A), B), C), and D). |
|
Clearly indicate the correct answer for each question at the end in a section called "Answer Key:". |
|
Format the quiz for easy reading. |
|
""" |
|
response = model.generate_content(prompt) |
|
quiz_content = response.text |
|
|
|
|
|
st.write("### Raw Quiz Content from Gemini:") |
|
st.code(quiz_content) |
|
|
|
parsed_quiz_data, answer_key = parse_quiz_content(quiz_content) |
|
|
|
st.write("### Parsed Quiz Data:") |
|
st.write(parsed_quiz_data) |
|
|
|
|
|
|
|
|
|
if quiz_content: |
|
if parsed_quiz_data: |
|
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: |
|
st.error("Failed to parse quiz content. Please try generating again.") |
|
st.session_state.quiz_data = None |
|
else: |
|
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.") |
|
|
|
|
|
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: |
|
st.info("Click 'Generate Quiz' to start the quiz.") |