math / app.py
Sina Media Lab
Updates
98ba964
import streamlit as st
import os
from fpdf import FPDF
import uuid
import time
# Initialize session state variables
if 'session_id' not in st.session_state:
st.session_state.session_id = str(uuid.uuid4())
if 'questions' not in st.session_state:
st.session_state.questions = []
if 'current_index' not in st.session_state:
st.session_state.current_index = 0
if 'current_module' not in st.session_state:
st.session_state.current_module = None
if 'correct_count' not in st.session_state:
st.session_state.correct_count = 0
if 'module_correct_count' not in st.session_state:
st.session_state.module_correct_count = {}
if 'module_question_count' not in st.session_state:
st.session_state.module_question_count = {}
if 'pdf_data' not in st.session_state:
st.session_state.pdf_data = None
if 'selected_answer' not in st.session_state:
st.session_state.selected_answer = None
if 'button_label' not in st.session_state:
st.session_state.button_label = "Submit/New"
if 'start_time' not in st.session_state:
st.session_state.start_time = time.time()
def reset_pdf_cache():
st.session_state.pdf_data = None
def generate_pdf_report():
pdf = FPDF()
pdf.add_page()
pdf.set_font("Arial", style='B', size=18)
# Title of the report with a clickable link
pdf.set_text_color(0, 0, 255) # Set text color to blue for the link
pdf.set_font("Arial", style='B', size=18)
pdf.cell(0, 10, txt="Magic Math Quiz!", ln=True, align="C", link="https://huggingface.co/spaces/tofighi/math")
pdf.ln(5)
# Load the image from the URL, scaled down and centered
pdf.image("assets/gt.png", x=(210 - 10) / 2, w=10, link="https://ghassem.com") # Centered horizontally and scaled down
pdf.ln(10)
for i, entry in enumerate(st.session_state.questions):
# Add Category > Module as a header
pdf.set_fill_color(0, 0, 0) # Black background
pdf.set_text_color(255, 255, 255) # White text
pdf.set_font("Arial", style='B', size=12)
pdf.cell(0, 10, f"{entry['category']} > {entry['module_title']}", ln=True, align="L", fill=True)
pdf.set_text_color(0, 0, 0) # Reset text color to black
pdf.set_font("Arial", size=12)
pdf.ln(5)
# Question number and text
pdf.set_font("Arial", style='B', size=12)
pdf.multi_cell(0, 10, f"Q{i+1}: {entry['question']}")
pdf.ln(3)
# Time taken
pdf.set_font("Arial", size=10)
pdf.multi_cell(0, 10, f"Time Taken: {entry['time_taken']} seconds")
pdf.ln(3)
# Options and correct/incorrect answer
pdf.set_font("Arial", size=10)
options = ['a', 'b', 'c', 'd']
for j, option in enumerate(entry['options']):
if option == entry['correct_answer']:
pdf.set_text_color(0, 128, 0) # Green for correct
elif option == entry['selected']:
pdf.set_text_color(255, 0, 0) # Red for incorrect
else:
pdf.set_text_color(0, 0, 0) # Default color
pdf.multi_cell(0, 10, f"{options[j]}. {option}")
pdf.set_text_color(0, 0, 0) # Reset color
pdf.ln(5)
# Explanation and Step-by-Step Solution
pdf.set_font("Arial", style='B', size=10)
pdf.multi_cell(0, 10, "Explanation:")
pdf.set_font("Arial", size=10)
pdf.multi_cell(0, 10, entry['explanation'])
pdf.ln(3)
pdf.set_font("Arial", style='B', size=10)
pdf.multi_cell(0, 10, "Step-by-Step Solution:")
pdf.set_font("Arial", size=10)
for step in entry['step_by_step_solution']:
pdf.multi_cell(0, 10, step)
pdf.ln(10) # Add space after each question
return pdf.output(dest='S').encode('latin1', 'replace')
def load_modules():
modules = {}
base_dir = "modules"
for category in os.listdir(base_dir):
category_dir = os.path.join(base_dir, category)
if os.path.isdir(category_dir):
config_path = os.path.join(category_dir, "config.py")
if os.path.exists(config_path):
config = {}
with open(config_path) as f:
exec(f.read(), config)
category_title = config.get("title", category.title().replace("_", " "))
order = config.get("order", 100) # Default order is 100 if not specified
else:
category_title = category.title().replace("_", " ")
order = 100
modules[category_title] = {"order": order, "modules": {}}
for filename in os.listdir(category_dir):
if filename.endswith(".py") and filename != "__init__.py" and filename != "config.py":
module_name = filename[:-3]
module = __import__(f"{category_dir.replace('/', '.')}.{module_name}", fromlist=[''])
modules[category_title]["modules"][module_name] = {
"title": getattr(module, "title", module_name.replace("_", " ").title()),
"description": getattr(module, "description", "No description available."),
"generate_question": module.generate_question # Access the generate_question function
}
# Sort categories by the order value
sorted_categories = sorted(modules.items(), key=lambda x: x[1]['order'])
return {category: data["modules"] for category, data in sorted_categories}
def generate_new_question(category_name, module_name, module):
question_data = module['generate_question']()
# Ensure 'answered' is initialized to False and add the 'module' and 'selected' keys
question_data['answered'] = False
question_data['category'] = category_name # Add the category name to the question data
question_data['module'] = module_name # Add the module name to the question data
question_data['module_title'] = module['title'] # Add the module title to the question data
question_data['selected'] = None # Initialize 'selected' to None
question_data['time_taken'] = 0 # Initialize time taken to 0
# Ensure there are exactly 4 options
if len(question_data['options']) != 4:
st.warning(f"Question in module '{module_name}' does not have 4 options. Found {len(question_data['options'])}.")
return question_data
# Load all modules dynamically
modules = load_modules()
# Streamlit sidebar
st.sidebar.markdown(
"""
<div style='background-color: lightgray; padding: 10px; border-radius: 10px; text-align: center; color: black;'>
<a href="https://huggingface.co/spaces/tofighi/math" target="_blank" style='text-decoration: none; color: black;'>
<h1 style='margin: 0;'>πŸͺ„ Magic Math Quiz!<sup>Beta</sup></h1>
</a>
<a href="https://ghassem.com" target="_blank">
<img src="https://huggingface.co/spaces/tofighi/math/resolve/main/assets/gt.png" alt="Logo" style="width:25%; margin-top: 10px;">
</a>
</div>
""", unsafe_allow_html=True)
st.sidebar.title("Quiz Categories")
selected_category = st.sidebar.selectbox("Choose a category:", list(modules.keys()))
if selected_category:
selected_module = st.sidebar.radio("Choose a module:", [modules[selected_category][module]["title"] for module in modules[selected_category]])
for module_name, module_data in modules[selected_category].items():
if module_data["title"] == selected_module:
selected_module_data = module_data
break
# Create a horizontal layout with the category title and the PDF download button
col1, col2 = st.columns([3, 1])
with col1:
st.markdown(
f"""
<div style="background-color: #333; padding: 10px; border-radius: 5px; margin-top: 20px;">
<span style='font-size: 18px; color: white;'>{selected_category} > {selected_module_data['title']}</span>
</div>
""",
unsafe_allow_html=True
)
with col2:
# Add some spacing above the button by placing it inside an empty container
st.markdown("<div style='margin-top: 25px;'></div>", unsafe_allow_html=True) # Increased margin by 5px
# Show PDF report button, initially disabled until a question is answered
pdf_disabled = len(st.session_state.questions) == 0
if st.session_state.pdf_data:
st.download_button(
label="Quiz Report",
data=st.session_state.pdf_data,
file_name="quiz_report.pdf",
mime="application/pdf",
disabled=False,
key="pdf_download_button"
)
if selected_module != st.session_state.current_module:
st.session_state.current_module = selected_module
st.session_state.current_index = len(st.session_state.questions) # Continue numbering from previous questions
st.session_state.selected_answer = None
st.session_state.button_label = "Submit/New"
st.session_state.start_time = time.time() # Start the timer for the new question
# Initialize question count and correct count if not already done
if selected_module not in st.session_state.module_question_count:
st.session_state.module_question_count[selected_module] = 0
if selected_module not in st.session_state.module_correct_count:
st.session_state.module_correct_count[selected_module] = 0
# Generate a new question without adding it to the answered list yet
st.session_state.current_question = generate_new_question(selected_category, selected_module, selected_module_data)
current_question = st.session_state.current_question
# Display the current question inside the options box
with st.form(key=f'question_form_{st.session_state.current_index}'):
options = ['a', 'b', 'c', 'd']
st.markdown(f"<b>Q{st.session_state.current_index + 1}: {current_question['question']}</b>", unsafe_allow_html=True)
selected_answer = st.radio(
"", # Empty label to put the question inside the options box
options=[f"{options[i]}. {opt}" for i, opt in enumerate(current_question['options'])],
key=f"question_{st.session_state.current_index}_options",
index=None,
)
submit_button = st.form_submit_button(label="Submit/New")
# Handle button state and answer submission
if submit_button:
if selected_answer is None:
st.warning("Please select an option before submitting.", icon="⚠️")
else:
# Calculate time taken to answer the question
current_question['time_taken'] = int(time.time() - st.session_state.start_time)
# Process the answer
selected_answer_text = selected_answer.split(". ", 1)[1] # Extract the text part after "a. ", "b. ", etc.
current_question['selected'] = selected_answer_text
current_question['answered'] = True
st.session_state.module_question_count[selected_module] += 1
# Check if the answer is correct
if selected_answer_text == current_question['correct_answer']:
st.session_state.correct_count += 1
st.session_state.module_correct_count[selected_module] += 1
feedback_text = "Correct! βœ…"
feedback_color = "green"
else:
feedback_text = "Incorrect ❌"
feedback_color = "red"
# Show correct/incorrect feedback, explanation, and step-by-step solution
col1, col2 = st.columns(2)
with col1:
for i, option in enumerate(current_question['options']):
option_text = f"{options[i]}. {option}"
if option == current_question['correct_answer']:
st.markdown(f"<span style='color:green;'>{option_text} βœ…</span>", unsafe_allow_html=True)
elif option == selected_answer_text:
st.markdown(f"<span style='color:{feedback_color};'>{option_text} ❌</span>", unsafe_allow_html=True)
else:
st.markdown(f"{option_text}", unsafe_allow_html=True)
with col2:
st.markdown(f"<span style='font-size: 14px;'><b>Explanation:</b> {current_question['explanation']}</span>", unsafe_allow_html=True)
st.markdown(f"<span style='font-size: 14px;'><b>Step-by-Step Solution:</b></span>", unsafe_allow_html=True)
for step in current_question['step_by_step_solution']:
st.markdown(f"<span style='font-size: 14px;'>{step}</span>", unsafe_allow_html=True)
# Add the question to the answered list only after submission
st.session_state.questions.append(current_question)
st.session_state.current_index = len(st.session_state.questions)
# Generate the PDF report data after each submission
st.session_state.pdf_data = generate_pdf_report()
# Refresh the PDF button state
st.download_button(
label="Quiz Report",
data=st.session_state.pdf_data,
file_name="quiz_report.pdf",
mime="application/pdf",
disabled=False,
key="pdf_download_button_updated"
)
# Generate a new question for the next round
st.session_state.current_question = generate_new_question(selected_category, selected_module, selected_module_data)