import streamlit as st import os from groq import Groq import json import re from typing import List, Dict, Optional import time import plotly.express as px import plotly.graph_objects as go import pandas as pd from datetime import datetime, timedelta import streamlit.components.v1 as components import hashlib # Configure page st.set_page_config( page_title="🧮 MathGenius Academy", page_icon="🧮", layout="wide", initial_sidebar_state="expanded" ) # Custom CSS for enhanced UI def load_custom_css(): st.markdown(""" """, unsafe_allow_html=True) class MathOlympiadExam: def __init__(self): self.client = self._initialize_groq_client() self.grade_levels = { 1: {"name": "Grade 1 (Ages 6-7)", "emoji": "🌱", "color": "#FF6B6B"}, 2: {"name": "Grade 2 (Ages 7-8)", "emoji": "đŸŒŋ", "color": "#4ECDC4"}, 3: {"name": "Grade 3 (Ages 8-9)", "emoji": "🍃", "color": "#45B7D1"}, 4: {"name": "Grade 4 (Ages 9-10)", "emoji": "đŸŒŗ", "color": "#96CEB4"}, 5: {"name": "Grade 5 (Ages 10-11)", "emoji": "đŸŽ¯", "color": "#FECA57"}, 6: {"name": "Grade 6 (Ages 11-12)", "emoji": "🚀", "color": "#FF9FF3"}, 7: {"name": "Grade 7 (Ages 12-13)", "emoji": "⭐", "color": "#54A0FF"}, 8: {"name": "Grade 8 (Ages 13-14)", "emoji": "💎", "color": "#5F27CD"}, 9: {"name": "Grade 9 (Ages 14-15)", "emoji": "🏆", "color": "#00D2D3"}, 10: {"name": "Grade 10 (Ages 15-16)", "emoji": "👑", "color": "#FF6348"} } def _initialize_groq_client(self) -> Optional[Groq]: """Initialize Groq client with API key""" try: api_key = "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" if not api_key: st.error("âš ī¸ GROQ_API_KEY not found!") return None return Groq(api_key=api_key) except Exception as e: st.error(f"Failed to initialize Groq client: {e}") return None def generate_questions(self, grade: int, num_questions: int, difficulty: str = "medium") -> List[Dict]: """Generate unique math olympiad questions with STRICT rules against visual questions - FIXED""" if not self.client: return [] try: difficulty_map = { 1: "Basic counting 1-20, simple addition/subtraction within 20, comparing numbers, basic money problems", 2: "Addition/subtraction within 100, word problems about time/money, simple multiplication by 2,5,10", 3: "Multiplication tables to 12, basic division, simple fractions (1/2, 1/4), word problems with multiple steps", 4: "Multi-digit multiplication/division, equivalent fractions, decimals to tenths, perimeter calculations", 5: "Advanced arithmetic, basic algebra (find the missing number), percentages, area calculations, factors/multiples", 6: "Pre-algebra equations, ratios/proportions, basic statistics (mean/median), coordinate problems", 7: "Linear equations, integers, basic probability, algebraic expressions, geometric formulas", 8: "Systems of equations, exponents/roots, advanced probability, quadratic expressions, geometric theorems", 9: "Advanced algebra, trigonometry basics, sequences/series, complex word problems, mathematical proofs", 10: "Pre-calculus topics, advanced trigonometry, logarithms, complex problem solving, advanced geometry" } difficulty_modifiers = { "easy": "Focus on fundamental concepts with straightforward calculations", "medium": "Standard olympiad difficulty with moderate complexity requiring 2-3 steps", "hard": "Advanced multi-step problems requiring deep mathematical reasoning" } topics = difficulty_map.get(grade, "general math concepts") modifier = difficulty_modifiers.get(difficulty, "") # STRICT forbidden keywords - expanded list forbidden_keywords = [ "figure", "diagram", "shape", "block", "square", "triangle", "circle", "rectangle", "polygon", "pattern below", "picture", "image", "graph", "chart", "visual", "draw", "sketch", "plot", "look at", "observe", "see the", "shown", "displayed", "illustrated", "appears", "geometric shape", "arrange", "tessellation", "symmetry", "reflection", "rotation", "hexagon", "pentagon", "octagon", "parallelogram", "rhombus", "trapezoid", "coordinate plane", "grid", "lattice", "dot", "point on", "line segment", "angle", "vertex", "side", "face", "edge", "cube", "cylinder", "sphere", "shaded", "colored", "pattern", "sequence of shapes", "visual pattern" ] seen_hashes = getattr(st.session_state, 'question_hashes', set()) # ENHANCED prompt with VERY strict formatting requirements base_prompt = f""" Generate exactly {num_questions} UNIQUE Math Olympiad questions for Grade {grade}. 🚨 ABSOLUTE RESTRICTIONS - NEVER INCLUDE: ❌ Any visual elements (shapes, figures, diagrams, patterns, images) ❌ Questions requiring visual interpretation ❌ Geometric shape identification or counting ❌ Pattern completion with visual elements ❌ Any reference to "figure", "diagram", "shape", "pattern below" ❌ Coordinate plotting or graph reading ❌ Any visual arrangement problems ✅ ONLY ALLOWED - TEXT-BASED QUESTIONS: ✅ Pure arithmetic calculations and word problems ✅ Number sequences (like: 2, 4, 6, 8, ?) ✅ Logic puzzles with numbers/text only ✅ Real-world scenarios (money, time, travel, shopping) ✅ Algebraic word problems ✅ Statistical problems with given data ✅ Mathematical reasoning without visuals TOPICS: {topics} DIFFICULTY: {modifier} đŸŽ¯ CRITICAL: Answer must be EXACTLY one letter: A, B, C, or D (no explanations in answer line) FORMAT (EXACT - NO DEVIATIONS): **Question 1:** [Engaging word problem - NO VISUALS] A) [Option A with number/calculation] B) [Option B with number/calculation] C) [Option C with number/calculation] D) [Option D with number/calculation] **Answer:** A **Explanation:** [Step-by-step solution] **Hint:** [Helpful tip for solving] **Question 2:** [Next question...] A) [Option A] B) [Option B] C) [Option C] D) [Option D] **Answer:** C **Explanation:** [Step-by-step solution] **Hint:** [Helpful tip for solving] [Continue for all {num_questions} questions] đŸ”Ĩ IMPORTANT FORMATTING RULES: - Answer line must contain ONLY the letter (A, B, C, or D) - No parentheses, explanations, or additional text in the answer - Each option must start with the letter followed by closing parenthesis - Keep questions practical and engaging with real scenarios MAKE QUESTIONS PRACTICAL AND ENGAGING - Use real scenarios students can relate to! """ max_attempts = 3 all_questions = [] for attempt in range(max_attempts): try: response = self.client.chat.completions.create( model="openai/gpt-oss-120b", # More reliable model messages=[ {"role": "system", "content": "You are an expert Math Olympiad question creator. You MUST create only text-based word problems without any visual elements. The answer must be EXACTLY one letter: A, B, C, or D with no additional text or explanations."}, {"role": "user", "content": base_prompt} ], temperature=0.7, # Balanced creativity max_tokens=4000 ) new_questions = self._parse_questions_enhanced(response.choices[0].message.content) # STRICT filtering valid_questions = [] for q in new_questions: q_text = q['question_text'].lower() # Check for forbidden keywords if any(forbidden in q_text for forbidden in forbidden_keywords): continue # Additional checks for visual elements if any(word in q_text for word in ['draw', 'sketch', 'plot', 'shown', 'displayed', 'appears']): continue # Check uniqueness q_hash = self._get_question_hash(q['question_text'] + str(q['options'])) if q_hash not in seen_hashes: q['hash'] = q_hash valid_questions.append(q) seen_hashes.add(q_hash) all_questions.extend(valid_questions) if len(all_questions) >= num_questions: break # Add retry hint with more specific guidance base_prompt += f"\n\nNEED MORE QUESTIONS: Generated {len(all_questions)} so far. Create MORE PRACTICAL WORD PROBLEMS about real-life scenarios (shopping, travel, sports, cooking, etc.). REMEMBER: Answer must be EXACTLY one letter (A, B, C, or D)." except Exception as e: st.error(f"Error in attempt {attempt + 1}: {e}") continue st.session_state.question_hashes = seen_hashes final_questions = all_questions[:num_questions] if len(final_questions) < num_questions: st.warning(f"âš ī¸ Generated {len(final_questions)} unique valid questions (requested {num_questions}). Some were filtered out for containing visual elements.") return final_questions except Exception as e: st.error(f"Failed to generate questions: {e}") return [] def _get_question_hash(self, question_text: str) -> str: """Generate hash for question to track uniqueness""" # Remove numbers and normalize text for better uniqueness detection normalized = re.sub(r'\d+', 'X', question_text.lower().strip()) normalized = re.sub(r'[^\w\s]', '', normalized) # Remove punctuation return hashlib.md5(normalized.encode()).hexdigest() def _parse_questions_enhanced(self, content: str) -> List[Dict]: """Enhanced question parsing with better error handling and validation""" questions = [] # Split by question markers question_blocks = re.split(r'\*\*Question \d+:\*\*', content) if len(question_blocks) < 2: # Try alternative splitting question_blocks = re.split(r'Question \d+:', content) for i, block in enumerate(question_blocks[1:], 1): try: lines = [line.strip() for line in block.strip().split('\n') if line.strip()] if len(lines) < 6: # Need at least question + 4 options + answer continue question_text = lines[0].strip() if not question_text: continue # Parse options - FIXED: Create both dictionary and list properly options_dict = {} options_list = [] correct_answer = None explanation = "" hint = "" current_section = "options" for line in lines[1:]: line = line.strip() # Detect sections if line.startswith('**Answer:**') or line.startswith('**Correct Answer:**'): current_section = "answer" answer_part = line.replace('**Answer:**', '').replace('**Correct Answer:**', '').strip() if answer_part: # Extract only the letter from the answer (A, B, C, or D) answer_match = re.search(r'[ABCD]', answer_part.upper()) if answer_match: correct_answer = answer_match.group() continue elif line.startswith('**Explanation:**'): current_section = "explanation" exp_part = line.replace('**Explanation:**', '').strip() if exp_part: explanation = exp_part continue elif line.startswith('**Hint:**'): current_section = "hint" hint_part = line.replace('**Hint:**', '').strip() if hint_part: hint = hint_part continue # Process based on current section if current_section == "options" and line.startswith(('A)', 'B)', 'C)', 'D)')): option_key = line[0] # A, B, C, or D option_text = line[2:].strip() # Remove "A) " options_dict[option_key] = option_text options_list.append(line) # Keep full format "A) text" elif current_section == "answer": # Try to extract single letter answer if line.strip() in ['A', 'B', 'C', 'D']: correct_answer = line.strip() elif not correct_answer: # Fallback: extract first letter found answer_match = re.search(r'[ABCD]', line.upper()) if answer_match: correct_answer = answer_match.group() elif current_section == "explanation" and line: if explanation: explanation += " " + line else: explanation = line elif current_section == "hint" and line: if hint: hint += " " + line else: hint = line # Validate question - ensure we have exactly 4 options and a valid answer if len(options_dict) == 4 and correct_answer and correct_answer in options_dict: questions.append({ 'question_number': i, 'question_text': question_text, 'options': options_list, # List format for compatibility: ['A) text', 'B) text', ...] 'options_dict': options_dict, # Dictionary format: {'A': 'text', 'B': 'text', ...} 'correct_answer': correct_answer, 'explanation': explanation or "Work through the problem step by step.", 'hint': hint or "Read the question carefully and identify what you need to find.", 'user_answer': None, 'time_taken': 0, 'difficulty': self._estimate_difficulty(question_text) }) else: st.warning(f"Question {i} validation failed: options={len(options_dict)}, answer='{correct_answer}', keys={list(options_dict.keys())}") except Exception as e: st.warning(f"Error parsing question {i}: {e}") continue return questions def _estimate_difficulty(self, question_text: str) -> str: """Estimate question difficulty based on content""" hard_keywords = ['system', 'equation', 'quadratic', 'prove', 'theorem', 'complex', 'advanced'] easy_keywords = ['add', 'subtract', 'simple', 'basic', 'find the', 'what is', 'how many'] text_lower = question_text.lower() if any(keyword in text_lower for keyword in hard_keywords): return "Hard" elif any(keyword in text_lower for keyword in easy_keywords): return "Easy" else: return "Medium" # Initialize session state def initialize_session_state(): if 'questions' not in st.session_state: st.session_state.questions = [] if 'exam_completed' not in st.session_state: st.session_state.exam_completed = False if 'grade_selected' not in st.session_state: st.session_state.grade_selected = None if 'exam_history' not in st.session_state: st.session_state.exam_history = [] if 'current_question' not in st.session_state: st.session_state.current_question = 0 if 'exam_mode' not in st.session_state: st.session_state.exam_mode = 'practice' if 'start_time' not in st.session_state: st.session_state.start_time = None if 'user_profile' not in st.session_state: st.session_state.user_profile = { 'name': 'Math Explorer', 'total_exams': 0, 'total_questions': 0, 'correct_answers': 0, 'favorite_grade': 5, 'badges': [], 'streak': 0 } if 'question_hashes' not in st.session_state: st.session_state.question_hashes = set() @st.cache_resource def get_exam_system(): return MathOlympiadExam() def render_hero_section(): """Render the hero section with animations""" st.markdown("""
🧮 MathGenius Academy
Master Mathematics Through Interactive Olympiad Training
đŸŽ¯ Practical Problems 📊 Progress Tracking 🏆 Achievement System
""", unsafe_allow_html = True) def render_timer_display(): """Render enhanced timer display with better positioning""" if st.session_state.exam_mode == 'timed' and st.session_state.start_time: total_time = len(st.session_state.questions) * 60 # 1 minute per question elapsed = time.time() - st.session_state.start_time remaining = max(0, total_time - elapsed) mins, secs = divmod(int(remaining), 60) # Add warning class if time is running low warning_class = "timer-warning" if remaining <= 60 else "" timer_html = f"""
⏰
{mins:02d}:{secs:02d}
Time Remaining
""" st.markdown(timer_html, unsafe_allow_html=True) # Auto-submit when time is up if remaining <= 0: st.error("⏰ Time's up! Auto-submitting your exam...") st.session_state.exam_completed = True time.sleep(1) st.rerun() # Show warning when time is low elif remaining <= 60: st.warning(f"âš ī¸ Only {int(remaining)} seconds left!") def render_dashboard(): """Render user dashboard with statistics""" profile = st.session_state.user_profile st.markdown("### 📊 Your Learning Dashboard") col1, col2, col3, col4 = st.columns(4) with col1: st.markdown(f"""
{profile['total_exams']}
Exams Taken
""", unsafe_allow_html=True) with col2: percentage = (profile['correct_answers'] / max(profile['total_questions'], 1)) * 100 st.markdown(f"""
{percentage:.1f}%
percentage
""", unsafe_allow_html=True) with col3: st.markdown(f"""
{profile['streak']}
Day Streak
""", unsafe_allow_html=True) with col4: st.markdown(f"""
{len(profile['badges'])}
Badges Earned
""", unsafe_allow_html=True) # Progress Chart if st.session_state.exam_history: df = pd.DataFrame(st.session_state.exam_history) col1, col2 = st.columns(2) with col1: st.markdown("#### 📈 Performance Trend") fig = px.line(df, x='date', y='percentage', title='Your Progress Over Time', color_discrete_sequence=['#667eea']) fig.update_layout( plot_bgcolor='rgba(0,0,0,0)', paper_bgcolor='rgba(0,0,0,0)', ) st.plotly_chart(fig, use_container_width=True) def render_question_card(question, question_index, total_questions): """Render an individual question with enhanced UI - FIXED""" progress = ((question_index + 1) / total_questions) * 100 st.markdown(f"""
Question {question_index + 1} of {total_questions}
""", unsafe_allow_html=True) st.markdown(f"""

