prasanthr0416 commited on
Commit
feabf04
Β·
verified Β·
1 Parent(s): b7fcd00

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +287 -128
app.py CHANGED
@@ -419,14 +419,7 @@ init_session_state()
419
 
420
  def extract_and_fix_json(text, subject="General"):
421
  """
422
- Extract JSON from text and fix common issues - UPDATED BASED ON ACTUAL AI RESPONSES
423
-
424
- Args:
425
- text (str): Raw text containing JSON
426
- subject (str): Subject name for fallback
427
-
428
- Returns:
429
- dict: Parsed JSON or generic plan
430
  """
431
  if not text or not isinstance(text, str):
432
  if st.session_state.show_debug:
@@ -436,18 +429,32 @@ def extract_and_fix_json(text, subject="General"):
436
  # Store raw response for debug
437
  st.session_state.raw_response = text
438
 
439
- # Clean the text
440
  text = text.strip()
441
 
442
- # Remove markdown code blocks (case insensitive)
443
- text = re.sub(r'```json\s*', '', text, flags=re.IGNORECASE)
444
- text = re.sub(r'```\s*', '', text, flags=re.IGNORECASE)
445
- text = re.sub(r'```javascript\s*', '', text, flags=re.IGNORECASE)
446
- text = re.sub(r'```python\s*', '', text, flags=re.IGNORECASE)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
447
 
448
  # Show cleaned text in debug
449
  if st.session_state.show_debug:
450
- with st.expander("🧹 Cleaned Text (first 500 chars)", expanded=False):
451
  st.code(text[:500] + "..." if len(text) > 500 else text)
452
 
453
  # Find JSON boundaries
@@ -457,6 +464,7 @@ def extract_and_fix_json(text, subject="General"):
457
  if start_idx == -1 or end_idx == -1 or end_idx <= start_idx:
458
  if st.session_state.show_debug:
459
  st.error(f"❌ No JSON found. Start: {start_idx}, End: {end_idx}")
 
460
  return None
461
 
462
  json_str = text[start_idx:end_idx+1]
@@ -464,7 +472,7 @@ def extract_and_fix_json(text, subject="General"):
464
  # Show JSON string in debug
465
  if st.session_state.show_debug:
466
  with st.expander("πŸ” JSON String Being Parsed", expanded=False):
467
- st.code(json_str[:1000] + "..." if len(json_str) > 1000 else json_str)
468
 
469
  try:
470
  # First try: Direct parse
@@ -662,7 +670,7 @@ def create_generic_plan(subject, days_available):
662
 
663
  def generate_study_plan(api_key, **kwargs):
664
  """
665
- Generate study plan using Gemini
666
  """
667
  if not api_key:
668
  st.error("❌ No API key provided")
@@ -674,17 +682,19 @@ def generate_study_plan(api_key, **kwargs):
674
  days_available = kwargs.get('days_available', 60)
675
  hours_per_day = kwargs.get('hours_per_day', 2)
676
  current_level = kwargs.get('current_level', 'Beginner')
 
677
  study_days = kwargs.get('study_days', ["Mon", "Tue", "Wed", "Thu", "Fri"])
678
 
