|
import streamlit as st |
|
import google.generativeai as genai |
|
import openai |
|
import os |
|
import re |
|
from streamlit_extras.metric_cards import style_metric_cards |
|
|
|
st.set_page_config(initial_sidebar_state="collapsed") |
|
|
|
|
|
def login_page(): |
|
|
|
|
|
|
|
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"] |
|
selected_model = st.selectbox("### Select Gemini Model:", model_options, index=0) |
|
elif selected_api == "OpenAI API": |
|
model_options = ["gpt-3.5-turbo", "gpt-4"] |
|
selected_model = st.selectbox("### Select OpenAI Model:", model_options, index=0) |
|
else: |
|
selected_model = None |
|
|
|
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 |
|
elif selected_api == "OpenAI API": |
|
st.session_state.openai_model = selected_model |
|
st.session_state.gemini_model = None |
|
st.session_state.logged_in = True |
|
st.success(f"Logged in with {selected_api} using model: {selected_model}!") |
|
st.rerun() |
|
|
|
|
|
def quiz_app(): |
|
st.title("QuizLM 🧠") |
|
|
|
|
|
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: |
|
st.session_state.quiz_started = False |
|
if 'quiz_topic' not in st.session_state: |
|
st.session_state.quiz_topic = "" |
|
if 'topic_from_sidebar' not in st.session_state: |
|
st.session_state.topic_from_sidebar = False |
|
|
|
|
|
|
|
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 |
|
del st.session_state.api_key |
|
del st.session_state.selected_api |
|
st.rerun() |
|
return |
|
elif st.session_state.selected_api == "OpenAI API": |
|
try: |
|
openai.api_key = st.session_state.api_key |
|
model_name = st.session_state.openai_model |
|
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 |
|
del st.session_state.api_key |
|
del st.session_state.selected_api |
|
st.rerun() |
|
return |
|
|
|
|
|
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 |
|
|
|
if re.match(r'^\d+\.\s', line): |
|
if current_question: |
|
questions.append(current_question) |
|
current_question = {'options': {}} |
|
current_question['question'] = re.sub(r'\*\*|\*', '', line).strip() |
|
elif re.match(r'^[A-D]\)\s', line) or re.match(r'^[A-D]\.\s', line): |
|
if current_question: |
|
option_letter = line[0] |
|
option_text = line[2:].strip() |
|
current_question['options'][option_letter] = option_text |
|
elif re.match(r'^\*\*.*?\*\*$', line) or re.match(r'^\*.*?\*$', line) : |
|
pass |
|
|
|
if current_question: |
|
questions.append(current_question) |
|
|
|
|
|
|
|
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.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}") |
|
|
|
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.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 |
|
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: |
|
border_color = "#9AD8E1" |
|
delta_text_color = "normal" |
|
else: |
|
border_color = "#E098B7" |
|
delta_text_color = "inverse" |
|
|
|
|
|
st.write( |
|
""" |
|
<style> |
|
[data-testid="stMetricDelta"] svg { |
|
display: none; |
|
} |
|
</style> |
|
""", |
|
unsafe_allow_html=True, |
|
) |
|
|
|
st.metric(label=f"**Final Score**", value=f"{correct_count} out of {len(st.session_state.quiz_data)}", delta=f"{percentage_correct:.0f}%", delta_color=delta_text_color) |
|
style_metric_cards(border_left_color=border_color, background_color="DEFAULT_BACKGROUND_COLOR") |
|
|
|
st.session_state.score = correct_count |
|
|
|
|
|
|
|
sidebar_topic = st.sidebar.text_input("New Quiz Topic", value=st.session_state.get('quiz_topic', '')) |
|
if st.sidebar.button("Generate New Quiz"): |
|
st.session_state.quiz_started = True |
|
st.session_state.quiz_topic = sidebar_topic |
|
st.session_state.topic_from_sidebar = True |
|
|
|
st.session_state.quiz_data = None |
|
st.session_state.current_question_index = 0 |
|
st.session_state.user_answers = [] |
|
st.session_state.quiz_completed = False |
|
st.session_state.score = 0 |
|
|
|
|
|
|
|
if not st.session_state.quiz_started: |
|
initial_topic = st.text_input("Enter the topic for your quiz:", value=st.session_state.get('quiz_topic', '') ) |
|
if st.button("Generate Quiz"): |
|
if initial_topic: |
|
st.session_state.quiz_started = True |
|
st.session_state.quiz_topic = initial_topic |
|
st.session_state.topic_from_sidebar = False |
|
|
|
st.session_state.quiz_data = None |
|
st.session_state.current_question_index = 0 |
|
st.session_state.user_answers = [] |
|
st.session_state.quiz_completed = False |
|
st.session_state.score = 0 |
|
else: |
|
st.warning("Please enter a topic to generate a quiz.") |
|
|
|
|
|
|
|
topic_to_generate = None |
|
if st.session_state.quiz_started and st.session_state.quiz_data is None: |
|
topic_to_generate = st.session_state.quiz_topic |
|
|
|
if topic_to_generate: |
|
with st.spinner(f"Generating quiz on '{topic_to_generate}'..."): |
|
try: |
|
prompt = f""" |
|
Generate a multiple-choice quiz on the topic of "{topic_to_generate}". |
|
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: |
|
if parsed_quiz_data: |
|
st.session_state.quiz_data = parsed_quiz_data |
|
st.success(f"Quiz on '{topic_to_generate}' 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.") |
|
st.rerun() |
|
|
|
|
|
|
|
if st.session_state.quiz_data: |
|
if not st.session_state.quiz_completed: |
|
display_question() |
|
else: |
|
display_results() |
|
elif not st.session_state.quiz_started and not st.session_state.quiz_data and not st.session_state.quiz_completed: |
|
st.info("Enter a topic in the main area and click 'Generate Quiz' to start, or use the sidebar to start a new quiz anytime.") |
|
|
|
|
|
|
|
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() |