Question {question['question_number']}

{question.get('difficulty', 'Medium')} Level
{question['question_text']}
""", unsafe_allow_html=True) # FIXED: Handle both list and dictionary options formats if isinstance(question['options'], list): # List format: ['A) text', 'B) text', ...] option_keys = [] option_labels = [] for opt in question['options']: if ')' in opt: key, label = opt.split(')', 1) option_keys.append(key.strip()) option_labels.append(label.strip()) # Create format function for radio buttons def format_option(key): try: idx = option_keys.index(key) return f"{key}) {option_labels[idx]}" except (ValueError, IndexError): return f"{key}) Option not found" selected = st.radio( "Choose your answer:", options=option_keys, format_func=format_option, key=f"q_{question_index}", index=None ) elif isinstance(question['options'], dict): # Dictionary format: {'A': 'text', 'B': 'text', ...} option_keys = list(question['options'].keys()) def format_option_dict(key): return f"{key}) {question['options'].get(key, 'Option not found')}" selected = st.radio( "Choose your answer:", options=option_keys, format_func=format_option_dict, key=f"q_{question_index}", index=None ) else: st.error("Invalid options format in question data") return None # Hint system if st.button(f"💡 Need a hint?", key=f"hint_{question_index}"): if question.get('hint'): st.info(f"💡 **Hint:** {question['hint']}") else: st.info("💡 **Hint:** Try breaking down the problem step by step!") return selected def render_exam_interface(): """Render the main exam interface with improved timer - FIXED""" questions = st.session_state.questions total_questions = len(questions) # Display timer at the top for timed mode render_timer_display() # Question navigation if total_questions > 1: st.markdown("#### 📍 Question Navigation") cols = st.columns(min(10, total_questions)) for i in range(total_questions): with cols[i % 10]: status = "✅" if questions[i]['user_answer'] else "⭕" if st.button(f"{status} Q{i+1}", key=f"nav_{i}"): st.session_state.current_question = i # Current question current_q = st.session_state.current_question if current_q < total_questions: # FIXED: Call render_question_card as a standalone function selected = render_question_card(questions[current_q], current_q, total_questions) questions[current_q]['user_answer'] = selected # Navigation buttons col1, col2, col3 = st.columns([1, 2, 1]) with col1: if current_q > 0: if st.button("âŦ…ī¸ Previous", key="prev_btn"): st.session_state.current_question -= 1 st.rerun() with col3: if current_q < total_questions - 1: if st.button("Next âžĄī¸", key="next_btn"): st.session_state.current_question += 1 st.rerun() else: if st.button("đŸŽ¯ Submit Exam", key="submit_btn", type="primary"): unanswered = [i+1 for i, q in enumerate(questions) if not q['user_answer']] if unanswered: st.error(f"âš ī¸ Please answer all questions. Missing: {', '.join(map(str, unanswered))}") else: st.session_state.exam_completed = True st.rerun() def calculate_badges(score, total, grade): """Calculate badges earned based on performance""" percentage = (score / total) * 100 badges = [] if percentage == 100: badges.append("🏆 Perfect Score") elif percentage >= 90: badges.append("⭐ Excellence") elif percentage >= 80: badges.append("đŸŽ¯ High Achiever") elif percentage >= 70: badges.append("📚 Good Progress") if grade >= 8: badges.append("🚀 Advanced Level") return badges def render_results_section(): """Render enhanced results with animations and insights""" st.markdown("---") st.markdown("### 🎉 Exam Results") questions = st.session_state.questions correct_count = sum(1 for q in questions if q['user_answer'] == q['correct_answer']) total_questions = len(questions) percentage = (correct_count / total_questions) * 100 # Update user profile profile = st.session_state.user_profile profile['total_exams'] += 1 profile['total_questions'] += total_questions profile['correct_answers'] += correct_count # Add to history st.session_state.exam_history.append({ 'date': datetime.now(), 'grade': st.session_state.grade_selected, 'score': correct_count, 'total': total_questions, 'percentage': percentage }) # Calculate badges new_badges = calculate_badges(correct_count, total_questions, st.session_state.grade_selected) profile['badges'].extend(new_badges) # Animated score reveal col1, col2, col3 = st.columns(3) with col1: st.markdown(f"""
{correct_count}/{total_questions}
Final Score
""", unsafe_allow_html=True) with col2: st.markdown(f"""
{percentage:.1f}%
percentage
""", unsafe_allow_html=True) with col3: if percentage >= 90: grade_emoji = "🏆" grade_text = "Excellent!" elif percentage >= 80: grade_emoji = "⭐" grade_text = "Great Job!" elif percentage >= 70: grade_emoji = "👍" grade_text = "Good Work!" else: grade_emoji = "📚" grade_text = "Keep Practicing!" st.markdown(f"""
{grade_emoji}
{grade_text}
""", unsafe_allow_html=True) # New badges notification if new_badges: st.success(f"🎉 New badges earned: {', '.join(new_badges)}") # Detailed question review st.markdown("#### 📝 Question Review") for i, question in enumerate(questions): is_correct = question['user_answer'] == question['correct_answer'] with st.expander(f"Question {i+1} - {'✅ Correct' if is_correct else '❌ Incorrect'} ({question.get('difficulty', 'Medium')} Level)"): st.markdown(f"**Question:** {question['question_text']}") col1, col2 = st.columns(2) with col1: st.markdown(f"**Your Answer:** {question['user_answer']}") with col2: st.markdown(f"**Correct Answer:** {question['correct_answer']}") if question.get('explanation'): st.markdown(f"**Explanation:** {question['explanation']}") if not is_correct and question.get('hint'): st.info(f"💡 **Hint for next time:** {question['hint']}") # Performance insights with bullet points exam_system = get_exam_system() if exam_system.client: st.markdown("#### 🧠 AI Performance Analysis") with st.spinner("Generating personalized insights..."): summary = generate_enhanced_summary( st.session_state.grade_selected, questions, correct_count, total_questions, exam_system ) st.markdown(f"""
{summary}
""", unsafe_allow_html=True) # Show completion message st.markdown("#### 🎓 Exam Complete!") st.info("Great job completing the exam! Use the sidebar to generate a new exam or check out the Dashboard tab to view your progress.") def generate_enhanced_summary(grade, questions, score, total, exam_system): """Generate enhanced AI-powered performance summary in bullet points""" try: # Analyze performance patterns correct_questions = [q for q in questions if q['user_answer'] == q['correct_answer']] incorrect_questions = [q for q in questions if q['user_answer'] != q['correct_answer']] difficulty_analysis = {} for q in questions: diff = q.get('difficulty', 'Medium') if diff not in difficulty_analysis: difficulty_analysis[diff] = {'correct': 0, 'total': 0} difficulty_analysis[diff]['total'] += 1 if q['user_answer'] == q['correct_answer']: difficulty_analysis[diff]['correct'] += 1 percentage = (score / total * 100) if total > 0 else 0 prompt = f"""Analyze this Math Olympiad performance for a Grade {grade} student: PERFORMANCE SUMMARY: - Score: {score}/{total} ({percentage:.1f}%) - Difficulty Breakdown: {difficulty_analysis} SAMPLE INCORRECT QUESTIONS: {chr(10).join([f"- {q['question_text'][:100]}..." for q in incorrect_questions[:2]])} Please provide a summary in BULLET POINTS with emojis using this exact format: đŸŽ¯ **Strengths:** â€ĸ [strength 1 with emoji] â€ĸ [strength 2 with emoji] 📈 **Areas to Improve:** â€ĸ [area to improve 1 with emoji] â€ĸ [area to improve 2 with emoji] 💡 **Quick Tips:** â€ĸ [tip 1 with emoji] â€ĸ [tip 2 with emoji] 🌟 **Motivation:** â€ĸ [encouraging message with emoji] Use Grade {grade} appropriate language and keep each bullet point short and encouraging. Include relevant emojis for each point.""" response = exam_system.client.chat.completions.create( model="openai/gpt-oss-120b", messages=[ {"role": "system", "content": "You are an expert Math Olympiad mentor providing detailed, encouraging feedback in bullet points with emojis."}, {"role": "user", "content": prompt} ], temperature=0, max_tokens=1000 ) return response.choices[0].message.content except Exception as e: return f""" đŸŽ¯ **Strengths:** â€ĸ ✨ You completed {score} out of {total} questions correctly ({percentage:.1f}%) â€ĸ đŸ’Ē You showed good problem-solving skills 📈 **Areas to Improve:** â€ĸ 📚 Review the questions you missed for better understanding â€ĸ ⏰ Work on time management if needed 💡 **Quick Tips:** â€ĸ 🔍 Read each question carefully before answering â€ĸ 💭 Use hints when you're unsure 🌟 **Motivation:** â€ĸ 🎉 Great effort! Keep practicing and you'll improve even more! """ def reset_exam(): """Reset exam state for new attempt""" st.session_state.questions = [] st.session_state.exam_completed = False st.session_state.current_question = 0 st.session_state.start_time = None st.rerun() def render_sidebar(): """Enhanced sidebar with user profile and settings""" exam_system = get_exam_system() with st.sidebar: # User profile section st.markdown(f"""
👋
{st.session_state.user_profile['name']}
Level {st.session_state.user_profile['favorite_grade']} Explorer
""", unsafe_allow_html=True) st.markdown("### âš™ī¸ Exam Configuration") # Grade selection with enhanced UI grade_options = list(exam_system.grade_levels.keys()) grade_labels = [f"{exam_system.grade_levels[g]['emoji']} {exam_system.grade_levels[g]['name']}" for g in grade_options] grade = st.selectbox( "đŸŽ¯ Select Grade Level:", options=grade_options, format_func=lambda x: f"{exam_system.grade_levels[x]['emoji']} {exam_system.grade_levels[x]['name']}", key="grade_selector", help="Choose your current grade level for appropriate difficulty" ) # Exam mode exam_mode = st.radio( "📝 Exam Mode:", ["practice", "timed", "challenge"], format_func=lambda x: { "practice": "đŸŽ¯ Practice Mode", "timed": "⏰ Timed Mode (1 min/question)", "challenge": "đŸ”Ĩ Challenge Mode" }[x], help="Practice: No time limit | Timed: 1 minute per question | Challenge: Extra hard questions" ) st.session_state.exam_mode = exam_mode # Number of questions if exam_mode == "challenge": num_questions = st.slider("📊 Questions:", 5, 20, 10, help="Challenge mode: 5-20 questions") else: num_questions = st.slider("📊 Questions:", 3, 15, 5, help="Choose number of questions") # Show timer info for timed mode if exam_mode == "timed": st.info(f"⏰ Total time: {num_questions} minutes ({num_questions} questions × 1 min each)") # Difficulty level difficulty = st.select_slider( "⚡ Difficulty:", options=["easy", "medium", "hard"], value="medium", format_func=lambda x: { "easy": "đŸŸĸ Easy", "medium": "🟡 Medium", "hard": "🔴 Hard" }[x] ) # Generate exam button if st.button("🚀 Generate New Exam", type="primary", use_container_width=True): if not exam_system.client: st.error("đŸšĢ API not available!") else: with st.spinner(f"🎭 Creating your {exam_mode} exam with unique questions..."): # Add loading animation progress_bar = st.progress(0) for i in range(100): time.sleep(0.01) progress_bar.progress(i + 1) challenge_difficulty = "hard" if exam_mode == "challenge" else difficulty questions = exam_system.generate_questions(grade, num_questions, challenge_difficulty) if questions: st.session_state.questions = questions st.session_state.exam_completed = False st.session_state.grade_selected = grade st.session_state.current_question = 0 st.session_state.start_time = time.time() if exam_mode == "timed" else None st.success(f"✨ Generated {len(st.session_state.questions)} unique questions!") st.balloons() else: st.error("❌ Failed to generate exam. Try again!") st.markdown("---") # Quick stats profile = st.session_state.user_profile st.markdown("### 📈 Quick Stats") st.metric("đŸŽ¯ Total Exams", profile['total_exams']) if profile['total_questions'] > 0: percentage = (profile['correct_answers'] / profile['total_questions']) * 100 st.metric("đŸŽĒ percentage", f"{percentage:.1f}%") st.metric("đŸ”Ĩ Streak", f"{profile['streak']} days") # Recent badges if profile['badges']: st.markdown("### 🏆 Recent Badges") for badge in profile['badges'][-3:]: st.markdown(f"- {badge}") st.markdown("---") # Settings with st.expander("âš™ī¸ Settings"): new_name = st.text_input("Your Name:", value=profile['name']) if new_name != profile['name']: profile['name'] = new_name st.success("Name updated!") if st.button("đŸ—‘ī¸ Reset Progress"): if st.checkbox("I'm sure"): initialize_session_state() st.success("Progress reset!") st.rerun() def render_welcome_screen(): """Render welcome screen for new users""" # Main welcome container st.markdown("""
🌟