679
  weeks = max(1, days_available // 7)
680
 
681
- # SIMPLIFIED PROMPT - based on what works
682
- prompt = f"""Create a detailed {weeks}-week study plan for: {subject}
683
 
684
  Study schedule: {hours_per_day} hours/day, {len(study_days)} days/week ({', '.join(study_days)})
685
  Current level: {current_level}
 
686
 
687
- Return valid JSON with this exact structure:
688
 
689
  {{
690
  "subject": "{subject}",
@@ -698,54 +708,73 @@ Return valid JSON with this exact structure:
698
  "focus": "Week focus title",
699
  "objectives": ["Objective 1", "Objective 2"],
700
  "daily_tasks": [
701
- ["Task 1 Monday", "Task 2 Monday"],
702
- ["Task 1 Tuesday", "Task 2 Tuesday"],
703
- ["Task 1 Wednesday", "Task 2 Wednesday"],
704
- ["Task 1 Thursday", "Task 2 Thursday"],
705
- ["Task 1 Friday", "Task 2 Friday"]
706
  ],
707
- "day_focus": ["Monday: Intro", "Tuesday: Practice", "Wednesday: Deep Dive", "Thursday: Review", "Friday: Project"],
708
- "week_summary": "Week summary",
709
  "resources": ["Resource 1", "Resource 2"],
710
- "milestone": "Weekly milestone"
711
  }}
712
  ],
713
  "study_tips": ["Tip 1", "Tip 2"],
714
  "success_metrics": ["Metric 1", "Metric 2"]
715
  }}
716
 
717
- IMPORTANT:
718
- 1. Make sure topics_allocation values are NUMBERS (not strings like "10 hours")
719
- 2. Include ALL {weeks} weeks in the weekly_schedule array
720
- 3. Return ONLY JSON, no additional text
721
- 4. Make tasks specific and actionable for {subject}"""
 
 
 
722
 
723
  try:
724
- # Configure API
725
  genai.configure(api_key=api_key)
726
  model = genai.GenerativeModel('gemini-2.5-flash')
727
 
728
  # Show prompt in debug mode
729
  if st.session_state.show_debug:
730
  with st.expander("πŸ“ Prompt Sent to AI", expanded=False):
731
- st.code(prompt[:1000] + "..." if len(prompt) > 1000 else prompt)
732
 
733
- # Generate response
734
  response = model.generate_content(
735
  prompt,
736
  generation_config=genai.GenerationConfig(
737
- max_output_tokens=4000,
738
- temperature=0.7
739
- )
 
 
 
 
 
 
 
 
740
  )
741
 
742
- raw_text = response.text
 
 
 
 
 
743
  st.session_state.raw_response = raw_text
744
 
745
  # Show raw response in debug mode
746
  if st.session_state.show_debug:
747
  with st.expander("πŸ” Raw AI Response", expanded=False):
748
- st.text_area("AI Response", raw_text, height=200, key="ai_response_raw")
 
 
 
749
 
750
  # Extract JSON
751
  plan = extract_and_fix_json(raw_text, subject)
@@ -766,6 +795,7 @@ IMPORTANT:
766
  plan['generated_at'] = datetime.now().isoformat()
767
  plan['total_days'] = days_available
768
  plan['user_level'] = current_level
 
769
 
770
  # Initialize week tracking
771
  for week in plan.get('weekly_schedule', []):
@@ -783,16 +813,26 @@ IMPORTANT:
783
 
784
  return plan
785
  else:
786
- st.warning("⚠️ Could not extract valid JSON from AI response")
787
  if st.session_state.show_debug:
788
- st.info("Falling back to generic plan")
 
 
 
 
789
  return create_generic_plan(subject, days_available)
790
 
791
  except Exception as e:
792
- st.error(f"❌ AI Generation Error: {str(e)}")
 
 
 
 
 
 
793
  if st.session_state.show_debug:
794
  with st.expander("πŸ” Error Details", expanded=False):
795
  st.exception(e)
 
796
  return create_generic_plan(subject, days_available)
797
 
798
  def generate_weekly_test(api_key, week_number, weekly_tasks, subject):
@@ -1188,7 +1228,7 @@ tab1, tab2, tab3, tab4, tab5, tab6 = st.tabs([
1188
 
1189
 
1190
  # ============================================
1191
- # TAB 1: DEFINE GOAL
1192
  # ============================================
1193
 
1194
  with tab1:
@@ -1206,26 +1246,38 @@ with tab1:
1206
  else:
1207
  st.success("βœ… **API Key Loaded**")
1208
 
1209
- # Test API Button
1210
  if st.session_state.api_key and st.session_state.show_debug:
1211
- if st.button("Test API Connection", type="secondary", key="test_api"):
1212
  try:
1213
  genai.configure(api_key=st.session_state.api_key)
1214
  model = genai.GenerativeModel('gemini-2.5-flash')
1215
- test_response = model.generate_content("Say 'API is working'",
1216
- generation_config=genai.GenerationConfig(max_output_tokens=10))
1217
- if test_response.text:
 
 
 
 
 
 
 
 
 
1218
  st.success(f"βœ… **API Connected:** {test_response.text}")
1219
  else:
1220
- st.error("❌ No response from API")
 
1221
  except Exception as e:
1222
  st.error(f"❌ **API Error:** {str(e)}")
 
 
1223
 
1224
  st.markdown("---")
1225
 
1226
  # Option 1: Upload Existing Plan
1227
  st.info("πŸ“€ **Option 1: Upload Existing Plan**")
1228
- uploaded_file = st.file_uploader("Upload saved study plan (JSON)", type=['json'], key="upload_plan")
1229
 
1230
  if uploaded_file is not None and not st.session_state.plan_loaded:
1231
  try:
@@ -1239,19 +1291,46 @@ with tab1:
1239
 
1240
  st.markdown("---")
1241
 
1242
- # Option 2: Create New Plan - SIMPLIFIED
1243
  st.info("🎯 **Option 2: Create New Plan**")
1244
 
1245
  col1, col2 = st.columns([2, 1])
1246
 
1247
  with col1:
1248
- subject = st.text_input(
1249
- "What do you want to learn?",
1250
- placeholder="e.g., Data Science, Python, Web Development...",
1251
- help="Enter the subject or topic",
1252
- key="subject_input"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1253
  )
1254
 
 
 
 
 
 
 
 
 
 
1255
  col1a, col2a = st.columns(2)
1256
  with col1a:
1257
  hours_per_day = st.slider(
@@ -1259,7 +1338,7 @@ with tab1:
1259
  min_value=1,
1260
  max_value=8,
1261
  value=2,
1262
- key="hours_slider"
1263
  )
1264
 
1265
  with col2a:
@@ -1268,14 +1347,14 @@ with tab1:
1268
  min_value=7,
1269
  max_value=365,
1270
  value=60,
1271
- key="days_slider"
1272
  )
1273
 
1274
  study_days = st.multiselect(
1275
  "Study days:",
1276
  ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
1277
  default=["Mon", "Tue", "Wed", "Thu", "Fri"],
1278
- key="study_days_select"
1279
  )
1280
 
1281
  with col2:
@@ -1283,17 +1362,25 @@ with tab1:
1283
  "Your level:",
1284
  ["Beginner", "Intermediate", "Advanced"],
1285
  index=0,
1286
- key="level_select"
 
 
 
 
 
 
 
 
1287
  )
1288
 
1289
  st.markdown("---")
1290
 
1291
  # Generate Plan Button
1292
- if st.button("πŸš€ Generate AI Study Plan", type="primary", use_container_width=True, key="generate_plan_btn"):
1293
  if not st.session_state.api_key:
1294
  st.error("⚠️ **API Key Required:** Please set GEMINI_API_KEY in Hugging Face Secrets.")
1295
- elif not subject:
1296
- st.error("⚠️ Please enter a subject/topic")
1297
  elif not study_days:
1298
  st.error("⚠️ Please select at least one study day")
1299
  else:
@@ -1306,12 +1393,13 @@ with tab1:
1306
  len(study_days)
1307
  )
