diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -1,2957 +1,276 @@ import streamlit as st -import pandas as pd import json -import plotly.express as px -import plotly.graph_objects as go -from datetime import datetime, timedelta import google.generativeai as genai -import os import re -import math -import time -# ============================================ -# ๐Ÿš€ CONFIGURATION FOR HUGGING FACE DEPLOYMENT -# ============================================ - -# Page configuration -st.set_page_config( - page_title="AI Study Planner Pro", - page_icon="๐Ÿ“š", - layout="wide", - initial_sidebar_state="collapsed" -) - -# Get API Key from Hugging Face Secrets -GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "") - -# ============================================ -# ๐ŸŽจ CUSTOM CSS WITH IMPROVED STYLING -# ============================================ - -st.markdown(""" - -""", unsafe_allow_html=True) - -# ============================================ -# ๐Ÿ”ง SESSION STATE INITIALIZATION - UPDATED -# ============================================ - -def init_session_state(): - """Initialize all session state variables with default values""" - session_vars = { - 'study_plan': None, - 'progress': {}, - 'subject': "", - 'api_key': GEMINI_API_KEY, - 'raw_response': "", - 'show_debug': False, - 'goal_type': "Skill/Topic Completion", - 'target_date': None, - 'study_days': ["Mon", "Tue", "Wed", "Thu", "Fri"], - 'plan_loaded': False, - 'current_week': 1, - 'task_states': {}, - 'weekly_streak': 0, - 'longest_weekly_streak': 0, - 'suggested_plans': [], - 'show_suggestions': False, - 'task_completion_dates': {}, - 'flexible_completion': {}, - 'daily_tips': {}, - 'study_hours_data': {}, - 'test_results': {}, - 'current_test': None, - 'test_start_time': None, - 'test_questions': [], - 'user_test_answers': {}, - 'test_completed': False, - 'last_rerun_time': None, - 'auto_submit_triggered': False, - 'test_timer_running': False, - 'timer_seconds': 600, - 'ai_error_message': "", - 'plan_generation_attempted': False, - 'api_available': True, - 'api_quota_exceeded': False, - 'selected_topics': [], - 'debug_mode': True, # NEW: Always show debug - 'raw_ai_response': "" # NEW: Store raw AI response - } - - for key, value in session_vars.items(): - if key not in st.session_state: - st.session_state[key] = value - -init_session_state() - -# ============================================ -# โฐ AUTO-RERUN FOR TIMER FUNCTIONALITY -# ============================================ - -if st.session_state.test_start_time and not st.session_state.test_completed: - current_time = datetime.now() - elapsed = current_time - st.session_state.test_start_time - remaining_seconds = max(0, 600 - elapsed.total_seconds()) - st.session_state.timer_seconds = remaining_seconds - - if remaining_seconds <= 0 and not st.session_state.auto_submit_triggered: - st.session_state.test_completed = True - st.session_state.auto_submit_triggered = True - st.rerun() - - time.sleep(1) - st.rerun() - -# ============================================ -# ๐Ÿ’พ FILE HANDLING FUNCTIONS -# ============================================ - -def save_plan(plan, subject): - os.makedirs("data", exist_ok=True) - filename = f"data/{subject.replace(' ', '_')}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" - - if plan.get('weekly_schedule'): - for week in plan['weekly_schedule']: - week_num = week['week'] - completed_tasks = 0 - completed_tasks_info = [] - - if 'daily_tasks' in week: - for day_idx, day_tasks in enumerate(week['daily_tasks']): - if isinstance(day_tasks, list): - for task_idx, task in enumerate(day_tasks): - task_key = f"task_week{week_num}_day{day_idx}_task{task_idx}_{subject}" - if st.session_state.get(task_key, False): - completed_tasks += 1 - completion_date = st.session_state.task_completion_dates.get(task_key, datetime.now().strftime("%Y-%m-%d")) - completed_tasks_info.append({ - 'day_idx': day_idx, - 'task_idx': task_idx, - 'task': task, - 'completion_date': completion_date, - 'completed_week': week_num - }) - - week['completed_tasks_info'] = completed_tasks_info - week['tasks_completed'] = completed_tasks - week['total_tasks'] = sum(len(day) for day in week.get('daily_tasks', [])) if isinstance(week.get('daily_tasks', []), list) else 0 - week['completed'] = (completed_tasks == week['total_tasks']) - - if week['completed'] and not week.get('completion_date'): - dates = [t['completion_date'] for t in completed_tasks_info if t.get('completion_date')] - if dates: - week['completion_date'] = max(dates) - - plan['task_completion_dates'] = st.session_state.task_completion_dates - plan['flexible_completion'] = st.session_state.flexible_completion - - try: - with open(filename, 'w', encoding='utf-8') as f: - json.dump(plan, f, indent=4, ensure_ascii=False) - except Exception as e: - with open(filename, 'w', encoding='utf-8') as f: - import copy - plan_copy = copy.deepcopy(plan) - def remove_emojis(obj): - if isinstance(obj, str): - return re.sub(r'[^\x00-\x7F]+', '', obj) - elif isinstance(obj, dict): - return {k: remove_emojis(v) for k, v in obj.items()} - elif isinstance(obj, list): - return [remove_emojis(item) for item in obj] - else: - return obj - - plan_copy = remove_emojis(plan_copy) - json.dump(plan_copy, f, indent=4, ensure_ascii=False) - - master_file = "data/all_plans.json" - if os.path.exists(master_file): - with open(master_file, 'r', encoding='utf-8') as f: - try: - all_plans = json.load(f) - except: - all_plans = [] - else: - all_plans = [] - - all_plans.append({ - "subject": subject, - "goal_type": st.session_state.goal_type, - "created_at": datetime.now().isoformat(), - "filename": filename, - "current_week": st.session_state.current_week, - "weekly_streak": st.session_state.weekly_streak, - "longest_weekly_streak": st.session_state.longest_weekly_streak - }) - - with open(master_file, 'w', encoding='utf-8') as f: - json.dump(all_plans, f, indent=4, ensure_ascii=False) - - return filename - -def load_progress(subject): - progress_file = f"data/{subject.replace(' ', '_')}_progress.json" - if os.path.exists(progress_file): - with open(progress_file, 'r', encoding='utf-8') as f: - try: - return json.load(f) - except: - return {} - return {} - -def save_progress(subject, progress_data): - os.makedirs("data", exist_ok=True) - progress_file = f"data/{subject.replace(' ', '_')}_progress.json" - with open(progress_file, 'w', encoding='utf-8') as f: - json.dump(progress_data, f, indent=4, ensure_ascii=False) - -# ============================================ -# ๐Ÿ› ๏ธ UTILITY FUNCTIONS - FIXED JSON EXTRACTION -# ============================================ - -def extract_and_fix_json(text, subject="General"): - """SIMPLE & ROBUST JSON extraction - Based on actual AI response""" - if not text or not isinstance(text, str): - return None - - # Step 1: Clean the text - cleaned_text = text.strip() - - # Step 2: Remove markdown if present - if cleaned_text.startswith('```json'): - cleaned_text = cleaned_text[7:] # Remove ```json - if cleaned_text.startswith('```'): - cleaned_text = cleaned_text[3:] # Remove ``` - if cleaned_text.endswith('```'): - cleaned_text = cleaned_text[:-3] # Remove ``` - - cleaned_text = cleaned_text.strip() - - # Step 3: Remove any explanatory text before/after JSON - # Find first { and last } - start_idx = cleaned_text.find('{') - end_idx = cleaned_text.rfind('}') + 1 - - if start_idx == -1 or end_idx == -1: - return None - - json_str = cleaned_text[start_idx:end_idx] - - # Step 4: Fix common JSON issues - json_str = json_str.strip() - - # Remove trailing commas (common AI mistake) - json_str = re.sub(r',\s*}', '}', json_str) - json_str = re.sub(r',\s*]', ']', json_str) - - # Fix smart quotes - json_str = json_str.replace('"', '"').replace('"', '"') +# Test function to capture raw AI responses +def test_ai_responses(): + st.title("๐Ÿค– AI Response Debugger") - # Remove comments if any - json_str = re.sub(r'//.*', '', json_str) - - # Step 5: Try to parse - try: - return json.loads(json_str) - except json.JSONDecodeError as e: - # Try to fix more issues - try: - # Add missing quotes around unquoted keys - json_str = re.sub(r'(\s*)(\w+)(\s*):', r'\1"\2"\3:', json_str) - return json.loads(json_str) - except: - # Last resort: try to find any valid JSON structure - try: - # Look for the largest valid JSON object - pattern = r'\{[^{}]*\}|\[[^\[\]]*\]' - matches = re.findall(pattern, json_str) - if matches: - # Try to parse the largest match - for match in sorted(matches, key=len, reverse=True): - try: - return json.loads(match) - except: - continue - except: - pass - - return None - -def calculate_learning_quality(days_available, hours_per_day, study_days_count): - total_study_hours = days_available * hours_per_day - required_hours = 60 - - coverage = (total_study_hours / required_hours) * 100 - - quality_score = 0 - - if 1 <= hours_per_day <= 3: - quality_score += 30 - elif hours_per_day > 3: - quality_score += 20 - else: - quality_score += 10 - - if 3 <= study_days_count <= 5: - quality_score += 30 - elif study_days_count > 5: - quality_score += 20 - else: - quality_score += 10 - - if coverage >= 80: - quality_score += 40 - elif coverage >= 50: - quality_score += 20 - else: - quality_score += 10 - - is_severely_inadequate = (study_days_count < 2 and days_available < 30) or (coverage < 30) - - return { - "quality_score": min(100, quality_score), - "coverage_percentage": min(100, coverage), - "total_hours": total_study_hours, - "required_hours": required_hours, - "weekly_hours": hours_per_day * study_days_count, - "is_optimal": quality_score >= 60, - "is_severely_inadequate": is_severely_inadequate - } - -def calculate_progress_status(plan, completed_tasks, total_tasks, days_passed, total_days): - if total_tasks == 0: - return "Not Started" - - task_completion_rate = completed_tasks / total_tasks - time_passed_ratio = days_passed / total_days if total_days > 0 else 0 - - if task_completion_rate >= time_passed_ratio + 0.1: - return "Ahead of Schedule" - elif task_completion_rate >= time_passed_ratio - 0.1: - return "On Track" - else: - return "Behind Schedule" - -def calculate_weekly_streak(plan): - if not plan or 'weekly_schedule' not in plan: - return 0, 0 - - completed_weeks = [] - for week in plan['weekly_schedule']: - if week.get('completed', False): - week_num = week['week'] - completion_date = week.get('completion_date', '2000-01-01') - completed_weeks.append((week_num, completion_date)) - - if not completed_weeks: - return 0, 0 - - completed_weeks.sort(key=lambda x: x[0]) - - current_streak = 1 - longest_streak = 1 - temp_streak = 1 - - for i in range(1, len(completed_weeks)): - if completed_weeks[i][0] == completed_weeks[i-1][0] + 1: - temp_streak += 1 - longest_streak = max(longest_streak, temp_streak) - - if completed_weeks[i][0] >= st.session_state.current_week - 1: - current_streak = temp_streak - else: - temp_streak = 1 - - return current_streak, longest_streak - -def calculate_weekly_task_completion_with_flexibility(plan, subject): - """Calculate completed tasks per week""" - weekly_completion = {} + # Get API Key + api_key = st.text_input("Enter Gemini API Key:", type="password") - if plan and plan.get('weekly_schedule'): - for week in plan['weekly_schedule']: - week_num = week['week'] - total_tasks = 0 - if 'daily_tasks' in week and isinstance(week['daily_tasks'], list): - for day_tasks in week['daily_tasks']: - if isinstance(day_tasks, list): - total_tasks += len(day_tasks) - - weekly_completion[week_num] = { - 'assigned_tasks': total_tasks, - 'completed_in_week': 0, - 'completed_elsewhere': 0, - 'total_completed': 0 - } + if api_key: + genai.configure(api_key=api_key) - # Count completions - for week in plan['weekly_schedule']: - week_num = week['week'] - if 'daily_tasks' in week and isinstance(week['daily_tasks'], list): - for day_idx, day_tasks in enumerate(week['daily_tasks']): - if isinstance(day_tasks, list): - for task_idx, task in enumerate(day_tasks): - task_key = f"task_week{week_num}_day{day_idx}_task{task_idx}_{subject}" - if st.session_state.get(task_key, False): - weekly_completion[week_num]['completed_in_week'] += 1 + tab1, tab2 = st.tabs(["๐Ÿ“š Study Plan Response", "๐Ÿงช Test Questions Response"]) - # Calculate totals - for week_num in weekly_completion: - weekly_completion[week_num]['total_completed'] = weekly_completion[week_num]['completed_in_week'] - - return weekly_completion - -def analyze_study_patterns(daily_progress, study_days): - """Analyze study patterns""" - if not daily_progress: - return [] - - recommendations = [] - - try: - df = pd.DataFrame(daily_progress) - if 'date' in df.columns: - df['date'] = pd.to_datetime(df['date'], errors='coerce') - df = df.dropna(subset=['date']) + with tab1: + st.subheader("Test Study Plan Generation") + + subject = st.text_input("Subject:", "Data Science") + goal_type = st.selectbox("Goal Type:", ["Skill/Topic Completion", "Certification Preparation"]) + days_available = st.slider("Days Available:", 7, 365, 60) + hours_per_day = st.slider("Hours per Day:", 1, 8, 2) + study_days = st.multiselect("Study Days:", ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], + default=["Mon", "Tue", "Wed", "Thu", "Fri"]) - if len(df) > 1: - date_range = (df['date'].max() - df['date'].min()).days + 1 - study_days_count = len(df) - - if date_range > 0: - study_frequency = (study_days_count / date_range) * 100 + if st.button("Generate Test Study Plan"): + with st.spinner("Generating..."): + weeks = max(1, days_available // 7) + study_days_count = len(study_days) - if study_frequency < 30: - recommendations.append(f"๐Ÿ“… **Increase Study Frequency:** Studying {study_frequency:.0f}% of days. Aim for 50%+ for better results.") - elif study_frequency > 80: - recommendations.append(f"๐ŸŽฏ **Excellent Consistency:** Studying {study_frequency:.0f}% of days! Keep it up!") - except: - pass - - return recommendations + prompt = f"""Create a detailed {weeks}-week study plan for: {subject} +Goal Type: {goal_type} +Level: Beginner +Study schedule: {hours_per_day} hours/day, {study_days_count} days/week ({', '.join(study_days)}) -def evaluate_test_results(api_key, questions, user_answers, subject, week_number): - """Evaluate test results""" - correct_count = 0 - for i, question in enumerate(questions): - if user_answers.get(str(i)) == question['correct_answer']: - correct_count += 1 - - score_percentage = (correct_count / len(questions)) * 100 - - if score_percentage >= 90: - feedback = f"๐ŸŽ‰ Outstanding mastery of Week {week_number} concepts!" - strengths = ["Excellent understanding", "Strong test performance"] - areas = ["Consider exploring advanced topics", "Help others learn"] - recommendations = ["Continue to next week", "Try more challenging exercises"] - elif score_percentage >= 70: - feedback = f"๐Ÿ‘ Solid understanding of Week {week_number} material." - strengths = ["Good grasp of core concepts", "Effective learning approach"] - areas = ["Review specific topics", "Practice more exercises"] - recommendations = ["Review weekly material", "Retake test after revision"] - else: - feedback = f"๐Ÿ“š Review Week {week_number} topics and try again." - strengths = ["Good effort", "Learning attitude"] - areas = ["Focus on fundamentals", "Spend more time on difficult topics"] - recommendations = ["Re-study weekly material", "Complete practice exercises", "Retake test"] - - return { - 'score': correct_count, - 'correct': correct_count, - 'total': len(questions), - 'percentage': score_percentage, - 'feedback_data': { - 'feedback': feedback, - 'strengths': strengths, - 'areas_for_improvement': areas, - 'recommendations': recommendations - }, - 'timestamp': datetime.now().isoformat() - } +IMPORTANT: Return JSON ONLY with this exact structure: -def get_motivational_message(progress_percentage, status, weekly_streak): - """Get motivational message""" - messages = { - "Not Started": [ - "๐ŸŽฏ Every expert was once a beginner. Start your journey today!", - "๐Ÿš€ The first step is always the hardest. You've got this!", - "๐Ÿ“š Your future self will thank you for starting today." - ], - "Behind Schedule": [ - "๐Ÿ’ช It's not about being perfect, it's about being consistent. Keep going!", - "๐Ÿ”„ Falling behind is normal. Adjust your pace and continue forward.", - "๐ŸŒŸ Progress is progress, no matter how small. Every step counts!" - ], - "On Track": [ - "โœ… Amazing! You're right on schedule. Keep up the great work!", - "๐Ÿ”ฅ Your consistency is paying off. You're doing fantastic!", - "๐Ÿ† You're making steady progress toward your goal. Stay focused!" - ], - "Ahead of Schedule": [ - "โšก You're crushing it! Consider challenging yourself with advanced topics.", - "๐ŸŽ–๏ธ Ahead of schedule! Your dedication is inspiring.", - "๐Ÿš€ You're making rapid progress. Consider helping others or exploring deeper." - ] - } - - import random - - if weekly_streak >= 3: - streak_messages = [ - f"๐Ÿ”ฅ Amazing {weekly_streak}-week streak! You're building powerful habits!", - f"๐Ÿ† {weekly_streak} consecutive weeks of progress! That's consistency at its best!", - f"โœจ Your {weekly_streak}-week streak shows incredible dedication! Keep it up!" - ] - if status in messages: - messages[status].extend(streak_messages) - - if status in messages: - return random.choice(messages[status]) - - if progress_percentage < 30: - return "๐ŸŒฑ You're planting the seeds of knowledge. Keep nurturing them!" - elif progress_percentage < 70: - return "๐ŸŒŠ You're in the flow. Maintain this momentum!" - else: - return "๐Ÿ”๏ธ You're approaching the summit. The view will be worth it!" - -# ============================================ -# ๐Ÿ›ก๏ธ ENHANCED SAFETY SANITIZATION -# ============================================ - -def sanitize_study_inputs(subject, goal): - """Enhanced sanitization for safe AI interaction""" - - # Convert to lowercase for easier matching - original_subject = subject - original_goal = goal if goal else "" - - clean_subject = original_subject.lower().strip() - clean_goal = original_goal.lower().strip() - - # Educational focus replacements - educational_replacements = { - # Convert potentially problematic terms to educational equivalents - 'hack': 'cybersecurity analysis', - 'hacking': 'cybersecurity studies', - 'exploit': 'security analysis', - 'crack': 'security testing', - 'bypass': 'security assessment', - 'attack': 'defensive strategy', - 'malware': 'security software analysis', - 'virus': 'computer security study', - 'weapon': 'defense system analysis', - 'combat': 'defensive technique', - - # Medical safety - 'surgery': 'medical procedure study', - 'diagnosis': 'analysis technique', - 'treatment': 'management approach', - - # Financial safety - 'trading': 'market analysis', - 'stock': 'financial market study', - 'crypto': 'digital currency technology', - 'bitcoin': 'cryptocurrency technology', - - # General educational focus - 'how to make': 'learn about creating', - 'how to build': 'study building', - 'how to create': 'understand creating', - 'make a': 'create a', - 'build a': 'develop a', - 'create a': 'design a' - } - - # Apply replacements - for risky, safe in educational_replacements.items(): - if risky in clean_subject: - clean_subject = clean_subject.replace(risky, safe) - if risky in clean_goal: - clean_goal = clean_goal.replace(risky, safe) - - # Ensure educational ending - if clean_goal and not any(x in clean_goal for x in ['learn', 'study', 'understand', 'master', 'explore', 'analyze']): - clean_goal += " learning" - - # Capitalize properly - clean_subject = clean_subject.title() - clean_goal = clean_goal.capitalize() - - # Return original for display, cleaned for API - return clean_subject, clean_goal - -def validate_and_fix_plan_structure(plan, subject, weeks): - if not isinstance(plan, dict): - return None - - # Core defaults - plan.setdefault("subject", subject) - plan.setdefault("total_weeks", weeks) - plan.setdefault("daily_hours", 2) - plan.setdefault("study_days", ["Mon", "Tue", "Wed", "Thu", "Fri"]) - plan.setdefault("weekly_schedule", []) - - # FORCE correct number of weeks - fixed_schedule = [] - - for i in range(weeks): - if i < len(plan["weekly_schedule"]) and isinstance(plan["weekly_schedule"][i], dict): - week = plan["weekly_schedule"][i] - else: - week = {} - - week_num = i + 1 - week["week"] = week_num - week.setdefault("focus", f"Week {week_num}: {subject}") - week.setdefault("objectives", ["Learn concepts", "Practice", "Review"]) - week.setdefault("day_focus", ["Learn", "Practice", "Review", "Apply", "Plan"]) - week.setdefault("week_summary", f"Progress in {subject}") - week.setdefault("resources", ["Online resources"]) - week.setdefault("milestone", f"Complete Week {week_num}") - - # DAILY TASKS FIX (critical) - daily_tasks = week.get("daily_tasks", []) - if not isinstance(daily_tasks, list) or len(daily_tasks) < 5: - daily_tasks = [] - - while len(daily_tasks) < 5: - daily_tasks.append([ - f"Study {subject}", - "Practice exercises" - ]) - - week["daily_tasks"] = daily_tasks[:5] - - # Task counters - week["tasks_completed"] = 0 - week["completed"] = False - week["completion_date"] = None - week["total_tasks"] = sum(len(d) for d in week["daily_tasks"]) - - fixed_schedule.append(week) - - plan["weekly_schedule"] = fixed_schedule - - # Ensure extras - plan.setdefault("topics_allocation", { - "Fundamentals": 30, - "Core Concepts": 30, - "Practice": 25, - "Review": 15 - }) - - plan.setdefault("study_tips", [ - "Study daily", - "Review weekly", - "Practice actively" - ]) - - plan.setdefault("success_metrics", [ - "Tasks completed", - "Concept mastery" - ]) - - return plan - -# ============================================ -# ๐Ÿค– AI STUDY PLAN GENERATION - FIXED VERSION -# ============================================ - -def generate_study_plan_safe(api_key, **kwargs): - """Generate study plan with FIXED JSON parsing""" - - if not api_key or st.session_state.api_quota_exceeded: - st.session_state.ai_error_message = "API key missing or quota exceeded." - return None - - try: - # Configure API - genai.configure(api_key=api_key) - model = genai.GenerativeModel('gemini-2.5-flash') - - # Extract and sanitize parameters - original_subject = kwargs.get('subject', 'General Learning') - original_goal = kwargs.get('goal', '') - selected_topics = kwargs.get('selected_topics', []) - days_available = kwargs.get('days_available', 60) - hours_per_day = kwargs.get('hours_per_day', 2) - study_days = kwargs.get('study_days', ["Mon", "Tue", "Wed", "Thu", "Fri"]) - study_days_count = len(study_days) - weeks = max(1, days_available // 7) - - # Sanitize inputs - safe_subject, safe_goal = sanitize_study_inputs(original_subject, original_goal) - - # ENHANCED PROMPT - SIMPLIFIED based on actual response - topics_text = "" - if selected_topics: - topics_text = f"TOPICS: {', '.join(selected_topics)}" - - # SIMPLE PROMPT that works (based on the response you got) - prompt = f"""Return ONLY valid JSON. Create a {weeks}-week study plan for {safe_subject}. - -{topics_text} -GOAL: {safe_goal} -DURATION: {weeks} weeks ({days_available} days) -SCHEDULE: {hours_per_day} hours/day, {study_days_count} days/week ({', '.join(study_days)}) -LEVEL: {kwargs.get('current_level', 'Beginner')} - -JSON STRUCTURE: {{ - "subject": "{safe_subject}", + "subject": "{subject}", "total_weeks": {weeks}, "daily_hours": {hours_per_day}, "study_days": {json.dumps(study_days)}, - "topics_allocation": {{"Fundamentals": 30, "Core Concepts": 30, "Practice": 25, "Review": 15}}, + "topics_allocation": {{"Topic1": X, "Topic2": X, "Topic3": X}}, "weekly_schedule": [ {{ "week": 1, - "focus": "Week 1: Introduction", - "objectives": ["Learn basics", "Setup environment", "Complete exercises"], - "daily_tasks": [["Read intro", "Watch tutorial"], ["Practice basics", "Take notes"], ["Review concepts", "Problems"], ["Apply knowledge", "Mini-project"], ["Weekly review", "Plan next"]], - "day_focus": ["Learn", "Practice", "Review", "Apply", "Plan"], - "week_summary": "Foundation week", - "resources": ["Tutorials", "Exercises", "Docs"], - "milestone": "Complete Week 1" + "focus": "Week focus title", + "objectives": ["Objective 1", "Objective 2"], + "daily_tasks": [ + ["Task 1 for Monday", "Task 2 for Monday", "Task 3 for Monday"], + ["Task 1 for Tuesday", "Task 2 for Tuesday"], + ["Task 1 for Wednesday", "Task 2 for Wednesday", "Task 3 for Wednesday"], + ["Task 1 for Thursday"], + ["Task 1 for Friday", "Task 2 for Friday"] + ], + "day_focus": ["Monday: Introduction", "Tuesday: Practice", "Wednesday: Deep Dive", "Thursday: Review", "Friday: Project"], + "week_summary": "Brief summary of what will be learned this week", + "resources": ["Resource 1", "Resource 2"], + "milestone": "Weekly milestone achievement" }} ], - "study_tips": ["Study regularly", "Practice daily", "Review weekly"], - "success_metrics": ["Complete tasks", "Understand concepts", "Apply knowledge"] -}} - -Add weeks 2-{weeks} with similar structure. Return ONLY JSON starting with {{ and ending with }}.""" - - # Generate with safety settings - response = model.generate_content( - prompt, - generation_config=genai.GenerationConfig( - max_output_tokens=3000, - temperature=0.7 - ), - safety_settings={ - 'HARM_CATEGORY_HARASSMENT': 'BLOCK_MEDIUM_AND_ABOVE', - 'HARM_CATEGORY_HATE_SPEECH': 'BLOCK_MEDIUM_AND_ABOVE', - 'HARM_CATEGORY_SEXUALLY_EXPLICIT': 'BLOCK_MEDIUM_AND_ABOVE', - 'HARM_CATEGORY_DANGEROUS_CONTENT': 'BLOCK_MEDIUM_AND_ABOVE' - } - ) - - if hasattr(response, 'text') and response.text: - raw_response = response.text - st.session_state.raw_ai_response = raw_response # Store for debug - - # SIMPLE EXTRACTION - Based on actual response - plan = extract_and_fix_json(raw_response) - - if plan: - # Store raw response in plan for debugging - plan['_raw_response'] = raw_response - - # Validate and fix structure - plan = validate_and_fix_plan_structure(plan, safe_subject, weeks) - - if plan: - # Add metadata - plan['subject'] = original_subject - plan['safe_subject'] = safe_subject - plan['selected_topics'] = selected_topics - plan['generated_at'] = datetime.now().isoformat() - plan['total_days'] = days_available - - # Initialize weekly tracking - for week in plan.get('weekly_schedule', []): - week['completed'] = False - week['completion_date'] = None - week['tasks_completed'] = 0 - week['completed_tasks_info'] = [] + "study_tips": ["Tip 1", "Tip 2"], + "success_metrics": ["Metric 1", "Metric 2"] +}}""" + + try: + model = genai.GenerativeModel('gemini-2.5-flash') + response = model.generate_content( + prompt, + generation_config=genai.GenerationConfig( + max_output_tokens=2000, + temperature=0.7 + ) + ) - # Calculate total tasks - total_tasks = 0 - if 'daily_tasks' in week and isinstance(week['daily_tasks'], list): - for day_tasks in week['daily_tasks']: - if isinstance(day_tasks, list): - total_tasks += len(day_tasks) - week['total_tasks'] = total_tasks - - return plan - else: - st.session_state.ai_error_message = "Plan structure validation failed." - return None - else: - st.session_state.ai_error_message = "Failed to parse AI response. The AI might have returned invalid JSON." - return None - else: - st.session_state.ai_error_message = "Empty AI response." - return None - - except Exception as e: - error_msg = str(e).lower() - - # Check for specific errors - if "quota" in error_msg or "exceeded" in error_msg: - st.session_state.api_quota_exceeded = True - st.session_state.ai_error_message = "API quota exceeded." - elif "invalid" in error_msg or "permission" in error_msg or "api key" in error_msg: - st.session_state.ai_error_message = "Invalid API key." - elif "safety" in error_msg: - st.session_state.ai_error_message = "Content safety issue." - else: - st.session_state.ai_error_message = f"AI Error: {str(e)[:100]}" - - return None - -# ============================================ -# ๐Ÿ“‚ PLAN LOADING -# ============================================ - -def load_plan_from_json(uploaded_file): - try: - plan = json.load(uploaded_file) - - # Validate - required_fields = ['subject', 'weekly_schedule', 'total_weeks'] - for field in required_fields: - if field not in plan: - st.error(f"Invalid plan: Missing '{field}' field") - return None - - if 'study_days' not in plan: - plan['study_days'] = ["Mon", "Tue", "Wed", "Thu", "Fri"] - - # Initialize weeks - for week in plan.get('weekly_schedule', []): - if 'completed' not in week: - week['completed'] = False - if 'completion_date' not in week: - week['completion_date'] = None - if 'tasks_completed' not in week: - week['tasks_completed'] = 0 - if 'completed_tasks_info' not in week: - week['completed_tasks_info'] = [] - - total_tasks = 0 - if 'daily_tasks' in week and isinstance(week['daily_tasks'], list): - for day_tasks in week['daily_tasks']: - if isinstance(day_tasks, list): - total_tasks += len(day_tasks) - week['total_tasks'] = total_tasks - - if 'day_focus' not in week: - week['day_focus'] = [] - if 'week_summary' not in week: - week['week_summary'] = "" - - # Restore progress - if 'task_completion_dates' in plan: - st.session_state.task_completion_dates = plan['task_completion_dates'] - if 'flexible_completion' in plan: - st.session_state.flexible_completion = plan['flexible_completion'] - if 'daily_progress' in plan: - st.session_state.daily_progress_data = plan['daily_progress'] - - return plan - except Exception as e: - st.error(f"Error loading plan: {str(e)}") - return None - -# ============================================ -# ๐ŸŽฏ FINAL PLAN GENERATION - WITH DEBUG -# ============================================ - -def generate_final_plan(api_key, plan_data, subject, learning_quality): - """Generate and save plan with debug info""" - try: - # Clear previous error - st.session_state.ai_error_message = "" - st.session_state.plan_generation_attempted = True - - # Generate plan - plan = generate_study_plan_safe(api_key, **plan_data) + raw_text = response.text + st.subheader("Raw AI Response:") + st.text_area("Copy this for debugging:", raw_text, height=400) + + # Try to extract JSON + st.subheader("Extracted JSON Attempt:") + extracted_json = extract_and_fix_json_test(raw_text, subject) + if extracted_json: + st.success("โœ… JSON successfully extracted!") + st.json(extracted_json) + else: + st.error("โŒ Failed to extract JSON") + st.text("Cleaned text for JSON extraction:") + st.text(clean_json_text(raw_text)) + + except Exception as e: + st.error(f"Error: {str(e)}") - if plan: - # Set session state - st.session_state.study_plan = plan - st.session_state.subject = subject - st.session_state.current_week = 1 - st.session_state.weekly_streak = 0 - st.session_state.longest_weekly_streak = 0 - st.session_state.plan_loaded = True - st.session_state.show_suggestions = False + with tab2: + st.subheader("Test Question Generation") - # Clear existing task states - keys_to_remove = [key for key in st.session_state.keys() if key.startswith('task_week')] - for key in keys_to_remove: - del st.session_state[key] + week_num = st.number_input("Week Number:", 1, 12, 1) + test_subject = st.text_input("Test Subject:", "Data Science") - # Initialize tracking - st.session_state.task_completion_dates = {} - st.session_state.flexible_completion = {} - - # Save plan - filename = save_plan(plan, subject) - - # Calculate total tasks - total_tasks = 0 - for week in plan.get('weekly_schedule', []): - if 'daily_tasks' in week and isinstance(week['daily_tasks'], list): - for day_tasks in week['daily_tasks']: - if isinstance(day_tasks, list): - total_tasks += len(day_tasks) - - # Create progress data - progress_data = { - 'subject': subject, - 'goal_type': plan_data.get('goal_type', 'Skill/Topic Completion'), - 'created_at': datetime.now().isoformat(), - 'total_tasks': total_tasks, - 'completed_tasks': 0, - 'days_passed': 1, - 'completed_weeks': [], - 'weekly_streak': 0, - 'longest_weekly_streak': 0, - 'daily_progress': [], - 'weekly_progress': [], - 'last_updated': datetime.now().isoformat() - } - save_progress(subject, progress_data) - - st.success("โœ… Study plan generated successfully!") - st.balloons() - - # Show info - study_days = plan.get('study_days', plan_data.get('study_days', ["Mon", "Tue", "Wed", "Thu", "Fri"])) - st.info(f"๐Ÿ“… Your study schedule: {len(study_days)} days per week ({', '.join(study_days)})") - - # Show quality score - quality_score = learning_quality.get('quality_score', 0) - if quality_score >= 70: - st.success(f"๐ŸŽฏ **Learning Quality:** {quality_score:.0f}/100 (Excellent)") - elif quality_score >= 40: - st.info(f"๐Ÿ“Š **Learning Quality:** {quality_score:.0f}/100 (Good)") - else: - st.warning(f"๐Ÿ“Š **Learning Quality:** {quality_score:.0f}/100 (Needs Improvement)") - - # Show motivation - st.markdown(""" -
-