Welcome to MathGenius Academy!

Ready to embark on an exciting mathematical journey? Let's discover your potential!

""", unsafe_allow_html=True) # Feature cards using Streamlit columns for better compatibility st.markdown("### ✨ What Makes Us Special") col1, col2, col3 = st.columns(3) with col1: st.markdown("""
đŸŽ¯

Unique Questions

Always new, never repeated

""", unsafe_allow_html=True) with col2: st.markdown("""
📝

Text-Based Only

Pure math, no visual puzzles

""", unsafe_allow_html=True) with col3: st.markdown("""
⏰

Smart Timer

1 minute per question

""", unsafe_allow_html=True) # Getting started message st.markdown("---") st.info("👈 Choose your grade level from the sidebar and click 'Generate New Exam' to begin your mathematical adventure!") # Quick start tips with st.expander("🚀 Quick Start Guide"): st.markdown(""" **Ready to begin? Follow these simple steps:** 1. **đŸŽ¯ Choose Your Grade**: Select your current grade level from the sidebar 2. **📝 Pick Your Mode**: - đŸŸĸ **Practice Mode**: No time pressure, perfect for learning - ⏰ **Timed Mode**: 1 minute per question challenge - đŸ”Ĩ **Challenge Mode**: Extra difficult questions for advanced learners 3. **⚡ Set Difficulty**: Easy, Medium, or Hard - pick what feels right 4. **📊 Choose Questions**: 3-20 questions depending on your time 5. **🚀 Generate & Start**: Hit the generate button and begin! **💡 Pro Tips:** - Start with Practice mode to get comfortable - Use hints when you're stuck - learning is the goal! - Review all explanations to understand concepts better - Track your progress in the Dashboard tab """) # Motivational quote st.markdown("""