1308
 
1309
- # Create plan data
1310
  plan_data = {
1311
  'subject': subject,
1312
  'days_available': days_available,
1313
  'hours_per_day': hours_per_day,
1314
  'current_level': current_level,
 
1315
  'study_days': study_days
1316
  }
1317
 
@@ -1371,7 +1459,7 @@ with tab1:
1371
  st.metric("Plan Type", plan_type)
1372
 
1373
  with st.expander("πŸ“‹ Raw AI Response", expanded=False):
1374
- st.text_area("Full Response", st.session_state.raw_response, height=300, key="raw_response_display")
1375
 
1376
  if st.session_state.study_plan:
1377
  with st.expander("πŸ“Š Parsed Plan Structure", expanded=False):
@@ -2157,7 +2245,7 @@ with tab4:
2157
  st.info("πŸ‘ˆ **Generate a study plan first to see analytics!**")
2158
 
2159
  # ============================================
2160
- # TAB 5: TESTS (Updated with proper JSON extraction)
2161
  # ============================================
2162
 
2163
  with tab5:
@@ -2188,44 +2276,54 @@ with tab5:
2188
  completed_week_tasks += 1
2189
 
2190
  completion_percentage = (completed_week_tasks / week_tasks * 100) if week_tasks > 0 else 0
2191
- test_available = completion_percentage >= 50 # Lower threshold for testing
2192
-
2193
- col1, col2, col3 = st.columns([3, 1, 1])
2194
-
2195
- with col1:
2196
- st.markdown(f"### Week {week_num}: {week.get('focus', '')}")
2197
- st.caption(f"Completion: {completion_percentage:.0f}%")
2198
 
2199
- with col2:
2200
- test_key = f"week_{week_num}_test"
2201
- test_taken = test_key in st.session_state.test_results
2202
 
2203
- if test_taken:
2204
- score = st.session_state.test_results[test_key]['percentage']
2205
- st.metric("Score", f"{score:.0f}%")
2206
-
2207
- with col3:
2208
- if test_available:
2209
- if st.button(f"πŸ“ Start Test", key=f"test_week{week_num}", use_container_width=True):
2210
- with st.spinner(f"Generating test..."):
2211
- test_data = generate_weekly_test(
2212
- st.session_state.api_key,
2213
- week_num,
2214
- week.get('daily_tasks', []),
2215
- subject
2216
- )
2217
-
2218
- if test_data and 'questions' in test_data:
2219
- st.session_state.current_test = week_num
2220
- st.session_state.test_questions = test_data['questions']
2221
- st.session_state.test_completed = False
2222
- st.session_state.user_test_answers = {}
2223
- st.rerun()
2224
- else:
2225
- st.error("Failed to generate test")
2226
- else:
2227
- st.button(f"πŸ”’", disabled=True, use_container_width=True)
2228
- st.caption(f"Complete 50%")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2229
 
2230
  # Test Taking View
2231
  elif st.session_state.current_test and not st.session_state.test_completed:
