|
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.set_text_color(0, 0, 255) |
|
pdf.cell(0, 10, txt="🪄 Magic Math Quiz!", ln=True, align="C", link="https://huggingface.co/spaces/tofighi/math") |
|
pdf.set_text_color(0, 0, 0) |
|
pdf.ln(5) |
|
|
|
|
|
pdf.image("assets/gt.png", x=(210 - 25) / 2, w=25, 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("_", " ")) |
|
category_description = config.get("description", "No description available.") |
|
order = config.get("order", 100) |
|
else: |
|
category_title = category.title().replace("_", " ") |
|
category_description = "No description available." |
|
order = 100 |
|
|
|
modules[category_title] = {"order": order, "description": category_description, "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;'> |
|
<a href="https://huggingface.co/spaces/tofighi/math" target="_blank" style="color: black; text-decoration: none;"> |
|
🪄 Magic Math Quiz!<sup>Beta</sup> |
|
</a> |
|
</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:50%; 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())) |
|
|
|
|
|
category_description = modules[selected_category]["description"] if selected_category and selected_category in modules else "No description available." |
|
|
|
if selected_category: |
|
module_options = [modules[selected_category][module]["title"] for module in modules[selected_category]] |
|
selected_module = st.sidebar.radio( |
|
"Choose a module:", |
|
module_options |
|
) |
|
|
|
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; color: white; padding: 10px; border-radius: 10px;"> |
|
<h2 style="margin: 0;">{selected_category} > {selected_module}</h2> |
|
<p>{selected_module_data['description']}</p> |
|
</div> |
|
""", unsafe_allow_html=True |
|
) |
|
with col2: |
|
if st.button("Download PDF Report", key="download_pdf"): |
|
st.session_state.pdf_data = generate_pdf_report() |
|
st.download_button( |
|
label="Download PDF Report", |
|
data=st.session_state.pdf_data, |
|
file_name=f"quiz_report_{st.session_state.session_id}.pdf", |
|
mime="application/pdf" |
|
) |
|
|
|
if selected_module_data: |
|
if st.button(st.session_state.button_label): |
|
if st.session_state.button_label == "Submit": |
|
if st.session_state.selected_answer is not None: |
|
question = st.session_state.questions[st.session_state.current_index] |
|
question['answered'] = True |
|
if st.session_state.selected_answer == question['correct_answer']: |
|
st.session_state.correct_count += 1 |
|
st.session_state.module_correct_count[selected_module] = st.session_state.module_correct_count.get(selected_module, 0) + 1 |
|
else: |
|
st.session_state.module_correct_count[selected_module] = st.session_state.module_correct_count.get(selected_module, 0) |
|
|
|
st.session_state.questions[st.session_state.current_index] = question |
|
st.session_state.current_index += 1 |
|
st.session_state.selected_answer = None |
|
st.session_state.button_label = "Next Question" |
|
st.session_state.pdf_data = None |
|
else: |
|
st.warning("Please select an answer before submitting.") |
|
elif st.session_state.button_label == "Next Question": |
|
if st.session_state.current_index < len(st.session_state.questions): |
|
question = st.session_state.questions[st.session_state.current_index] |
|
st.session_state.selected_answer = None |
|
st.session_state.button_label = "Submit" |
|
else: |
|
st.session_state.button_label = "Submit/New" |
|
st.session_state.current_index = 0 |
|
st.session_state.questions = [] |
|
st.session_state.correct_count = 0 |
|
st.session_state.module_correct_count = {} |
|
st.session_state.pdf_data = generate_pdf_report() |
|
|
|
st.experimental_rerun() |
|
|
|
if st.session_state.questions: |
|
question = st.session_state.questions[st.session_state.current_index] |
|
st.write(f"**Q{st.session_state.current_index + 1}:** {question['question']}") |
|
options = ['a', 'b', 'c', 'd'] |
|
for i, option in enumerate(question['options']): |
|
is_selected = st.session_state.selected_answer == options[i] |
|
option_text = f"{options[i]}. {option}" |
|
if question['answered']: |
|
if options[i] == question['correct_answer']: |
|
st.markdown(f"<p style='color: green;'>{option_text}</p>", unsafe_allow_html=True) |
|
elif is_selected: |
|
st.markdown(f"<p style='color: red;'>{option_text}</p>", unsafe_allow_html=True) |
|
else: |
|
st.markdown(f"<p>{option_text}</p>", unsafe_allow_html=True) |
|
else: |
|
if is_selected: |
|
st.markdown(f"<p style='color: blue;'>{option_text}</p>", unsafe_allow_html=True) |
|
else: |
|
st.markdown(f"<p>{option_text}</p>", unsafe_allow_html=True) |
|
|
|
if not question['answered']: |
|
if st.radio("Options", options, key=question['question'], index=options.index(st.session_state.selected_answer) if st.session_state.selected_answer else -1) == options[i]: |
|
st.session_state.selected_answer = options[i] |
|
else: |
|
st.write("No questions available.") |
|
else: |
|
st.write("Please select a category and module.") |
|
|
|
|
|
st.markdown( |
|
""" |
|
<div style='text-align: center; color: grey; font-size: 12px;'> |
|
<p>By Ghassem Tofighi</p> |
|
</div> |
|
""", unsafe_allow_html=True |
|
) |
|
|