"Mathematics is not about numbers, equations, or algorithms. It is about understanding." - William Paul Thurston

""", unsafe_allow_html=True) def main(): # Load custom CSS load_custom_css() # Initialize session state initialize_session_state() # Get exam system exam_system = get_exam_system() # Render hero section render_hero_section() # Main navigation tab1, tab2, tab3 = st.tabs(["đŸŽ¯ Exam", "📊 Dashboard", "â„šī¸ About"]) with tab1: # Render sidebar render_sidebar() # Main exam interface if not st.session_state.questions: render_welcome_screen() elif st.session_state.exam_completed: render_results_section() else: st.markdown("### 🧮 Math Olympiad Challenge") # Show exam info col1, col2, col3 = st.columns(3) with col1: st.info(f"📚 Grade {st.session_state.grade_selected}") with col2: if st.session_state.exam_mode == "timed": total_time = len(st.session_state.questions) st.info(f"âąī¸ {st.session_state.exam_mode.title()} Mode ({total_time} min)") else: st.info(f"âąī¸ {st.session_state.exam_mode.title()} Mode") with col3: st.info(f"📝 {len(st.session_state.questions)} Questions") render_exam_interface() with tab2: render_dashboard() # Additional dashboard features if st.session_state.exam_history: st.markdown("#### đŸŽ¯ Detailed Analytics") df = pd.DataFrame(st.session_state.exam_history) col1, col2 = st.columns(2) with col1: st.markdown("##### 📊 Score Distribution") fig = px.histogram(df, x='percentage', nbins=10, title='Score Distribution', color_discrete_sequence=['#667eea']) fig.update_layout(plot_bgcolor='rgba(0,0,0,0)', paper_bgcolor='rgba(0,0,0,0)') st.plotly_chart(fig, use_container_width=True) with col2: st.markdown("##### 📈 Improvement Trend") df_trend = df.copy() df_trend['moving_avg'] = df_trend['percentage'].rolling(window=3, min_periods=1).mean() fig = go.Figure() fig.add_trace(go.Scatter(x=df_trend['date'], y=df_trend['percentage'], mode='markers+lines', name='Actual Score', line=dict(color='#667eea'))) fig.add_trace(go.Scatter(x=df_trend['date'], y=df_trend['moving_avg'], mode='lines', name='Trend', line=dict(color='#764ba2', dash='dash'))) fig.update_layout(title='Performance Trend', plot_bgcolor='rgba(0,0,0,0)', paper_bgcolor='rgba(0,0,0,0)') st.plotly_chart(fig, use_container_width=True) # Achievement showcase profile = st.session_state.user_profile if profile['badges']: st.markdown("#### 🏆 Your Achievements") badge_cols = st.columns(min(4, len(profile['badges']))) for i, badge in enumerate(profile['badges'][-8:]): # Show last 8 badges with badge_cols[i % 4]: st.markdown(f"""
{badge.split()[0]}
{' '.join(badge.split()[1:])}
""", unsafe_allow_html=True) with tab3: st.markdown("# 📖 About MathGenius Academy") # Mission section with st.container(): st.markdown("## đŸŽ¯ Our Mission") st.markdown("""