@@ -2233,27 +2331,58 @@ with tab5:
2233
  test_questions = st.session_state.test_questions
2234
 
2235
  st.markdown(f"### πŸ“ Week {week_num} Test")
 
2236
 
2237
  for i, question in enumerate(test_questions):
2238
- st.markdown(f"**Q{i+1}: {question['question']}**")
2239
-
2240
- current_answer = st.session_state.user_test_answers.get(str(i), "")
2241
-
2242
- cols = st.columns(2)
2243
- for opt_idx, option in enumerate(['A', 'B', 'C', 'D']):
2244
- col = cols[opt_idx % 2]
2245
- with col:
2246
- if st.button(
2247
- f"{option}. {question['options'][option]}",
2248
- key=f"q{i}_opt{option}",
2249
- use_container_width=True
2250
- ):
2251
- st.session_state.user_test_answers[str(i)] = option
2252
- st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2253
 
2254
- if st.button("Submit Test", type="primary"):
2255
- st.session_state.test_completed = True
2256
- st.rerun()
 
 
 
 
 
 
2257
 
2258
  # Test Results View
2259
  elif st.session_state.test_completed:
@@ -2281,20 +2410,50 @@ with tab5:
2281
  st.success(f"## 🎯 Test Score: {score_percentage:.1f}%")
2282
  st.info(f"**{correct_count} out of {len(test_questions)} correct**")
2283
 
2284
- with st.expander("Review Answers"):
 
 
 
 
 
 
 
 
 
2285
  for i, question in enumerate(test_questions):
2286
  user_answer = user_answers.get(str(i), "Not answered")
2287
  is_correct = user_answer == question['correct_answer']
2288
 