๐ŸŽ‰ Ready to Begin!

-

- Your personalized study journey starts now. Complete tasks at your own pace! -

-
- """, unsafe_allow_html=True) - - return True - else: - # Show specific error message - if st.session_state.ai_error_message: - st.error(f"โŒ {st.session_state.ai_error_message}") - else: - st.error("โŒ Failed to generate study plan.") - - # SHOW DEBUG INFO WHEN FAILED - if hasattr(st.session_state, 'raw_ai_response') and st.session_state.raw_ai_response: - st.markdown("---") - st.markdown("### ๐Ÿ› Debug Information") - st.markdown('
', unsafe_allow_html=True) - st.markdown("**Raw AI Response:**") - st.markdown('
', unsafe_allow_html=True) - st.text(st.session_state.raw_ai_response) - st.markdown('
', unsafe_allow_html=True) - - st.markdown("**Try to copy this response and share it:**") - if st.button("๐Ÿ“‹ Copy Raw Response"): - st.code(st.session_state.raw_ai_response, language="text") - st.success("Response shown above. Copy it and share for debugging.") - - st.markdown('
', unsafe_allow_html=True) - - return False - - except Exception as e: - st.error(f"โŒ Error: {str(e)[:100]}") - return False - -# ============================================ -# ๐Ÿ’ก DAILY TIPS AND TEST GENERATION -# ============================================ + if st.button("Generate Test Questions"): + with st.spinner("Generating test questions..."): + prompt = f"""Create 5 multiple choice questions for Week {week_num} of {test_subject}. -def generate_simple_study_tips(): - """Generate simple study tips (No AI)""" - return [ - "๐Ÿ“š **Study regularly**: Short daily sessions beat long weekly cramming", - "๐Ÿ’ช **Practice daily**: Apply what you learn through exercises", - "๐Ÿ”„ **Review weekly**: Spend 30 minutes each week reviewing past material", - "๐ŸŽฏ **Set clear goals**: Know what you want to achieve each session", - "๐Ÿง  **Take breaks**: Study 25 minutes, break 5 minutes (Pomodoro technique)", - "๐Ÿ“ **Take notes**: Write key points to improve retention", - "๐Ÿ” **Ask questions**: If confused, research or ask for help", - "๐ŸŽฎ **Make it fun**: Use apps, games, or study groups", - "โฐ **Consistent time**: Study at same time daily to build habit", - "๐Ÿ† **Celebrate wins**: Reward yourself for completing tasks" - ] - -def generate_weekly_test(api_key, week_number, weekly_tasks, subject): - """Generate weekly test with simple questions""" - if not api_key: - return None - - try: - genai.configure(api_key=api_key) - model = genai.GenerativeModel('gemini-2.5-flash') - - # Simple prompt for token efficiency - prompt = f"""Create 5 simple multiple choice questions for Week {week_number} of {subject}. -Make questions clear with complete answer options. Return JSON: {{ "questions": [ {{ - "question": "Clear complete question?", + "question": "Clear, complete question?", "options": {{ - "A": "Complete option A text", - "B": "Complete option B text", - "C": "Complete option C text", - "D": "Complete option D text" + "A": "Complete option A text with full explanation", + "B": "Complete option B text with full explanation", + "C": "Complete option C text with full explanation", + "D": "Complete option D text with full explanation" }}, "correct_answer": "A", - "explanation": "Brief explanation" + "explanation": "Brief explanation of correct answer" }} ] -}} - -Keep questions educational and simple. Make all options complete sentences.""" - - response = model.generate_content( - prompt, - generation_config=genai.GenerationConfig( - max_output_tokens=1000, - temperature=0.7 - ) - ) - - test_text = response.text.strip() - test_text = re.sub(r'```json\s*', '', test_text) - test_text = re.sub(r'```\s*', '', test_text) - - start_idx = test_text.find('{') - end_idx = test_text.rfind('}') - - if start_idx != -1 and end_idx != -1: - json_str = test_text[start_idx:end_idx+1] - try: - test_data = json.loads(json_str) - if 'questions' in test_data: - return test_data - except: - pass - - return None - - except: +}}""" + + try: + model = genai.GenerativeModel('gemini-2.5-flash') + response = model.generate_content( + prompt, + generation_config=genai.GenerationConfig( + max_output_tokens=1200, + temperature=0.7 + ) + ) + + raw_text = response.text + st.subheader("Raw AI Response:") + st.text_area("Copy this for debugging:", raw_text, height=400) + + # Try to extract JSON + st.subheader("Extracted JSON Attempt:") + extracted_json = extract_test_json_test(raw_text) + if extracted_json: + st.success("โœ… JSON successfully extracted!") + st.json(extracted_json) + else: + st.error("โŒ Failed to extract JSON") + st.text("Cleaned text for JSON extraction:") + st.text(clean_json_text(raw_text)) + + except Exception as e: + st.error(f"Error: {str(e)}") + +# Test version of extract_and_fix_json +def extract_and_fix_json_test(text, subject="General"): + """ + Extract JSON from text and fix common issues - TEST VERSION + """ + if not text or not isinstance(text, str): return None - -# ============================================ -# ๐Ÿ“š TOPIC SUGGESTIONS DATABASE -# ============================================ - -def get_topic_suggestions(subject): - """Get topic suggestions based on subject""" - topic_library = { - "python": [ - "Python Basics & Syntax", "Data Types & Variables", "Control Flow", - "Functions & Modules", "Object-Oriented Programming", "Error Handling", - "File Operations", "Libraries & Packages", "Data Structures", - "Algorithms", "Web Development", "Data Science", "Machine Learning", - "Automation", "Testing", "Debugging", "Best Practices" - ], - "web development": [ - "HTML & CSS", "JavaScript Basics", "DOM Manipulation", - "React/Vue/Angular", "Node.js", "Express.js", - "REST APIs", "Database Integration", "Authentication", - "Deployment", "Performance Optimization", "Security", - "Responsive Design", "Web Accessibility", "SEO" - ], - "data science": [ - "Statistics Fundamentals", "Python for Data Science", - "NumPy & Pandas", "Data Visualization", "Machine Learning Basics", - "Data Cleaning", "Exploratory Data Analysis", "Feature Engineering", - "Model Evaluation", "Deep Learning", "Natural Language Processing", - "Time Series Analysis", "Big Data Tools", "Cloud Platforms" - ], - "machine learning": [ - "Linear Algebra", "Calculus", "Probability", - "Supervised Learning", "Unsupervised Learning", "Neural Networks", - "Deep Learning", "Computer Vision", "Natural Language Processing", - "Reinforcement Learning", "Model Deployment", "MLOps", - "Feature Selection", "Hyperparameter Tuning", "Ensemble Methods" - ], - "aws": [ - "EC2 & Compute", "S3 Storage", "VPC Networking", - "IAM Security", "Lambda Serverless", "RDS Databases", - "CloudFront CDN", "Route 53 DNS", "CloudWatch Monitoring", - "Auto Scaling", "Load Balancing", "Container Services", - "Security Best Practices", "Cost Optimization", "Architecture Design" - ], - "spanish": [ - "Basic Greetings", "Pronunciation", "Vocabulary Building", - "Grammar Rules", "Verb Conjugation", "Sentence Structure", - "Conversation Practice", "Listening Comprehension", "Reading Skills", - "Writing Practice", "Cultural Context", "Business Spanish", - "Travel Phrases", "Idioms & Slang", "Accent Reduction" - ], - "javascript": [ - "Variables & Data Types", "Operators", "Control Structures", - "Functions", "Objects & Arrays", "DOM Manipulation", - "Events", "Async Programming", "ES6+ Features", - "Error Handling", "Debugging", "Testing", - "Frameworks", "APIs", "Best Practices" - ] - } - - # Check for exact matches - subject_lower = subject.lower() - for key in topic_library: - if key in subject_lower: - return topic_library[key] - - # Return general topics if no match - return [ - "Fundamentals & Basics", "Core Concepts", "Practical Applications", - "Advanced Techniques", "Tools & Software", "Best Practices", - "Problem Solving", "Project Work", "Review & Revision", - "Testing & Evaluation", "Real-world Applications", "Industry Standards" - ] - -# ============================================ -# ๐ŸŽช MAIN APPLICATION LAYOUT -# ============================================ - -# Main header -st.markdown('