To make mathematics accessible, engaging, and fun for students of all levels through interactive Olympiad-style training. We believe every student can excel in mathematics with the right guidance and practice.

""", unsafe_allow_html=True) # Features section st.markdown("## ✨ Key Features") col1, col2 = st.columns(2) with col1: st.markdown( """

🤖 AI-Powered Learning

🎮 Multiple Game Modes

""", unsafe_allow_html=True ) with col2: st.markdown( """

📊 Progress & Analytics

🏆 Achievement System

""", unsafe_allow_html=True ) # Getting Started Guide st.markdown("## 🚀 Getting Started") steps_col1, steps_col2 = st.columns(2) with steps_col1: st.markdown("""

📋 Setup Steps

  1. Select Grade Level: Choose from Grade 1-10
  2. Pick Exam Mode: Practice, Timed, or Challenge
  3. Set Difficulty: Easy, Medium, or Hard
  4. Choose Questions: 3-20 questions per exam
  5. Generate Exam: Click the generate button
""", unsafe_allow_html=True) with steps_col2: st.markdown("""

💡 Success Tips

""", unsafe_allow_html=True) # Grade Level Guide st.markdown("## đŸŽ¯ Grade Level Guide") grade_col1, grade_col2, grade_col3 = st.columns(3) with grade_col1: st.markdown("""

