| import streamlit as st |
| import os |
| from fpdf import FPDF |
| import uuid |
| import time |
|
|
| |
| 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) |
| |
| |
| pdf.cell(0, 10, txt="Magic Math Quiz!", ln=True, align="C") |
| pdf.ln(5) |
| |
| |
| pdf.image("assets/gt.png", x=(210 - 10) / 2, w=10, link="https://ghassem.com") |
| pdf.ln(10) |
|
|
| for i, entry in enumerate(st.session_state.questions): |
| |
| pdf.set_fill_color(0, 0, 0) |
| pdf.set_text_color(255, 255, 255) |
| 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) |
| pdf.set_font("Arial", size=12) |
| pdf.ln(5) |
| |
| |
| pdf.set_font("Arial", style='B', size=12) |
| pdf.multi_cell(0, 10, f"Q{i+1}: {entry['question']}") |
| pdf.ln(3) |
| |
| |
| pdf.set_font("Arial", size=10) |
| pdf.multi_cell(0, 10, f"Time Taken: {entry['time_taken']} seconds") |
| pdf.ln(3) |
| |
| |
| 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) |
| elif option == entry['selected']: |
| pdf.set_text_color(255, 0, 0) |
| else: |
| pdf.set_text_color(0, 0, 0) |
|
|
| pdf.multi_cell(0, 10, f"{options[j]}. {option}") |
| |
| pdf.set_text_color(0, 0, 0) |
| pdf.ln(5) |
| |
| |
| 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) |
|
|
| 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) |
| 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 |
| } |
| |
| 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']() |
| |
| question_data['answered'] = False |
| question_data['category'] = category_name |
| question_data['module'] = module_name |
| question_data['module_title'] = module['title'] |
| question_data['selected'] = None |
| question_data['time_taken'] = 0 |
| |
| 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 |
|
|
| |
| modules = load_modules() |
|
|
| |
| st.sidebar.markdown( |
| """ |
| <div style='background-color: white; padding: 10px; border-radius: 10px; text-align: center; color: black;'> |
| <h1 style='margin: 0;'>🪄 Magic Math Quiz!<sup>Beta</sup></h1> |
| <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 |
|
|
| |
| 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: |
| |
| st.markdown("<div style='margin-top: 25px;'></div>", unsafe_allow_html=True) |
| |
| 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) |
| st.session_state.selected_answer = None |
| st.session_state.button_label = "Submit/New" |
| st.session_state.start_time = time.time() |
| |
| 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 |
| |
| st.session_state.current_question = generate_new_question(selected_category, selected_module, selected_module_data) |
|
|
| current_question = st.session_state.current_question |
|
|
| |
| 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( |
| "", |
| 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") |
|
|
| |
| if submit_button: |
| if selected_answer is None: |
| st.warning("Please select an option before submitting.", icon="⚠️") |
| else: |
| |
| current_question['time_taken'] = int(time.time() - st.session_state.start_time) |
|
|
| |
| selected_answer_text = selected_answer.split(". ", 1)[1] |
| current_question['selected'] = selected_answer_text |
| current_question['answered'] = True |
| st.session_state.module_question_count[selected_module] += 1 |
|
|
| if selected_answer_text == current_question['correct_answer']: |
| st.session_state.correct_count += 1 |
| st.session_state.module_correct_count[selected_module] += 1 |
|
|
| |
| 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:red;'>{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) |
|
|
| |
| st.session_state.questions.append(current_question) |
| st.session_state.current_index = len(st.session_state.questions) |
|
|
| |
| st.session_state.pdf_data = generate_pdf_report() |
|
|
| |
| 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" |
| ) |
|
|
| |
| st.session_state.current_question = generate_new_question(selected_category, selected_module, selected_module_data) |
|
|