import streamlit as st import os from fpdf import FPDF import uuid import importlib # 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 'answered' not in st.session_state: st.session_state.answered = False def reset_pdf_cache(): st.session_state.pdf_data = None def generate_pdf_report(): pdf = FPDF() pdf.add_page() pdf.set_font("Arial", size=12) pdf.cell(200, 10, txt="Quiz Report", ln=True, align="C") pdf.ln(10) for module, data in st.session_state.module_question_count.items(): pdf.set_text_color(0, 0, 0) # Reset to default color pdf.set_font("Arial", size=12, style='B') pdf.cell(200, 10, txt=f"Module: {module}", ln=True, align="L") pdf.ln(5) for entry in st.session_state.questions: if entry['module'] == module: question, options, selected, correct, explanation, step_by_step_solution = ( entry['question'], entry['options'], entry['selected'] if entry['selected'] is not None else "Not Answered", entry['correct_answer'], entry['explanation'], entry['step_by_step_solution'] ) # Draw a border around the question and options with a light background pdf.set_draw_color(0, 0, 0) # Black border pdf.set_fill_color(240, 240, 240) # Light gray background for question pdf.set_line_width(0.3) # Question pdf.multi_cell(0, 10, f"Q: {question}", border=1, align='L', fill=True) pdf.ln(5) # Options with distinct background for option in options: if option == correct: pdf.set_text_color(0, 128, 0) # Green for correct pdf.set_fill_color(200, 255, 200) # Light green background pdf.multi_cell(0, 10, f"{option}", border=1, align='L', fill=True) elif option == selected: pdf.set_text_color(255, 0, 0) # Red for incorrect pdf.set_fill_color(255, 200, 200) # Light red background pdf.multi_cell(0, 10, f"{option}", border=1, align='L', fill=True) else: pdf.set_text_color(0, 0, 0) # Default color for others pdf.set_fill_color(240, 240, 240) # Light gray background pdf.multi_cell(0, 10, f" {option}", border=1, align='L', fill=True) pdf.ln(5) # Explanation with background pdf.set_text_color(0, 0, 0) # Reset to black pdf.set_fill_color(230, 230, 250) # Light blue background pdf.multi_cell(0, 10, f"Explanation: {explanation}", border=1, align='L', fill=True) pdf.ln(5) # Step-by-step solution with background pdf.set_fill_color(250, 235, 215) # Light tan background pdf.multi_cell(0, 10, "Step-by-Step Solution:", border=1, align='L', fill=True) for step in step_by_step_solution: pdf.multi_cell(0, 10, step, border=1, align='L', fill=True) pdf.ln(10) pdf.ln(10) # Add space after each module return pdf.output(dest='S').encode('latin1', 'replace') def load_modules(): modules = {} module_dir = "modules" for filename in os.listdir(module_dir): if filename.endswith(".py") and filename != "__init__.py": module_name = filename[:-3] # Dynamically import the module only when needed module = importlib.import_module(f"modules.{module_name}") modules[module_name] = { "title": getattr(module, "title", module_name), "description": getattr(module, "description", "No description available."), "generate_question": module.generate_question # Access the generate_question function } return modules def generate_new_question(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['module'] = module_name # Add the module name to the question data question_data['selected'] = None # Initialize 'selected' to None # 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 def navigate_question(direction): if direction == "prev" and st.session_state.current_index > 0: st.session_state.current_index -= 1 st.session_state.answered = True elif direction == "next" and st.session_state.current_index < len(st.session_state.questions) - 1: st.session_state.current_index += 1 st.session_state.answered = True # Load all modules dynamically modules = load_modules() # Streamlit interface st.sidebar.title("Quiz Modules") module_name = st.sidebar.radio("Choose a module:", [modules[module]["title"] for module in modules], index=0) selected_module = None for module in modules: if modules[module]["title"] == module_name: selected_module = module break if selected_module != st.session_state.current_module: st.session_state.current_module = selected_module st.session_state.current_index = 0 st.session_state.questions = [generate_new_question(selected_module, modules[selected_module])] st.session_state.module_question_count[selected_module] = 0 st.session_state.module_correct_count[selected_module] = 0 st.session_state.selected_answer = None st.session_state.answered = False # Load the current module's question current_question = st.session_state.questions[st.session_state.current_index] # Display module title and description st.title(modules[selected_module]["title"]) st.write(modules[selected_module]["description"]) # Navigation and PDF report buttons col1, col2, col3 = st.columns([1, 1, 2]) with col1: if st.button("⬅️ Prev", disabled=st.session_state.current_index == 0): navigate_question("prev") with col2: if st.button("➡️ Next", disabled=st.session_state.current_index >= len(st.session_state.questions) - 1): navigate_question("next") with col3: if len(st.session_state.questions) > 0: pdf = generate_pdf_report() st.session_state.pdf_data = pdf # Reset PDF cache st.download_button( label="Download PDF Report 📄", data=st.session_state.pdf_data, file_name="quiz_report.pdf", mime="application/pdf" ) # Web Page Presentation Styling def display_question_with_styles(question_data): st.markdown(f""" """, unsafe_allow_html=True) st.markdown(f"""
Q: {question_data['question']}
""", unsafe_allow_html=True) for option in question_data['options']: option_class = '' if option == question_data['correct_answer']: option_class = 'correct' elif option == question_data['selected']: option_class = 'incorrect' st.markdown(f"""
{option}
""", unsafe_allow_html=True) if question_data.get('answered', False): st.markdown(f"""
Explanation: {question_data['explanation']}
""", unsafe_allow_html=True) st.markdown(f"""
Step-by-Step Solution:
", unsafe_allow_html=True) # Display the current question with styles display_question_with_styles(current_question) # Disable the radio buttons if the question is already answered disabled_options = current_question['answered'] # Create the form for the question with st.form(key=f'question_form_{st.session_state.current_index}'): selected_answer = st.radio( "Choose an answer:", options=current_question['options'], index=current_question['options'].index(current_question['selected']) if current_question['answered'] else None, disabled=disabled_options, # Disable if the question has been answered key=f"question_{st.session_state.current_index}_options" ) submit_button = st.form_submit_button(label="Submit/Next") # Handle button state and answer submission if submit_button: if not current_question['answered']: if selected_answer is not None: # Process the answer current_question['selected'] = selected_answer current_question['answered'] = True st.session_state.module_question_count[selected_module] += 1 if selected_answer == current_question['correct_answer']: st.session_state.correct_count += 1 st.session_state.module_correct_count[selected_module] += 1 # Set answered to true to disable options st.session_state.questions[st.session_state.current_index]['answered'] = True st.session_state.selected_answer = selected_answer else: # If already answered, move to the next question new_question = generate_new_question(selected_module, modules[selected_module]) st.session_state.questions.append(new_question) st.session_state.current_index = len(st.session_state.questions) - 1 st.session_state.answered = False # Show correct/incorrect feedback after submission if current_question.get('answered', False): display_question_with_styles(current_question)