🌱 Elementary (Grades 1-4)

""", unsafe_allow_html=True) with grade_col2: st.markdown("""

🚀 Middle School (Grades 5-8)

""", unsafe_allow_html=True) with grade_col3: st.markdown("""

⭐ High School (Grades 9-10)

""", unsafe_allow_html=True) # FAQ Section with st.expander("❓ Frequently Asked Questions"): st.markdown(""" **Q: How are questions made unique?** A: We use advanced AI with hash tracking to ensure you never see the same question twice, even across different sessions. **Q: Why text-based questions only?** A: We focus purely on mathematical reasoning and calculations, avoiding visual puzzles that don't translate well to text interfaces. **Q: How does the timer work in timed mode?** A: You get exactly 1 minute per question. For 5 questions, you get 5 minutes total. The timer is prominently displayed above the questions. **Q: What if I get stuck on a question?** A: Use the hint feature! We provide helpful hints to guide your thinking without giving away the answer. **Q: How is my progress tracked?** A: We track your percentage, response times, difficulty preferences, and improvement trends with detailed analytics. **Q: How do I earn badges?** A: Badges are earned automatically based on your performance, consistency, and achievements. """) # Footer st.markdown("---") st.markdown("""

Ready to become a Math Genius? 🧮

Join thousands of students improving their mathematical skills with unique, challenging questions every day!

""", unsafe_allow_html=True) st.markdown("
", unsafe_allow_html=True) st.markdown("**Made with â¤ī¸ for young mathematicians everywhere!**") if __name__ == "__main__": main()