๐Ÿ“š AI Study Planner Pro

', unsafe_allow_html=True) -st.markdown('

Plan โ†’ Track โ†’ Achieve | Your personal AI study assistant

', unsafe_allow_html=True) - -# API Status -if not st.session_state.api_key: - st.warning("โš ๏ธ **API Key Required:** Set GEMINI_API_KEY in Hugging Face Secrets for AI features.") -elif st.session_state.api_quota_exceeded: - st.error("๐Ÿšซ **API Quota Exceeded:** Please try again tomorrow or check Google AI Studio quota.") -elif not st.session_state.api_available: - st.error("โŒ **API Unavailable:** Check your API key and internet connection.") -else: - st.success("โœ… **AI Features Ready:** You can generate study plans!") - -# Debug toggle -st.checkbox("๐Ÿ”ง Show Debug Info on Failure", - value=st.session_state.debug_mode, - key="debug_mode", - help="Show raw AI response when JSON parsing fails") - -# Create tabs -tab1, tab2, tab3, tab4, tab5, tab6 = st.tabs([ - "๐ŸŽฏ Define Goal", - "๐Ÿ“… Study Plan", - "โœ… Track Progress", - "๐Ÿ“ˆ Analytics", - "๐Ÿงช Test", - "๐Ÿ“ค Export" -]) - -# ============================================ -# TAB 1: DEFINE GOAL - WITH DEBUG -# ============================================ - -with tab1: - st.markdown('