2289
- st.markdown(f"**Q{i+1}: {question['question']}**")
2290
- st.write(f"Your answer: {user_answer}")
2291
- st.write(f"Correct answer: {question['correct_answer']}")
2292
- st.write(f"Explanation: {question['explanation']}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2293
  st.markdown("---")
2294
 
2295
- if st.button("Back to Tests"):
 
 
 
2296
  st.session_state.current_test = None
2297
  st.session_state.test_completed = False
 
 
2298
  st.rerun()
2299
 
2300
  else:
 
419
 
420
  def extract_and_fix_json(text, subject="General"):
421
  """
422
+ Extract JSON from text and fix common issues
 
 
 
 
 
 
 
423
  """
424
  if not text or not isinstance(text, str):
425
  if st.session_state.show_debug:
 
429
  # Store raw response for debug
430
  st.session_state.raw_response = text
431
 
432
+ # Clean the text - more aggressive cleaning
433
  text = text.strip()
434
 
435
+ # Check if response indicates safety blocking
436
+ if "safety" in text.lower() or "blocked" in text.lower() or "not allowed" in text.lower():
437
+ if st.session_state.show_debug:
438
+ st.error("❌ Response indicates safety blocking")
439
+ return None
440
+
441
+ # Remove ALL markdown code blocks and any surrounding text
442
+ text = re.sub(r'.*?```(?:json)?\s*', '', text, flags=re.IGNORECASE | re.DOTALL)
443
+ text = re.sub(r'```.*', '', text, flags=re.IGNORECASE | re.DOTALL)
444
+
445
+ # Remove any non-JSON text before first {
446
+ first_brace = text.find('{')
447
+ if first_brace > 0:
448
+ text = text[first_brace:]
449
+
450
+ # Remove any non-JSON text after last }
451
+ last_brace = text.rfind('}')
452
+ if last_brace != -1:
453
+ text = text[:last_brace+1]
454
 
455
  # Show cleaned text in debug
456
  if st.session_state.show_debug:
457
+ with st.expander("🧹 Cleaned Text", expanded=False):
458
  st.code(text[:500] + "..." if len(text) > 500 else text)
459
 
460
  # Find JSON boundaries
 
464
  if start_idx == -1 or end_idx == -1 or end_idx <= start_idx:
465
  if st.session_state.show_debug:
466
  st.error(f"❌ No JSON found. Start: {start_idx}, End: {end_idx}")
467
+ st.info(f"Text preview: {text[:200]}...")
468
  return None
469
 
470
  json_str = text[start_idx:end_idx+1]
 
472
  # Show JSON string in debug
473
  if st.session_state.show_debug:
474
  with st.expander("πŸ” JSON String Being Parsed", expanded=False):
475
+ st.code(json_str[:800] + "..." if len(json_str) > 800 else json_str)
476
 
477
  try:
478
  # First try: Direct parse
 
670
 
671
  def generate_study_plan(api_key, **kwargs):
672
  """
673
+ Generate study plan using Gemini with safety handling
674
  """
675
  if not api_key:
676
  st.error("❌ No API key provided")
 
682
  days_available = kwargs.get('days_available', 60)
683
  hours_per_day = kwargs.get('hours_per_day', 2)
684
  current_level = kwargs.get('current_level', 'Beginner')
685
+ intensity = kwargs.get('intensity', 'Moderate')
686
  study_days = kwargs.get('study_days', ["Mon", "Tue", "Wed", "Thu", "Fri"])
687
 
688
  weeks = max(1, days_available // 7)
689
 
690
+ # IMPROVED PROMPT with safety considerations
691
+ prompt = f"""Create a detailed {weeks}-week study plan for learning: {subject}
692
 
693
  Study schedule: {hours_per_day} hours/day, {len(study_days)} days/week ({', '.join(study_days)})
694
  Current level: {current_level}
695
+ Intensity: {intensity}
696
 
697
+ Return ONLY valid JSON with this exact structure:
698
 
699
  {{
700
  "subject": "{subject}",
 
708
  "focus": "Week focus title",
709
  "objectives": ["Objective 1", "Objective 2"],
710
  "daily_tasks": [
711
+ ["Task 1 for Monday", "Task 2 for Monday"],
712
+ ["Task 1 for Tuesday", "Task 2 for Tuesday"],
713
+ ["Task 1 for Wednesday", "Task 2 for Wednesday"],
714
+ ["Task 1 for Thursday", "Task 2 for Thursday"],
715
+ ["Task 1 for Friday", "Task 2 for Friday"]
716
  ],
717
+ "day_focus": ["Monday: Introduction", "Tuesday: Practice", "Wednesday: Deep Dive", "Thursday: Review", "Friday: Project"],
718
+ "week_summary": "Brief summary of week's learning objectives",
719
  "resources": ["Resource 1", "Resource 2"],
720
+ "milestone": "What to achieve by week's end"
721
  }}
722
  ],
723
  "study_tips": ["Tip 1", "Tip 2"],
724
  "success_metrics": ["Metric 1", "Metric 2"]
725
  }}
726
 
727
+ IMPORTANT RULES:
728
+ 1. Return ONLY JSON, no additional text
729
+ 2. topics_allocation values must be NUMBERS (e.g., 10, not "10 hours")
730
+ 3. Include ALL {weeks} weeks in weekly_schedule array
731
+ 4. Make tasks specific, practical, and educational
732
+ 5. Keep content appropriate for all ages
733
+ 6. Focus on learning and skill development
734
+ 7. Ensure JSON is valid and complete"""
735
 
736
  try:
737
+ # Configure API with safety settings
738
  genai.configure(api_key=api_key)
739
  model = genai.GenerativeModel('gemini-2.5-flash')
740
 
741
  # Show prompt in debug mode
742
  if st.session_state.show_debug:
743
  with st.expander("πŸ“ Prompt Sent to AI", expanded=False):
744
+ st.code(prompt[:800] + "..." if len(prompt) > 800 else prompt)
745
 
746
+ # Generate response with safety settings
747
  response = model.generate_content(
748
  prompt,
749
  generation_config=genai.GenerationConfig(
750
+ max_output_tokens=3000, # Reduced for safety
751
+ temperature=0.7,
752
+ top_p=0.95,
753
+ top_k=40
754
+ ),
755
+ safety_settings=[
756
+ {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
757
+ {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
758
+ {"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
759
+ {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"}
760
+ ]
761
  )
762
 
763
+ # SAFE way to get response text
764
+ if response and hasattr(response, 'text'):
765
+ raw_text = response.text
766
+ else:
767
+ raw_text = ""
768
+
769
  st.session_state.raw_response = raw_text
770
 
771
  # Show raw response in debug mode
772
  if st.session_state.show_debug:
773
  with st.expander("πŸ” Raw AI Response", expanded=False):
774
+ if raw_text:
775
+ st.text_area("AI Response", raw_text, height=200, key="ai_response_raw_gen")
776
+ else:
777
+ st.warning("No response text received")
778
 
779
  # Extract JSON
780
  plan = extract_and_fix_json(raw_text, subject)
 
795
  plan['generated_at'] = datetime.now().isoformat()
796
  plan['total_days'] = days_available
797
  plan['user_level'] = current_level
798
+ plan['intensity'] = intensity
799
 
800
  # Initialize week tracking
801
  for week in plan.get('weekly_schedule', []):
 
813
 
814
  return plan
815
  else:
 
816
  if st.session_state.show_debug:
817
+ st.warning("⚠️ Could not extract valid JSON from AI response")
818
+ if not raw_text:
819
+ st.error("❌ Empty response from AI")
820
+ else:
821
+ st.info("Falling back to generic plan")
822
  return create_generic_plan(subject, days_available)
823
 
824
  except Exception as e:
825
+ error_msg = str(e)
826
+ st.error(f"❌ AI Generation Error: {error_msg}")
827
+
828
+ # Handle safety errors
829
+ if "safety" in error_msg.lower() or "finish_reason" in error_msg.lower():
830
+ st.warning("πŸ”’ **Safety restriction triggered.** Try a different subject or simpler request.")
831
+
832
  if st.session_state.show_debug:
833
  with st.expander("πŸ” Error Details", expanded=False):
834
  st.exception(e)
835
+
836
  return create_generic_plan(subject, days_available)
837
 
838
  def generate_weekly_test(api_key, week_number, weekly_tasks, subject):
 
1228
 
1229
 
1230
  # ============================================
1231
+ # TAB 1: DEFINE GOAL - WITH DROPDOWN OPTIONS
1232
  # ============================================
1233
 
1234
  with tab1:
 
1246
  else:
1247
  st.success("βœ… **API Key Loaded**")
1248
 
1249
+ # Test API Button with SAFETY FIX
1250
  if st.session_state.api_key and st.session_state.show_debug:
1251
+ if st.button("Test API Connection", type="secondary", key="test_api_tab1"):
1252
  try:
1253
  genai.configure(api_key=st.session_state.api_key)
1254
  model = genai.GenerativeModel('gemini-2.5-flash')
1255
+
1256
+ # Use a safer, more neutral prompt
1257
+ test_response = model.generate_content(
1258
+ "Please respond with just the word 'Connected'",
1259
+ generation_config=genai.GenerationConfig(
1260
+ max_output_tokens=10,
1261
+ temperature=0.1
1262
+ )
1263
+ )
1264
+
1265
+ # Safely get response text
1266
+ if test_response and hasattr(test_response, 'text') and test_response.text:
1267
  st.success(f"βœ… **API Connected:** {test_response.text}")
1268
  else:
1269
+ st.error("❌ No valid response from API")
1270
+
1271
  except Exception as e:
1272
  st.error(f"❌ **API Error:** {str(e)}")
1273
+ if "safety" in str(e).lower():
1274
+ st.info("πŸ”’ **Safety restriction detected.** Try a different subject or rephrase your request.")
1275
 
1276
  st.markdown("---")
1277
 
1278
  # Option 1: Upload Existing Plan
1279
  st.info("πŸ“€ **Option 1: Upload Existing Plan**")
1280
+ uploaded_file = st.file_uploader("Upload saved study plan (JSON)", type=['json'], key="upload_plan_tab1")
1281
 
1282
  if uploaded_file is not None and not st.session_state.plan_loaded:
1283
  try:
 
1291
 
1292
  st.markdown("---")
1293
 
1294
+ # Option 2: Create New Plan - WITH DROPDOWN + CUSTOM
1295
  st.info("🎯 **Option 2: Create New Plan**")
1296
 
1297
  col1, col2 = st.columns([2, 1])
1298
 
1299
  with col1:
1300
+ # Subject selection: Dropdown OR Custom input
1301
+ subject_options = [
1302
+ "Select a subject or enter custom...",
1303
+ "Data Science & Machine Learning",
1304
+ "Python Programming",
1305
+ "Web Development (Full Stack)",
1306
+ "Artificial Intelligence",
1307
+ "Cloud Computing (AWS/Azure/GCP)",
1308
+ "Cybersecurity",
1309
+ "Mobile App Development",
1310
+ "Digital Marketing",
1311
+ "Business Analytics",
1312
+ "Language Learning (Spanish/French/English)",
1313
+ "Project Management",
1314
+ "Graphic Design",
1315
+ "Game Development"
1316
+ ]
1317
+
1318
+ selected_option = st.selectbox(
1319
+ "Choose a subject or enter custom:",
1320
+ options=subject_options,
1321
+ index=0,
1322
+ key="subject_select_tab1"
1323
  )
1324
 
1325
+ if selected_option == "Select a subject or enter custom...":
1326
+ subject = st.text_input(
1327
+ "Or enter your own subject:",
1328
+ placeholder="e.g., Quantum Computing, Ethical Hacking, Music Production...",
1329
+ key="custom_subject_tab1"
1330
+ )
1331
+ else:
1332
+ subject = selected_option
1333
+
1334
  col1a, col2a = st.columns(2)
1335
  with col1a:
1336
  hours_per_day = st.slider(
 
1338
  min_value=1,
1339
  max_value=8,
1340
  value=2,
1341
+ key="hours_slider_tab1"
1342
  )
1343
 
1344
  with col2a:
 
1347
  min_value=7,
1348
  max_value=365,
1349
  value=60,
1350
+ key="days_slider_tab1"
1351
  )
1352
 
1353
  study_days = st.multiselect(
1354
  "Study days:",
1355
  ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
1356
  default=["Mon", "Tue", "Wed", "Thu", "Fri"],
1357
+ key="study_days_select_tab1"
1358
  )
1359
 
1360
  with col2:
 
1362
  "Your level:",
1363
  ["Beginner", "Intermediate", "Advanced"],
1364
  index=0,
1365
+ key="level_select_tab1"
1366
+ )
1367
+
1368
+ # Add study intensity
1369
+ intensity = st.select_slider(
1370
+ "Study intensity:",
1371
+ options=["Casual", "Moderate", "Intensive"],
1372
+ value="Moderate",
1373
+ key="intensity_slider_tab1"
1374
  )
1375
 
1376
  st.markdown("---")
1377
 
1378
  # Generate Plan Button
1379
+ if st.button("πŸš€ Generate AI Study Plan", type="primary", use_container_width=True, key="generate_plan_btn_tab1"):
1380
  if not st.session_state.api_key:
1381
  st.error("⚠️ **API Key Required:** Please set GEMINI_API_KEY in Hugging Face Secrets.")
1382
+ elif not subject or subject == "Select a subject or enter custom...":
1383
+ st.error("⚠️ Please enter or select a subject")
1384
  elif not study_days:
1385
  st.error("⚠️ Please select at least one study day")
1386
  else:
 
1393
  len(study_days)
1394
  )
1395
 
1396
+ # Create plan data with intensity
1397
  plan_data = {
1398
  'subject': subject,
1399
  'days_available': days_available,
1400
  'hours_per_day': hours_per_day,
1401
  'current_level': current_level,
1402
+ 'intensity': intensity,
1403
  'study_days': study_days
1404
  }
1405
 
 
1459
  st.metric("Plan Type", plan_type)
1460
 
1461
  with st.expander("πŸ“‹ Raw AI Response", expanded=False):
1462
+ st.text_area("Full Response", st.session_state.raw_response, height=300, key="raw_response_display_tab1")
1463
 
1464
  if st.session_state.study_plan:
1465
  with st.expander("πŸ“Š Parsed Plan Structure", expanded=False):
 
2245
  st.info("πŸ‘ˆ **Generate a study plan first to see analytics!**")
2246
 
2247
  # ============================================
2248
+ # TAB 5: TESTS - FIXED DUPLICATE BUTTON ERROR
2249
  # ============================================
2250
 
2251
  with tab5:
 
2276
  completed_week_tasks += 1
2277
 
2278
  completion_percentage = (completed_week_tasks / week_tasks * 100) if week_tasks > 0 else 0
2279
+ test_available = completion_percentage >= 50
 
 
 
 
 
 
2280
 
2281
+ with st.container():
2282
+ col1, col2, col3 = st.columns([3, 1, 1])
 
2283
 
2284
+ with col1:
2285
+ st.markdown(f"**Week {week_num}:** {week.get('focus', '')[:50]}...")
2286
+ st.caption(f"Completion: {completion_percentage:.0f}% β€’ Tasks: {completed_week_tasks}/{week_tasks}")
2287
+
2288
+ with col2:
2289
+ test_key = f"week_{week_num}_test"
2290
+ test_taken = test_key in st.session_state.test_results
2291
+
2292
+ if test_taken:
2293
+ score = st.session_state.test_results[test_key]['percentage']
2294
+ st.metric("", f"{score:.0f}%", label_visibility="collapsed")
2295
+
2296
+ with col3:
2297
+ if test_available:
2298
+ if st.button(f"πŸ“ Start Test",
2299
+ key=f"start_test_week_{week_num}_{subject.replace(' ', '_')}",
2300
+ use_container_width=True):
2301
+ with st.spinner(f"Generating test for Week {week_num}..."):
2302
+ test_data = generate_weekly_test(
2303
+ st.session_state.api_key,
2304
+ week_num,
2305
+ week.get('daily_tasks', []),
2306
+ subject
2307
+ )
2308
+
2309
+ if test_data and 'questions' in test_data:
2310
+ st.session_state.current_test = week_num
2311
+ st.session_state.test_questions = test_data['questions']
2312
+ st.session_state.test_completed = False
2313
+ st.session_state.user_test_answers = {}
2314
+ st.rerun()
2315
+ else:
2316
+ st.error("Failed to generate test. Try again.")
2317
+ else:
2318
+ # FIXED: Added unique key and disabled properly
2319
+ st.button(f"πŸ”’ Locked",
2320
+ key=f"locked_test_week_{week_num}_{subject.replace(' ', '_')}",
2321
+ disabled=True,
2322
+ use_container_width=True)
2323
+ if completion_percentage < 50:
2324
+ st.caption(f"Complete 50% to unlock")
2325
+
2326
+ st.markdown("---")
2327
 
2328
  # Test Taking View
2329
  elif st.session_state.current_test and not st.session_state.test_completed:
 
2331
  test_questions = st.session_state.test_questions
2332
 
2333
  st.markdown(f"### πŸ“ Week {week_num} Test")
2334
+ st.info(f"Answer all {len(test_questions)} questions. Click on your chosen answer.")
2335
 
2336
  for i, question in enumerate(test_questions):
2337
+ with st.container():
2338
+ st.markdown(f"**Q{i+1}. {question['question']}**")
2339
+
2340
+ current_answer = st.session_state.user_test_answers.get(str(i), "")
2341
+
2342
+ # Create 2 columns for options
2343
+ col_a, col_b = st.columns(2)
2344
+
2345
+ with col_a:
2346
+ # Option A and B
2347
+ for option in ['A', 'B']:
2348
+ option_text = question['options'][option]
2349
+ is_selected = (current_answer == option)
2350
+
2351
+ if st.button(
2352
+ f"{option}. {option_text[:80]}..." if len(option_text) > 80 else f"{option}. {option_text}",
2353
+ key=f"test_q{week_num}_{i}_opt{option}",
2354
+ use_container_width=True,
2355
+ type="primary" if is_selected else "secondary"
2356
+ ):
2357
+ st.session_state.user_test_answers[str(i)] = option
2358
+ st.rerun()
2359
+
2360
+ with col_b:
2361
+ # Option C and D
2362
+ for option in ['C', 'D']:
2363
+ option_text = question['options'][option]
2364
+ is_selected = (current_answer == option)
2365
+
2366
+ if st.button(
2367
+ f"{option}. {option_text[:80]}..." if len(option_text) > 80 else f"{option}. {option_text}",
2368
+ key=f"test_q{week_num}_{i}_opt{option}",
2369
+ use_container_width=True,
2370
+ type="primary" if is_selected else "secondary"
2371
+ ):
2372
+ st.session_state.user_test_answers[str(i)] = option
2373
+ st.rerun()
2374
+
2375
+ st.markdown("---")
2376
 
2377
+ # Submit button
2378
+ col_submit1, col_submit2, col_submit3 = st.columns([1, 2, 1])
2379
+ with col_submit2:
2380
+ if st.button("πŸ“€ Submit Test",
2381
+ type="primary",
2382
+ key=f"submit_test_week_{week_num}",
2383
+ use_container_width=True):
2384
+ st.session_state.test_completed = True
2385
+ st.rerun()
2386
 
2387
  # Test Results View
2388
  elif st.session_state.test_completed:
 
2410
  st.success(f"## 🎯 Test Score: {score_percentage:.1f}%")
2411
  st.info(f"**{correct_count} out of {len(test_questions)} correct**")
2412
 
2413
+ # Performance feedback
2414
+ if score_percentage >= 80:
2415
+ st.balloons()
2416
+ st.success("**Excellent!** You have a strong understanding of this week's material.")
2417
+ elif score_percentage >= 60:
2418
+ st.success("**Good job!** You understand the main concepts.")
2419
+ else:
2420
+ st.warning("**Review needed.** Consider revisiting this week's material.")
2421
+
2422
+ with st.expander("πŸ“‹ Review Answers", expanded=False):
2423
  for i, question in enumerate(test_questions):
2424
  user_answer = user_answers.get(str(i), "Not answered")
2425
  is_correct = user_answer == question['correct_answer']
2426
 
2427
+ st.markdown(f"**Q{i+1}. {question['question']}**")
2428
+
2429
+ # Show user's answer with color
2430
+ if user_answer in question['options']:
2431
+ user_answer_text = f"{user_answer}. {question['options'][user_answer]}"
2432
+ if is_correct:
2433
+ st.success(f"βœ… **Your answer:** {user_answer_text}")
2434
+ else:
2435
+ st.error(f"❌ **Your answer:** {user_answer_text}")
2436
+ else:
2437
+ st.warning("⏸️ **Your answer:** Not answered")
2438
+
2439
+ # Show correct answer
2440
+ correct_answer_text = f"{question['correct_answer']}. {question['options'][question['correct_answer']]}"
2441
+ st.info(f"πŸ“š **Correct answer:** {correct_answer_text}")
2442
+
2443
+ # Show explanation
2444
+ if question.get('explanation'):
2445
+ st.markdown(f"πŸ’‘ **Explanation:** {question['explanation']}")
2446
+
2447
  st.markdown("---")
2448
 
2449
+ # Back button
2450
+ if st.button("← Back to Test List",
2451
+ key=f"back_from_test_{week_num}",
2452
+ use_container_width=True):
2453
  st.session_state.current_test = None
2454
  st.session_state.test_completed = False
2455
+ st.session_state.test_questions = []
2456
+ st.session_state.user_test_answers = {}
2457
  st.rerun()
2458
 
2459
  else: