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('', 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'', 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'', 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('', 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'', 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('', 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")}
-
-
-
-
-
-
-
- """
+ # 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