Define Your Learning Goal

', unsafe_allow_html=True) - # Option 1: Upload Existing Plan - st.info("๐Ÿ“ค **Option 1: Upload Existing Plan**") - uploaded_file = st.file_uploader("Upload your saved study plan (JSON)", type=['json'], key="upload_plan") + # Clean the text + text = text.strip() - if uploaded_file is not None and not st.session_state.plan_loaded: - plan = load_plan_from_json(uploaded_file) - if plan: - st.session_state.study_plan = plan - st.session_state.subject = plan['subject'] - st.session_state.plan_loaded = True - st.success(f"โœ… Plan loaded: {plan['subject']}") - st.rerun() + # Remove markdown code blocks + text = re.sub(r'```json\s*', '', text) + text = re.sub(r'```\s*', '', text) - st.markdown("---") + # Find JSON boundaries + start_idx = text.find('{') + end_idx = text.rfind('}') - # Option 2: Create New Plan - st.info("๐ŸŽฏ **Option 2: Create New Plan**") + if start_idx == -1 or end_idx == -1 or end_idx <= start_idx: + return None - col1, col2 = st.columns([2, 1]) + json_str = text[start_idx:end_idx+1] - with col1: - st.subheader("๐ŸŽฏ Goal Definition") - - subject = st.text_input( - "Subject/Topic:", - value="", - placeholder="e.g., Python Programming, AWS Cloud, Spanish Language, Machine Learning...", - help="What do you want to learn?" - ) - - goal = st.text_area( - "Specific Learning Goal:", - value="", - placeholder="e.g., 'Master Python for data analysis', 'Prepare for AWS certification', 'Learn Spanish conversation skills'...", - height=80, - help="Be specific about what you want to achieve" - ) - - # TOPIC SELECTOR - if subject: - st.subheader("๐Ÿ“š Topics to Learn") - st.write("Select specific topics you want to cover:") - - # Get topic suggestions - topics = get_topic_suggestions(subject) - - # Display topics in a multi-select - selected_topics = st.multiselect( - "Choose topics (select up to 10):", - options=topics, - default=topics[:min(5, len(topics))] if topics else [], - max_selections=10, - help="Select specific topics you want to focus on" - ) - - st.session_state.selected_topics = selected_topics - - if selected_topics: - st.success(f"โœ… Selected {len(selected_topics)} topics") - # Display selected topics - cols = st.columns(2) - for i, topic in enumerate(selected_topics): - with cols[i % 2]: - st.markdown(f'
๐Ÿ“Œ {topic}
', unsafe_allow_html=True) - - st.subheader("โฐ Time Availability") - - col1a, col2a = st.columns(2) - with col1a: - hours_per_day = st.slider("Hours per day:", 1, 8, 2) - with col2a: - days_available = st.slider("Total days available:", 7, 365, 60) + try: + return json.loads(json_str) + except json.JSONDecodeError as e: + st.error(f"Initial JSON parse error: {str(e)}") - st.write("**Preferred study days:**") - study_days = st.multiselect( - "Select days:", - ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], - default=["Mon", "Tue", "Wed", "Thu", "Fri"], - label_visibility="collapsed" - ) - st.session_state.study_days = study_days + # Fix common JSON issues + # 1. Fix unquoted keys + json_str = re.sub(r'([{,])\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*:', r'\1"\2":', json_str) - with col2: - st.subheader("๐ŸŽ“ Learning Preferences") + # 2. Fix trailing commas + json_str = re.sub(r',\s*}', '}', json_str) + json_str = re.sub(r',\s*]', ']', json_str) - current_level = st.selectbox( - "Current Level:", - ["Beginner", "Intermediate", "Advanced"], - index=0 - ) + # 3. Fix single quotes to double quotes + json_str = re.sub(r"'([^']*)'", r'"\1"', json_str) - # REMOVED: Learning Style as requested + # 4. Fix missing commas between objects in arrays + json_str = re.sub(r'}\s*{', '},{', json_str) - # Quality indicator - if study_days and days_available and hours_per_day: - study_days_count = len(study_days) - learning_quality = calculate_learning_quality(days_available, hours_per_day, study_days_count) + try: + return json.loads(json_str) + except json.JSONDecodeError as e2: + st.error(f"Second JSON parse error: {str(e2)}") - quality_score = learning_quality['quality_score'] - color = "green" if quality_score >= 70 else "orange" if quality_score >= 40 else "red" + # Try to fix unescaped quotes + json_str = json_str.replace('\\"', '"') - st.markdown(f""" -
-

๐Ÿ“Š Learning Quality

-
- {quality_score:.0f}/100 -
-

- {learning_quality['coverage_percentage']:.0f}% coverage -

-
-
-
-
- """, unsafe_allow_html=True) + # Remove any non-ASCII characters if needed + json_str = json_str.encode('ascii', 'ignore').decode('ascii') - if learning_quality['is_severely_inadequate']: - st.warning("โš ๏ธ **Low time allocation:** Consider increasing study hours or days for better results.") - elif learning_quality['is_optimal']: - st.success("โœ… **Optimal schedule:** Your study plan looks well-balanced!") - else: - st.info("๐Ÿ“Š **Good schedule:** Your plan is achievable with consistent effort.") + try: + return json.loads(json_str) + except: + return None + +# Test version for test questions +def extract_test_json_test(text): + """ + Extract JSON from test response text + """ + if not text or not isinstance(text, str): + return None - st.markdown("---") + text = text.strip() - # Generate Plan Button - WITH DEBUG - if st.button("๐Ÿš€ Generate AI Study Plan", type="primary", use_container_width=True): - if not st.session_state.api_key: - st.error("โš ๏ธ **API Key Required:** Please set GEMINI_API_KEY in Hugging Face Secrets.") - elif not subject: - st.error("โš ๏ธ Please enter a Subject/Topic") - elif not study_days: - st.error("โš ๏ธ Please select at least one study day") - else: - with st.spinner("๐Ÿค– AI is crafting your personalized study plan..."): - try: - study_days_count = len(study_days) - learning_quality = calculate_learning_quality(days_available, hours_per_day, study_days_count) - - plan_data = { - 'subject': subject, - 'goal': goal, - 'selected_topics': st.session_state.selected_topics, - 'hours_per_day': hours_per_day, - 'days_available': days_available, - 'current_level': current_level, - 'study_days': study_days, - 'goal_type': "Skill/Topic Completion" - } - - success = generate_final_plan(st.session_state.api_key, plan_data, subject, learning_quality) - - if success: - st.rerun() - else: - if st.session_state.ai_error_message: - st.error(f"โŒ {st.session_state.ai_error_message}") - else: - st.error("โŒ Failed to generate plan. Please try again.") - - except Exception as e: - st.error(f"โŒ Error: {str(e)[:100]}") - -# ============================================ -# TAB 2: STUDY PLAN (Keep as is) -# ============================================ - -with tab2: - if st.session_state.study_plan: - plan = st.session_state.study_plan - subject = st.session_state.subject - study_days = plan.get('study_days', ["Mon", "Tue", "Wed", "Thu", "Fri"]) - - st.markdown(f'

๐Ÿ“… Study Plan: {subject}

', unsafe_allow_html=True) - - # Calculate progress metrics - total_tasks = 0 - completed_tasks = 0 - - for week in plan.get('weekly_schedule', []): - if 'daily_tasks' in week and isinstance(week['daily_tasks'], list): - for day_idx, day_tasks in enumerate(week['daily_tasks']): - if isinstance(day_tasks, list): - total_tasks += len(day_tasks) - for task_idx in range(len(day_tasks)): - task_key = f"task_week{week['week']}_day{day_idx}_task{task_idx}_{subject}" - if st.session_state.get(task_key, False): - completed_tasks += 1 - - progress_percentage = (completed_tasks / total_tasks * 100) if total_tasks > 0 else 0 - - start_date_str = plan.get('start_date', datetime.now().strftime("%Y-%m-%d")) - try: - start_date = datetime.strptime(start_date_str, "%Y-%m-%d") - days_passed = max(1, (datetime.now() - start_date).days + 1) - except: - days_passed = 1 - - total_days = plan.get('total_days', 60) - status = calculate_progress_status(plan, completed_tasks, total_tasks, days_passed, total_days) - - current_streak, longest_streak = calculate_weekly_streak(plan) - st.session_state.weekly_streak = current_streak - st.session_state.longest_weekly_streak = longest_streak - - status_class = { - "Not Started": "status-behind", - "Behind Schedule": "status-behind", - "On Track": "status-on-track", - "Ahead of Schedule": "status-ahead" - }.get(status, "status-on-track") - - # Display metrics - FIXED STREAK DISPLAY - col1, col2, col3, col4 = st.columns(4) - with col1: - weeks = plan.get('total_weeks', 0) - st.metric("Total Weeks", weeks) - with col2: - st.metric("Study Days/Week", len(study_days)) - with col3: - if current_streak > 0: - st.markdown(f""" -
-
๐Ÿ”ฅ {current_streak} Week Streak
-
Longest: {longest_streak} weeks
-
- """, unsafe_allow_html=True) - else: - st.metric("Weekly Streak", "0 weeks") - with col4: - daily_hours = plan.get('daily_hours', 2) - st.metric("Daily Hours", f"{daily_hours}h") - - st.info(f"๐Ÿ“… Your study schedule: {len(study_days)} days per week ({', '.join(study_days)})") - - # Progress display - st.markdown(f""" -
-
-
-

Progress Status

-

Overall Progress: {progress_percentage:.1f}%

-

- Weekly Streak: {current_streak} weeks โ€ข Longest: {longest_streak} weeks -

-
-
- {status} -
-
-
-
-
-

- {completed_tasks} of {total_tasks} tasks completed | Day {min(days_passed, total_days)} of {total_days} -

-
- """, unsafe_allow_html=True) - - # Motivation - motivation = get_motivational_message(progress_percentage, status, current_streak) - st.info(f"๐Ÿ’ก {motivation}") - - # SIMPLE STUDY TIPS - NO AI AS REQUESTED - st.subheader("๐Ÿ’ก Simple Study Tips") - simple_tips = generate_simple_study_tips() + # Remove markdown code blocks + text = re.sub(r'```json\s*', '', text) + text = re.sub(r'```\s*', '', text) + text = re.sub(r'```javascript\s*', '', text) + text = re.sub(r'```python\s*', '', text) + + # Find the JSON part + start_idx = text.find('{') + end_idx = text.rfind('}') + + if start_idx == -1 or end_idx == -1 or end_idx <= start_idx: + # Maybe it's an array + start_idx = text.find('[') + end_idx = text.rfind(']') - tips_cols = st.columns(2) - for i, tip in enumerate(simple_tips): - with tips_cols[i % 2]: - st.markdown(f'
{tip}
', unsafe_allow_html=True) - - # Topic Allocation Chart - if 'topics_allocation' in plan: - st.subheader("๐Ÿ“š Topic Allocation") - topics = plan['topics_allocation'] - chart_data = [] - for topic, value in topics.items(): - try: - num_value = float(value) - if num_value > 0: - chart_data.append({"Topic": topic, "Percentage": num_value}) - except: - continue - - if chart_data: - df = pd.DataFrame(chart_data) - fig = px.pie(df, values='Percentage', names='Topic', - title=f"Time Allocation for {subject}", hole=0.3) - st.plotly_chart(fig, use_container_width=True) - - # Weekly Schedule - st.subheader("๐Ÿ“‹ Weekly Schedule") - - current_week = st.selectbox( - "Jump to week:", - options=list(range(1, plan.get('total_weeks', 1) + 1)), - index=st.session_state.current_week - 1, - key="week_selector" - ) - st.session_state.current_week = current_week - - if plan.get('weekly_schedule'): - week_idx = current_week - 1 - if week_idx < len(plan['weekly_schedule']): - week = plan['weekly_schedule'][week_idx] - - with st.expander(f"**Week {week['week']}: {week.get('focus', 'Weekly Focus')}**", expanded=True): - # Week Focus Box - st.markdown(f""" -
-

๐Ÿ“š Week {week['week']} Focus

-

{week.get('focus', 'Weekly learning objectives')}

-
- """, unsafe_allow_html=True) - - col1, col2 = st.columns([2, 1]) - - with col1: - st.markdown("### ๐Ÿ“‹ Objectives") - if 'objectives' in week and week['objectives']: - for obj in week['objectives']: - st.markdown(f"โ€ข **{obj}**") - - st.markdown("### ๐ŸŽฏ Milestone") - if 'milestone' in week: - st.info(f"**{week['milestone']}**") - - if week.get('completed') and week.get('completion_date'): - st.success(f"โœ… **Completed on:** {week['completion_date']}") - - with col2: - week_tasks = week['total_tasks'] - completed_week_tasks = 0 - - # Count completed tasks for this week - if 'daily_tasks' in week and isinstance(week['daily_tasks'], list): - for day_idx, day_tasks in enumerate(week['daily_tasks']): - if isinstance(day_tasks, list): - for task_idx in range(len(day_tasks)): - task_key = f"task_week{week['week']}_day{day_idx}_task{task_idx}_{subject}" - if st.session_state.get(task_key, False): - completed_week_tasks += 1 - - week_progress = (completed_week_tasks / week_tasks * 100) if week_tasks > 0 else 0 - week_completed = (completed_week_tasks == week_tasks) - - if week_completed: - st.success("โœ… **Week Completed**") - if week.get('completion_date'): - st.caption(f"Completed on: {week['completion_date']}") - else: - st.metric("Week Progress", f"{week_progress:.0f}%") - - if st.button(f"Mark Week {week['week']} Complete", key=f"complete_week_{week['week']}"): - week['completed'] = True - week['completion_date'] = datetime.now().strftime("%Y-%m-%d") - week['tasks_completed'] = completed_week_tasks - - new_streak, new_longest = calculate_weekly_streak(plan) - st.session_state.weekly_streak = new_streak - st.session_state.longest_weekly_streak = max(st.session_state.longest_weekly_streak, new_longest) - - save_plan(plan, subject) - - progress_data = load_progress(subject) - if progress_data: - progress_data['completed_weeks'] = [w['week'] for w in plan.get('weekly_schedule', []) if w.get('completed', False)] - progress_data['weekly_streak'] = new_streak - progress_data['longest_weekly_streak'] = max(progress_data.get('longest_weekly_streak', 0), new_longest) - progress_data['last_updated'] = datetime.now().isoformat() - save_progress(subject, progress_data) - - st.success(f"Week {week['week']} marked as complete!") - st.balloons() - st.rerun() - - # Week Summary - if week.get('week_summary'): - st.markdown(f""" -
-

๐Ÿ“ Week Summary

-

{week['week_summary']}

-
- """, unsafe_allow_html=True) - - st.markdown("---") - st.markdown(f"### ๐Ÿ“ Tasks for Week {week['week']}") - - if 'daily_tasks' in week and isinstance(week['daily_tasks'], list): - day_focus = week.get('day_focus', []) - - for day_idx, day_tasks in enumerate(week['daily_tasks']): - if isinstance(day_tasks, list) and day_idx < len(study_days): - day_name = study_days[day_idx] - - st.markdown(f"#### {day_name}") - - # Show day focus - if day_idx < len(day_focus): - st.markdown(f""" -
- Focus: {day_focus[day_idx]} -
- """, unsafe_allow_html=True) - - # Show each task - for task_idx, task in enumerate(day_tasks): - task_key = f"task_week{week['week']}_day{day_idx}_task{task_idx}_{subject}" - - if task_key not in st.session_state: - st.session_state[task_key] = False - - task_class = "completed" if st.session_state[task_key] else "" - - # Task container - col_check, col_desc = st.columns([1, 20]) - with col_check: - is_completed = st.checkbox( - "", - value=st.session_state[task_key], - key=f"checkbox_{task_key}", - label_visibility="collapsed" - ) - - with col_desc: - st.markdown(f""" -
- {task} -
- """, unsafe_allow_html=True) - - if is_completed != st.session_state[task_key]: - st.session_state[task_key] = is_completed - - if is_completed: - today = datetime.now().strftime("%Y-%m-%d") - st.session_state.task_completion_dates[task_key] = today - - # Update week progress - completed_week_tasks = 0 - if 'daily_tasks' in week and isinstance(week['daily_tasks'], list): - for d_idx, d_tasks in enumerate(week['daily_tasks']): - if isinstance(d_tasks, list): - for t_idx in range(len(d_tasks)): - tk = f"task_week{week['week']}_day{d_idx}_task{t_idx}_{subject}" - if st.session_state.get(tk, False): - completed_week_tasks += 1 - - week['tasks_completed'] = completed_week_tasks - week['completed'] = (completed_week_tasks == week['total_tasks']) - - if week['completed'] and not week.get('completion_date'): - week['completion_date'] = today - - new_streak, new_longest = calculate_weekly_streak(plan) - st.session_state.weekly_streak = new_streak - st.session_state.longest_weekly_streak = max(st.session_state.longest_weekly_streak, new_longest) - - save_plan(plan, subject) - - progress_data = load_progress(subject) - if progress_data: - all_completed = 0 - for w in plan.get('weekly_schedule', []): - if 'daily_tasks' in w and isinstance(w['daily_tasks'], list): - for d_idx, d_tasks in enumerate(w['daily_tasks']): - if isinstance(d_tasks, list): - for t_idx in range(len(d_tasks)): - tk = f"task_week{w['week']}_day{d_idx}_task{t_idx}_{subject}" - if st.session_state.get(tk, False): - all_completed += 1 - - progress_data['completed_tasks'] = all_completed - progress_data['completed_weeks'] = [w['week'] for w in plan.get('weekly_schedule', []) if w.get('completed', False)] - progress_data['weekly_streak'] = st.session_state.weekly_streak - progress_data['longest_weekly_streak'] = max(progress_data.get('longest_weekly_streak', 0), st.session_state.longest_weekly_streak) - progress_data['last_updated'] = datetime.now().isoformat() - save_progress(subject, progress_data) - - st.rerun() - - st.markdown("---") - else: - st.info("No tasks defined for this week") - - else: - st.info("๐Ÿ‘ˆ **Define your learning goal first in the 'Define Goal' tab!**") - if st.session_state.plan_generation_attempted and st.session_state.ai_error_message: - st.error(f"โŒ **Plan Generation Failed:** {st.session_state.ai_error_message}") - -# ============================================ -# TAB 3-6: REST OF THE APP (Keep as is) -# ============================================ - -# (The rest of your tabs remain the same - tab3, tab4, tab5, tab6) -# I'm shortening for character limit, but keep your existing code for tabs 3-6 - -with tab3: - if st.session_state.study_plan: - subject = st.session_state.subject - progress_data = load_progress(subject) - plan = st.session_state.study_plan - study_days = plan.get('study_days', ["Mon", "Tue", "Wed", "Thu", "Fri"]) - - st.markdown(f'

โœ… Progress Tracker: {subject}

', unsafe_allow_html=True) - - # Calculate progress - total_tasks = 0 - completed_tasks = 0 - - for week in plan.get('weekly_schedule', []): - if 'daily_tasks' in week and isinstance(week['daily_tasks'], list): - for day_idx, day_tasks in enumerate(week['daily_tasks']): - if isinstance(day_tasks, list): - total_tasks += len(day_tasks) - for task_idx in range(len(day_tasks)): - task_key = f"task_week{week['week']}_day{day_idx}_task{task_idx}_{subject}" - if st.session_state.get(task_key, False): - completed_tasks += 1 - - progress_percentage = (completed_tasks / total_tasks * 100) if total_tasks > 0 else 0 - - weekly_completion = calculate_weekly_task_completion_with_flexibility(plan, subject) - current_streak, longest_streak = calculate_weekly_streak(plan) - - # Display metrics with FIXED STREAK - col1, col2, col3, col4 = st.columns(4) - with col1: - st.metric("Overall Progress", f"{progress_percentage:.1f}%") - with col2: - st.metric("Tasks Completed", f"{completed_tasks}/{total_tasks}") - with col3: - completed_weeks = sum(1 for week in plan.get('weekly_schedule', []) if week.get('completed', False)) - total_weeks = plan.get('total_weeks', 0) - st.metric("Weeks Completed", f"{completed_weeks}/{total_weeks}") - with col4: - if current_streak > 0: - st.markdown(f""" -
-
๐Ÿ”ฅ {current_streak} Week Streak
-
Longest: {longest_streak} weeks
-
- """, unsafe_allow_html=True) - else: - st.metric("Weekly Streak", "0 weeks") - - # Daily Progress Log - ALLOWS MULTIPLE ENTRIES PER DAY - st.subheader("๐Ÿ“ Daily Progress Log") + if start_idx == -1 or end_idx == -1 or end_idx <= start_idx: + return None + + json_str = text[start_idx:end_idx+1] + + try: + return json.loads(json_str) + except json.JSONDecodeError as e: + st.error(f"JSON parse error: {str(e)}") - with st.form("daily_log_form"): - today = datetime.now().strftime("%Y-%m-%d %H:%M") # Include time for multiple entries - st.write(f"**Date & Time:** {today}") - - col1, col2, col3 = st.columns(3) - with col1: - hours_studied = st.number_input("Hours studied:", - min_value=0.1, max_value=8.0, - value=1.0, step=0.5) - with col2: - study_hour = st.selectbox( - "Study period:", - options=["Morning (6AM-12PM)", "Afternoon (12PM-6PM)", "Evening (6PM-10PM)", - "Night (10PM-6AM)", "Multiple times today"], - index=1, - help="When did you study?" - ) - with col3: - mood = st.select_slider("Study mood:", - options=["๐Ÿ˜ซ", "๐Ÿ˜•", "๐Ÿ˜", "๐Ÿ™‚", "๐Ÿ˜Š", "๐Ÿคฉ"], - value="๐Ÿ˜Š") - - topics_covered = st.text_input("Topics covered:", - placeholder=f"e.g., {subject} concepts, exercises...") - achievements = st.text_area("Key achievements:", - placeholder="What did you learn or accomplish?") - challenges = st.text_area("Challenges faced:", - placeholder="What was difficult? How did you overcome it?") - - if st.form_submit_button("๐Ÿ’พ Save Progress Entry", use_container_width=True): - daily_log = { - 'date': datetime.now().strftime("%Y-%m-%d %H:%M:%S"), # Full timestamp - 'hours': hours_studied, - 'study_hour': study_hour, - 'mood': mood, - 'topics': topics_covered, - 'achievements': achievements, - 'challenges': challenges - } - - if 'daily_progress' not in progress_data: - progress_data['daily_progress'] = [] - - # ALWAYS ADD NEW ENTRY (allows multiple per day) - progress_data['daily_progress'].append(daily_log) - - progress_data['completed_tasks'] = completed_tasks - progress_data['completed_weeks'] = [w['week'] for w in plan.get('weekly_schedule', []) if w.get('completed', False)] - progress_data['weekly_streak'] = current_streak - progress_data['longest_weekly_streak'] = max(progress_data.get('longest_weekly_streak', 0), longest_streak) - progress_data['last_updated'] = datetime.now().isoformat() - - save_progress(subject, progress_data) - st.success("โœ… Progress saved successfully!") - st.rerun() - - # Progress History - if progress_data.get('daily_progress'): - st.subheader("๐Ÿ“… Progress History") - try: - df = pd.DataFrame(progress_data['daily_progress']) - if not df.empty: - display_df = df.copy() - - if 'date' in display_df.columns: - try: - display_df['Date'] = pd.to_datetime(display_df['date']).dt.strftime('%Y-%m-%d %H:%M') - except: - display_df['Date'] = display_df['date'] - - display_cols = [] - if 'Date' in display_df.columns: - display_cols.append('Date') - if 'hours' in display_df.columns: - display_df['Hours'] = display_df['hours'] - display_cols.append('Hours') - if 'study_hour' in display_df.columns: - display_df['Study Period'] = display_df['study_hour'] - display_cols.append('Study Period') - if 'mood' in display_df.columns: - display_df['Mood'] = display_df['mood'] - display_cols.append('Mood') - if 'topics' in display_df.columns: - display_df['Topics'] = display_df['topics'] - display_cols.append('Topics') - if 'achievements' in display_df.columns: - display_df['Achievements'] = display_df['achievements'] - display_cols.append('Achievements') - if 'challenges' in display_df.columns: - display_df['Challenges'] = display_df['challenges'] - display_cols.append('Challenges') - - if display_cols: - st.dataframe(display_df[display_cols], use_container_width=True) - else: - st.warning("No valid data available to display.") - except Exception as e: - st.warning(f"Could not display progress history: {str(e)}") - - else: - st.info("๐Ÿ‘ˆ **Generate a study plan first to track progress!**") - -# ============================================ -# TAB 4: ANALYTICS (No changes needed - same as before) -# ============================================ - -with tab4: - if st.session_state.study_plan: - st.markdown('

๐Ÿ“ˆ Study Analytics

', unsafe_allow_html=True) - - subject = st.session_state.subject - progress_data = load_progress(subject) - plan = st.session_state.study_plan - study_days = plan.get('study_days', ["Mon", "Tue", "Wed", "Thu", "Fri"]) - - daily_progress = progress_data.get('daily_progress', []) - study_days_logged = len(daily_progress) - - # Calculate completed tasks - completed_tasks = 0 - total_tasks = 0 - - for week in plan.get('weekly_schedule', []): - if 'daily_tasks' in week and isinstance(week['daily_tasks'], list): - for day_idx, day_tasks in enumerate(week['daily_tasks']): - if isinstance(day_tasks, list): - total_tasks += len(day_tasks) - for task_idx in range(len(day_tasks)): - task_key = f"task_week{week['week']}_day{day_idx}_task{task_idx}_{subject}" - if st.session_state.get(task_key, False): - completed_tasks += 1 - - completed_weeks = sum(1 for week in plan.get('weekly_schedule', []) if week.get('completed', False)) - total_weeks = plan.get('total_weeks', 0) - - current_streak = progress_data.get('weekly_streak', 0) - longest_streak = progress_data.get('longest_weekly_streak', 0) - - if daily_progress: - try: - col1, col2, col3, col4 = st.columns(4) - - with col1: - if daily_progress: - df = pd.DataFrame(daily_progress) - if 'hours' in df.columns: - df['hours'] = pd.to_numeric(df['hours'], errors='coerce') - total_hours = df['hours'].sum() - else: - total_hours = 0 - else: - total_hours = 0 - st.metric("Total Hours", f"{total_hours:.1f}") - - with col2: - st.metric("Study Days Logged", study_days_logged) - - with col3: - st.metric("Tasks Completed", f"{completed_tasks}/{total_tasks}") - - with col4: - if current_streak > 0: - st.markdown(f""" -
-
๐Ÿ”ฅ {current_streak} Week Streak
-
Longest: {longest_streak} weeks
-
- """, unsafe_allow_html=True) - else: - st.metric("Weekly Streak", "0 weeks") - - st.subheader("๐Ÿ“ˆ Study Patterns Analysis") - - if daily_progress and len(daily_progress) > 1: - df = pd.DataFrame(daily_progress) - df['date'] = pd.to_datetime(df['date'], errors='coerce') - df = df.dropna(subset=['date']) - - if len(df) > 1: - if 'study_hour' in df.columns: - st.subheader("๐Ÿ• Preferred Study Hours") - - def extract_hour(hour_str): - if 'hour' in hour_str: - try: - hour_num = int(''.join(filter(str.isdigit, hour_str.split('hour')[0].strip()))) - return hour_num - except: - return 0 - return 0 - - df['hour_num'] = df['study_hour'].apply(extract_hour) - - fig_hour = go.Figure() - fig_hour.add_trace(go.Scatter( - x=df['date'], - y=df['hour_num'], - mode='lines+markers', - name='Study Hour', - line=dict(color='#3B82F6', width=2), - marker=dict(size=8), - text=df['study_hour'], - hoverinfo='text+y' - )) - - fig_hour.update_layout( - title='Study Hours Over Time', - xaxis_title='Date', - yaxis_title='Study Hour (1-24)', - yaxis=dict(tickmode='linear', dtick=1), - hovermode='x unified', - showlegend=True - ) - - st.plotly_chart(fig_hour, use_container_width=True) - - if not df.empty and 'study_hour' in df.columns: - common_hour = df['study_hour'].mode() - if not common_hour.empty: - st.info(f"**Most frequent study time:** {common_hour.iloc[0]}") - - if 'mood' in df.columns: - st.subheader("๐Ÿ˜Š Study Mood Analysis") - - mood_counts = df['mood'].value_counts().reset_index() - mood_counts.columns = ['Mood', 'Count'] - - fig_mood = px.pie(mood_counts, values='Count', names='Mood', - title='Mood Distribution During Study Sessions', - hole=0.3) - - st.plotly_chart(fig_mood, use_container_width=True) - - st.subheader("๐Ÿ’ก Personalized Recommendations") - - recommendations = [] - - if daily_progress: - pattern_recommendations = analyze_study_patterns(daily_progress, study_days) - recommendations.extend(pattern_recommendations) - - progress_percentage = (completed_tasks / total_tasks * 100) if total_tasks > 0 else 0 - - if progress_percentage < 30: - recommendations.append("**๐ŸŒฑ You're just getting started!** Focus on building consistent weekly study habits.") - elif progress_percentage < 70: - recommendations.append("**๐Ÿ“š Great momentum!** You're making solid progress. Keep up the weekly consistency!") - else: - recommendations.append("**๐Ÿ† Excellent progress!** You're approaching completion. Stay focused on your final goals!") - - if current_streak > 0: - if current_streak >= 5: - recommendations.append(f"๐Ÿ”ฅ **Incredible {current_streak}-week streak!** You've mastered consistent study habits!") - elif current_streak >= 3: - recommendations.append(f"๐ŸŽฏ **Great {current_streak}-week streak!** You're building powerful momentum!") - else: - recommendations.append(f"๐Ÿ“ˆ **Good start with {current_streak} week streak!** Keep going to build a longer streak!") - else: - recommendations.append("โณ **Start a streak!** Complete a full week to begin your weekly streak journey.") - - if recommendations: - for rec in recommendations: - st.info(rec) - else: - st.success("**Excellent!** Your study habits are on track. Keep up the good work!") - - st.subheader("โœ… Success Metrics Check") - - success_data = [] - success_data.append({ - 'Metric': 'Task completion progress', - 'Status': 'โœ… Excellent' if progress_percentage >= 80 else '๐ŸŸก Good' if progress_percentage >= 50 else 'โณ Needs work', - 'Value': f'{progress_percentage:.0f}%' - }) - - week_completion_rate = (completed_weeks / total_weeks * 100) if total_weeks > 0 else 0 - success_data.append({ - 'Metric': 'Weekly completion rate', - 'Status': 'โœ… Excellent' if week_completion_rate >= 80 else '๐ŸŸก Good' if week_completion_rate >= 50 else 'โณ Needs work', - 'Value': f'{week_completion_rate:.0f}%' - }) - - df_success = pd.DataFrame(success_data) - st.dataframe(df_success, use_container_width=True) - - except Exception as e: - st.warning(f"Could not generate analytics: {str(e)}") - else: - st.info("๐Ÿ“Š **Log your daily progress in the 'Track Progress' tab to unlock analytics!**") - - else: - st.info("๐Ÿ‘ˆ **Generate a study plan first to see analytics!**") - -# ============================================ -# TAB 5: TESTS (No changes needed - same as before) -# ============================================ - -with tab5: - if st.session_state.study_plan: - plan = st.session_state.study_plan - subject = st.session_state.subject + # Common fixes + # Fix 1: Unquoted keys + json_str = re.sub(r'([{,])\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*:', r'\1"\2":', json_str) - st.markdown(f'

๐Ÿงช Weekly Tests: {subject}

', unsafe_allow_html=True) + # Fix 2: Single quotes to double quotes + json_str = re.sub(r"'([^']*)'", r'"\1"', json_str) - # Active Test View - if st.session_state.current_test is not None and not st.session_state.test_completed: - week_num = st.session_state.current_test - test_questions = st.session_state.test_questions - user_answers = st.session_state.user_test_answers - - # Timer (10 minutes = 600 seconds) - timer_seconds = st.session_state.timer_seconds - minutes = int(timer_seconds // 60) - seconds = int(timer_seconds % 60) - - timer_class = "timer-warning" if timer_seconds < 300 else "" - - st.markdown(f""" -
- โฐ {minutes:02d}:{seconds:02d} -
- """, unsafe_allow_html=True) - - st.markdown(f""" -
-

๐Ÿ“ Week {week_num} Test

-

Test your knowledge from Week {week_num}

-
- """, unsafe_allow_html=True) - - # Display Questions with FULL TEXT - for i, question in enumerate(test_questions): - st.markdown(f""" -
-

Question {i+1}

-

{question['question']}

-
- """, unsafe_allow_html=True) - - current_answer = user_answers.get(str(i), "") - - # Use buttons with full text display - for option in ['A', 'B', 'C', 'D']: - option_text = question['options'][option] - is_selected = (current_answer == option) - - if st.button( - f"{option}. {option_text}", - key=f"test_q{i}_opt{option}", - use_container_width=True, - type="primary" if is_selected else "secondary" - ): - st.session_state.user_test_answers[str(i)] = option - st.rerun() - - col1, col2, col3 = st.columns([1, 2, 1]) - with col2: - if st.button("๐Ÿ“ค Submit Test", type="primary", use_container_width=True): - st.session_state.test_completed = True - st.rerun() + # Fix 3: Escape quotes in strings + json_str = json_str.replace('\\"', '\\\\"') - # Test Results View - elif st.session_state.test_completed and st.session_state.current_test is not None: - week_num = st.session_state.current_test - test_questions = st.session_state.test_questions - user_answers = st.session_state.user_test_answers - - results = evaluate_test_results(st.session_state.api_key, test_questions, user_answers, subject, week_num) - - test_key = f"week_{week_num}_test" - st.session_state.test_results[test_key] = results - - st.markdown(f""" -
-

๐Ÿ“Š Test Results: Week {week_num}

-
- {results['percentage']:.1f}% -
-

- {results['correct']} out of {results['total']} correct -

-
- """, unsafe_allow_html=True) - - feedback_data = results['feedback_data'] - - st.success(f"**๐Ÿ“ Feedback:** {feedback_data['feedback']}") - - col1, col2 = st.columns(2) - with col1: - st.info("**โœ… Strengths:**") - for strength in feedback_data['strengths']: - st.write(f"โ€ข {strength}") - - with col2: - st.warning("**๐Ÿ“š Areas for Improvement:**") - for area in feedback_data['areas_for_improvement']: - st.write(f"โ€ข {area}") - - st.info("**๐Ÿ’ก Recommendations:**") - for rec in feedback_data['recommendations']: - st.write(f"โ€ข {rec}") - - with st.expander("๐Ÿ“‹ Review Answers", expanded=False): - for i, question in enumerate(test_questions): - user_answer = user_answers.get(str(i), "Not answered") - is_correct = (user_answer == question['correct_answer']) - - st.markdown(f""" -
-

Question {i+1} {'โœ…' if is_correct else 'โŒ'}

-

{question['question']}

-
- """, unsafe_allow_html=True) - - col1, col2 = st.columns(2) - with col1: - st.write("**Your Answer:**") - if user_answer in question['options']: - st.write(f"{user_answer}. {question['options'][user_answer]}") - else: - st.write("Not answered") - - with col2: - st.write("**Correct Answer:**") - st.write(f"{question['correct_answer']}. {question['options'][question['correct_answer']]}") - - st.info(f"**Explanation:** {question['explanation']}") - st.markdown("---") - - if st.button("โ† Back to Test List", use_container_width=True): - st.session_state.current_test = None - st.session_state.test_questions = [] - st.session_state.user_test_answers = {} - st.session_state.test_completed = False - st.session_state.test_start_time = None - st.session_state.auto_submit_triggered = False - st.session_state.test_timer_running = False - st.session_state.timer_seconds = 600 - st.rerun() - - # Test Selection View - else: - st.info("Take weekly tests to assess your understanding of each week's material.") - - if plan.get('weekly_schedule'): - for week in plan['weekly_schedule']: - week_num = week['week'] - - # Calculate completion percentage for this week - week_tasks = week['total_tasks'] - completed_week_tasks = 0 - - if 'daily_tasks' in week and isinstance(week['daily_tasks'], list): - for day_idx, day_tasks in enumerate(week['daily_tasks']): - if isinstance(day_tasks, list): - for task_idx in range(len(day_tasks)): - task_key = f"task_week{week_num}_day{day_idx}_task{task_idx}_{subject}" - if st.session_state.get(task_key, False): - completed_week_tasks += 1 - - completion_percentage = (completed_week_tasks / week_tasks * 100) if week_tasks > 0 else 0 - test_available = completion_percentage >= 80 - - col1, col2, col3 = st.columns([3, 1, 1]) - - with col1: - st.markdown(f"### Week {week_num}: {week.get('focus', '')}") - st.caption(f"Completion: {completion_percentage:.0f}% โ€ข Tasks: {completed_week_tasks}/{week_tasks}") - - with col2: - test_key = f"week_{week_num}_test" - test_taken = test_key in st.session_state.test_results - - if test_taken: - score = st.session_state.test_results[test_key]['percentage'] - st.metric("Score", f"{score:.0f}%") - else: - st.write("Not taken") - - with col3: - if test_available: - if st.button(f"๐Ÿ“ Start Test", key=f"test_week{week_num}", use_container_width=True): - with st.spinner(f"Generating test for Week {week_num}..."): - weekly_tasks = [] - if 'daily_tasks' in week and isinstance(week['daily_tasks'], list): - for day_tasks in week['daily_tasks']: - if isinstance(day_tasks, list): - weekly_tasks.extend(day_tasks) - - test_data = generate_weekly_test(st.session_state.api_key, week_num, weekly_tasks, subject) - - if test_data and 'questions' in test_data: - st.session_state.current_test = week_num - st.session_state.test_questions = test_data['questions'] - st.session_state.user_test_answers = {} - st.session_state.test_completed = False - st.session_state.test_start_time = datetime.now() - st.session_state.auto_submit_triggered = False - st.session_state.test_timer_running = True - st.session_state.timer_seconds = 600 # 10 minutes - st.rerun() - else: - st.error("Failed to generate test. Please try again.") - else: - st.button(f"๐Ÿ”’ Locked", key=f"locked_week{week_num}", disabled=True, use_container_width=True) - if completion_percentage < 80: - needed = 80 - completion_percentage - st.caption(f"Complete {needed:.0f}% more") - else: - st.caption("Complete 80% to unlock") - - st.markdown("---") - else: - st.info("๐Ÿ‘ˆ **Generate a study plan first to take tests!**") - -# ============================================ -# TAB 6: EXPORT (No changes needed - same as before) -# ============================================ - -with tab6: - st.markdown('

๐Ÿ“ค Export Your Study Plan

', unsafe_allow_html=True) - - if st.session_state.study_plan: - plan = st.session_state.study_plan - subject = st.session_state.subject - - st.info("๐Ÿ’พ **Download your study plan to continue later**") - st.write("Save your progress and come back anytime to continue where you left off!") - - export_plan = plan.copy() - - # Update completed tasks info - if export_plan.get('weekly_schedule'): - for week in export_plan['weekly_schedule']: - week_num = week['week'] - completed_tasks_info = [] - - if 'daily_tasks' in week and isinstance(week['daily_tasks'], list): - for day_idx, day_tasks in enumerate(week['daily_tasks']): - if isinstance(day_tasks, list): - for task_idx, task in enumerate(day_tasks): - task_key = f"task_week{week_num}_day{day_idx}_task{task_idx}_{subject}" - if st.session_state.get(task_key, False): - completion_date = st.session_state.task_completion_dates.get(task_key, datetime.now().strftime("%Y-%m-%d")) - completed_tasks_info.append({ - 'day_idx': day_idx, - 'task_idx': task_idx, - 'task': task, - 'completion_date': completion_date - }) - - week['completed_tasks_info'] = completed_tasks_info - week['tasks_completed'] = len(completed_tasks_info) - week['completed'] = (len(completed_tasks_info) == week['total_tasks']) - if week['completed'] and not week.get('completion_date'): - dates = [t['completion_date'] for t in completed_tasks_info if t.get('completion_date')] - if dates: - week['completion_date'] = max(dates) - - export_plan['task_completion_dates'] = st.session_state.task_completion_dates - export_plan['flexible_completion'] = st.session_state.flexible_completion - export_plan['exported_at'] = datetime.now().isoformat() - - # Add daily progress - progress_data = load_progress(subject) - if progress_data and 'daily_progress' in progress_data: - export_plan['daily_progress'] = progress_data['daily_progress'] - - json_str = json.dumps(export_plan, indent=2, ensure_ascii=False) - - st.download_button( - label="๐Ÿ“„ Download Study Plan (JSON)", - data=json_str, - file_name=f"study_plan_{subject.replace(' ', '_')}.json", - mime="application/json", - use_container_width=True, - help="Download your complete study plan with all your progress and streak information" - ) - - st.markdown("---") - st.subheader("๐Ÿ† Completion Certificate") - - # Calculate completion metrics - total_tasks = 0 - completed_tasks = 0 - - for week in plan.get('weekly_schedule', []): - if 'daily_tasks' in week and isinstance(week['daily_tasks'], list): - for day_idx, day_tasks in enumerate(week['daily_tasks']): - if isinstance(day_tasks, list): - total_tasks += len(day_tasks) - for task_idx in range(len(day_tasks)): - task_key = f"task_week{week['week']}_day{day_idx}_task{task_idx}_{subject}" - if st.session_state.get(task_key, False): - completed_tasks += 1 - - completed_weeks = sum(1 for week in plan.get('weekly_schedule', []) if week.get('completed', False)) - total_weeks = plan.get('total_weeks', 0) - completion_percentage = (completed_tasks / total_tasks * 100) if total_tasks > 0 else 0 - - # Show certificate preview - certificate_html = f""" - - - - Certificate - {subject} - - - -
-
-

Certificate of Achievement

-

{subject}

- -
-
{completion_percentage:.1f}%
-
- -
-
-
{completed_tasks}/{total_tasks}
-
Tasks Mastered
-
-
-
{completed_weeks}/{total_weeks}
-
Weeks Completed
-
-
-
{completion_percentage:.1f}%
-
Success Rate
-
-
- -
-
Issued on {datetime.now().strftime("%B %d, %Y")}
-
- -
-
SEAL OF
ACHIEVEMENT
-
-
-
- - - """ + # Fix 4: Remove trailing commas + json_str = re.sub(r',\s*}', '}', json_str) + json_str = re.sub(r',\s*]', ']', json_str) - if certificate_html: - with st.expander("๐ŸŽ–๏ธ View Certificate Preview", expanded=True): - st.components.v1.html(certificate_html, height=800, scrolling=True) - - # Show certificate download options only if 90%+ complete - if completion_percentage >= 90: - st.success("๐ŸŽ‰ **Congratulations! You've unlocked the certificate download!**") - - col1, col2 = st.columns(2) - with col1: - st.download_button( - label="๐ŸŽ–๏ธ Download Certificate (HTML)", - data=certificate_html, - file_name=f"certificate_{subject.replace(' ', '_')}.html", - mime="text/html", - use_container_width=True, - help="Download your certificate as HTML file" - ) - with col2: - st.download_button( - label="๐Ÿ“ Download Certificate (TXT)", - data=f"""CERTIFICATE OF COMPLETION - -Subject: {subject} -Completion: {completion_percentage:.1f}% -Tasks Mastered: {completed_tasks}/{total_tasks} -Weeks Completed: {completed_weeks}/{total_weeks} -Date: {datetime.now().strftime("%B %d, %Y")} - -This certifies successful completion of the {subject} study program. -Generated by AI Study Planner Pro.""", - file_name=f"certificate_{subject.replace(' ', '_')}.txt", - mime="text/plain", - use_container_width=True, - help="Download certificate details as text file" - ) - else: - remaining_percentage = 90 - completion_percentage - st.info(f"๐Ÿ“ˆ **Complete {remaining_percentage:.1f}% more to unlock certificate download!**") - st.progress(completion_percentage / 100) - st.caption(f"Current progress: {completion_percentage:.1f}% (Minimum 90% required for certificate download)") - else: - st.info("๐Ÿ‘ˆ **Generate or load a study plan first to export!**") - - -# ============================================ -# ๐Ÿ”ง DEBUG INFO SECTION -# ============================================ + try: + return json.loads(json_str) + except: + return None -if st.session_state.debug_mode and st.session_state.raw_ai_response: - st.markdown("---") - st.markdown("### ๐Ÿ› Last AI Response (For Debugging)") - st.markdown('
', unsafe_allow_html=True) - st.markdown("**If JSON parsing failed, copy this response:**") - st.markdown('
', unsafe_allow_html=True) - st.text(st.session_state.raw_ai_response[:2000] + "..." if len(st.session_state.raw_ai_response) > 2000 else st.session_state.raw_ai_response) - st.markdown('
', unsafe_allow_html=True) - - if st.button("๐Ÿ“‹ Copy Response for Debugging", key="copy_debug"): - st.code(st.session_state.raw_ai_response, language="text") - st.success("Response shown above. Copy it and share for debugging!") - - st.markdown('
', unsafe_allow_html=True) \ No newline at end of file +def clean_json_text(text): + """ + Clean text for JSON extraction + """ + text = text.strip() + text = re.sub(r'```json\s*', '', text) + text = re.sub(r'```\s*', '', text) + text = re.sub(r'```javascript\s*', '', text) + text = re.sub(r'```python\s*', '', text) + return text + +# Run the test +test_ai_responses() \ No newline at end of file