|
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) |
|
|
|
|
|
if blocks: |
|
first_block = blocks[0].strip() |
|
if not re.match(r'^\d+\.\s', first_block): |
|
|
|
blocks = blocks[1:] |
|
else: |
|
st.warning("Unexpected format: First block starts with a question number. Title might not be removed correctly.") |
|
|
|
|
|
|
|
for block in blocks: |
|
block = block.strip() |
|
if not block: |
|
continue |
|
|
|
lines = block.split('\n') |
|
|
|
question_text = re.sub(r'\*\*|\*', '', lines[0]).strip() |
|
|
|
options = {} |
|
for line in lines[1:]: |
|
line = line.strip() |
|
if re.match(r'^[A-D]\.\s', line): |
|
option_letter = line[0] |
|
option_text = line[2:].strip() |
|
options[option_letter] = option_text |
|
|
|
if question_text: |
|
questions.append({'question': question_text, 'options': options}) |
|
|
|
|
|
|
|
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. |
|
For each question, provide four plausible options labeled A, B, C, and D. |
|
Clearly indicate the correct answer for each question at the end in a separate section called "Answer Key". |
|
Format the quiz clearly 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.") |