prasanthr0416 commited on
Commit
077e236
Β·
verified Β·
1 Parent(s): a655f38

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +840 -176
app.py CHANGED
@@ -1,57 +1,392 @@
1
  import streamlit as st
2
- import google.generativeai as genai
3
  import json
4
- import re
 
 
 
5
  import os
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
- # Set your API key
8
- os.environ["GEMINI_API_KEY"] = "your-api-key-here" # Replace with your actual key
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
- def test_ai_response(subject, topics=[], days_available=60, hours_per_day=2, study_days=["Mon", "Tue", "Wed", "Thu", "Fri"], current_level="Beginner"):
11
- """Test what the AI model actually returns"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
- api_key = os.environ.get("GEMINI_API_KEY")
14
  if not api_key:
15
- st.error("❌ Please set GEMINI_API_KEY environment variable")
16
  return None
17
 
18
  try:
19
  # Configure API
20
  genai.configure(api_key=api_key)
21
- model = genai.GenerativeModel('gemini-2.5-flash') # Or gemini-1.5-flash
22
-
23
- # Sanitize subject
24
- subject_clean = subject.lower().strip()
25
- subject_clean = subject_clean.title()
26
 
27
- # Calculate weeks
28
- weeks = max(1, days_available // 7)
 
 
 
 
 
29
  study_days_count = len(study_days)
 
 
 
 
30
 
31
  # Topics text
32
  topics_text = ""
33
- if topics:
34
- topics_text = f"SPECIFIC TOPICS TO COVER: {', '.join(topics)}"
35
 
36
- # Your exact prompt
37
- prompt = f"""Create a comprehensive, practical study plan for learning: {subject_clean}
38
 
 
39
  {topics_text}
40
- LEARNING GOAL: Master {subject_clean}
41
  DURATION: {weeks} weeks ({days_available} days)
42
- STUDY SCHEDULE: {hours_per_day} hours/day, {study_days_count} days/week ({', '.join(study_days)})
43
- CURRENT LEVEL: {current_level}
44
 
45
- IMPORTANT RULES:
46
- 1. Return ONLY valid JSON format
47
- 2. NO markdown, NO explanations, NO comments
48
- 3. NO emojis in the JSON
49
- 4. NO trailing commas
50
- 5. Ensure proper JSON syntax
 
51
 
52
  REQUIRED JSON STRUCTURE:
53
  {{
54
- "subject": "{subject_clean}",
55
  "total_weeks": {weeks},
56
  "daily_hours": {hours_per_day},
57
  "study_days": {json.dumps(study_days)},
@@ -64,7 +399,7 @@ REQUIRED JSON STRUCTURE:
64
  "weekly_schedule": [
65
  {{
66
  "week": 1,
67
- "focus": "Week 1: Introduction to {subject_clean}",
68
  "objectives": ["Understand basic concepts", "Setup learning environment", "Complete first exercises"],
69
  "daily_tasks": [
70
  ["Read introduction chapter", "Watch tutorial video"],
@@ -74,183 +409,512 @@ REQUIRED JSON STRUCTURE:
74
  ["Weekly review", "Plan next week"]
75
  ],
76
  "day_focus": ["Introduction", "Practice", "Review", "Application", "Planning"],
77
- "week_summary": "This week focuses on building foundational knowledge of {subject_clean}.",
78
  "resources": ["Online tutorials", "Practice exercises", "Reference materials"],
79
- "milestone": "Complete basic understanding of {subject_clean}"
80
  }}
81
  ],
82
  "study_tips": ["Study regularly", "Practice daily", "Review weekly", "Ask questions when stuck"],
83
  "success_metrics": ["Complete all weekly tasks", "Understand core concepts", "Apply knowledge practically"]
84
  }}
85
 
86
- For each week in weekly_schedule:
87
- - Create {weeks} weeks total
88
- - Each week must have 5 days of daily_tasks (2-3 tasks per day)
89
- - Make tasks specific and actionable
90
- - Include real learning objectives
91
- - Cover topics: {', '.join(topics) if topics else 'all essential topics'}
92
 
93
- Return ONLY the JSON object, nothing else."""
94
 
95
- st.markdown("### πŸ“€ Sending this prompt to AI:")
96
- st.code(prompt[:1000] + "..." if len(prompt) > 1000 else prompt, language="text")
97
 
98
  # Generate response
99
- with st.spinner("πŸ€– Asking AI..."):
100
- response = model.generate_content(
101
- prompt,
102
- generation_config=genai.GenerationConfig(
103
- max_output_tokens=4000,
104
- temperature=0.7
105
- )
106
- )
 
 
 
 
 
 
107
 
108
- # Get the raw response
109
  if hasattr(response, 'text') and response.text:
110
  raw_response = response.text
 
111
 
112
- st.markdown("### πŸ“₯ Raw AI Response Received:")
113
- st.text_area("Full Response:", raw_response, height=300)
 
114
 
115
- # Analyze the response
116
- st.markdown("### πŸ” Response Analysis:")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
- # Check for common patterns
119
- has_json_blocks = "```json" in raw_response or "```" in raw_response
120
- has_explanations = any(phrase in raw_response.lower() for phrase in [
121
- "here is", "here's", "the study plan", "i have", "created", "json format",
122
- "please note", "this plan", "you can", "remember"
123
- ])
124
- starts_with_json = raw_response.strip().startswith("{")
125
- ends_with_json = raw_response.strip().endswith("}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
 
127
- col1, col2, col3, col4 = st.columns(4)
128
- with col1:
129
- st.metric("Length", f"{len(raw_response)} chars")
130
- with col2:
131
- st.metric("Markdown", "βœ…" if has_json_blocks else "❌")
132
- with col3:
133
- st.metric("Explanations", "βœ…" if has_explanations else "❌")
134
- with col4:
135
- st.metric("Valid JSON", "βœ…" if starts_with_json and ends_with_json else "❌")
136
 
137
- # Show first and last 200 characters
138
- st.markdown("#### πŸ“Š First 200 characters:")
139
- st.code(raw_response[:200], language="text")
140
 
141
- st.markdown("#### πŸ“Š Last 200 characters:")
142
- st.code(raw_response[-200:], language="text")
 
 
 
 
 
143
 
144
- # Try to extract JSON
145
- st.markdown("### πŸ› οΈ Attempting JSON Extraction:")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
 
147
- # Method 1: Direct parse
148
- try:
149
- parsed = json.loads(raw_response)
150
- st.success("βœ… Method 1: Direct JSON.parse() succeeded!")
151
- st.json(parsed)
152
- return raw_response
153
- except json.JSONDecodeError as e:
154
- st.error(f"❌ Method 1 failed: {e}")
155
 
156
- # Method 2: Remove markdown
157
- cleaned = raw_response.replace("```json", "").replace("```", "").strip()
158
- try:
159
- parsed = json.loads(cleaned)
160
- st.success("βœ… Method 2: After removing markdown succeeded!")
161
- st.json(parsed)
162
- return raw_response
163
- except json.JSONDecodeError as e:
164
- st.error(f"❌ Method 2 failed: {e}")
165
 
166
- # Method 3: Extract between first { and last }
167
- start = raw_response.find('{')
168
- end = raw_response.rfind('}') + 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
- if start != -1 and end != -1:
171
- extracted = raw_response[start:end]
172
- st.code(f"Extracted JSON (chars {start} to {end}):\n{extracted[:500]}...", language="text")
173
-
174
- try:
175
- parsed = json.loads(extracted)
176
- st.success("βœ… Method 3: Extract between {{}} succeeded!")
177
- st.json(parsed)
178
- return raw_response
179
- except json.JSONDecodeError as e:
180
- st.error(f"❌ Method 3 failed: {e}")
181
-
182
- # Show where the error is
183
- st.markdown("#### πŸ› JSON Error Analysis:")
184
- try:
185
- # Try to find the problematic line
186
- lines = extracted.split('\n')
187
- for i, line in enumerate(lines):
188
- try:
189
- test = json.loads('{' + line + '}')
190
- except:
191
- st.warning(f"Problematic line {i+1}: {line[:100]}")
192
- except:
193
- pass
194
 
195
- # Save the raw response for analysis
196
- save_response = st.checkbox("πŸ’Ύ Save this response for analysis?")
197
- if save_response:
198
- with open(f"ai_response_{subject.replace(' ', '_')}.txt", "w") as f:
199
- f.write(f"PROMPT:\n{prompt}\n\n" + "="*80 + "\n\n" + f"RESPONSE:\n{raw_response}")
200
- st.success(f"βœ… Response saved to ai_response_{subject.replace(' ', '_')}.txt")
 
 
 
 
 
 
 
 
201
 
202
- return raw_response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
  else:
204
- st.error("❌ Empty response from AI")
205
- return None
206
-
207
- except Exception as e:
208
- st.error(f"❌ API Error: {str(e)}")
209
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
 
211
- # Streamlit app for testing
212
- st.title("πŸ” AI Response Debugger")
213
- st.markdown("Test what the Gemini AI actually returns for your study plan prompt")
214
 
215
- subject = st.text_input("Subject/Topic:", "Python Programming")
216
- topics_input = st.text_area("Topics (comma-separated):", "Variables, Functions, Loops, Data Structures")
217
- topics = [t.strip() for t in topics_input.split(",") if t.strip()]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
 
219
- col1, col2 = st.columns(2)
220
- with col1:
221
- days = st.slider("Days available:", 7, 365, 60)
222
- with col2:
223
- hours = st.slider("Hours per day:", 1, 8, 2)
224
 
225
- study_days = st.multiselect(
226
- "Study days:",
227
- ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
228
- default=["Mon", "Tue", "Wed", "Thu", "Fri"]
229
- )
 
 
 
230
 
231
- current_level = st.selectbox(
232
- "Current Level:",
233
- ["Beginner", "Intermediate", "Advanced"],
234
- index=0
235
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
 
237
- if st.button("πŸš€ Test AI Response", type="primary"):
238
- test_ai_response(subject, topics, days, hours, study_days, current_level)
239
-
240
- st.markdown("---")
241
- st.markdown("### πŸ“ Sample Responses to Test Against:")
242
- st.code("""Example 1 (Good - pure JSON):
243
- {
244
- "subject": "Python Programming",
245
- "total_weeks": 8,
246
- "daily_hours": 2,
247
- ...
248
- }
249
-
250
- Example 2 (With markdown):
251
- ```json
252
- {
253
- "subject": "Python Programming",
254
- "total_weeks": 8,
255
- ...
256
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
+ import pandas as pd
3
  import json
4
+ import plotly.express as px
5
+ import plotly.graph_objects as go
6
+ from datetime import datetime, timedelta
7
+ import google.generativeai as genai
8
  import os
9
+ import re
10
+ import math
11
+ import time
12
+
13
+ # ============================================
14
+ # πŸš€ CONFIGURATION FOR HUGGING FACE DEPLOYMENT
15
+ # ============================================
16
+
17
+ # Page configuration
18
+ st.set_page_config(
19
+ page_title="AI Study Planner Pro",
20
+ page_icon="πŸ“š",
21
+ layout="wide",
22
+ initial_sidebar_state="collapsed"
23
+ )
24
+
25
+ # Get API Key from Hugging Face Secrets
26
+ GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "")
27
+
28
+ # ============================================
29
+ # 🎨 CUSTOM CSS WITH IMPROVED STYLING
30
+ # ============================================
31
+
32
+ st.markdown("""
33
+ <style>
34
+ .main-header {
35
+ font-size: 2.5rem;
36
+ color: #1E3A8A;
37
+ text-align: center;
38
+ margin-bottom: 1rem;
39
+ font-weight: 800;
40
+ }
41
+
42
+ .tagline {
43
+ font-size: 1.2rem;
44
+ color: #6B7280;
45
+ text-align: center;
46
+ margin-bottom: 2rem;
47
+ font-weight: 400;
48
+ }
49
+
50
+ .sub-header {
51
+ font-size: 1.8rem;
52
+ color: #3B82F6;
53
+ margin-top: 1.5rem;
54
+ margin-bottom: 1rem;
55
+ font-weight: 700;
56
+ border-bottom: 3px solid #3B82F6;
57
+ padding-bottom: 10px;
58
+ }
59
+
60
+ .metric-card {
61
+ background-color: #F3F4F6;
62
+ padding: 1rem;
63
+ border-radius: 10px;
64
+ border-left: 5px solid #3B82F6;
65
+ margin-bottom: 1rem;
66
+ }
67
+
68
+ .debug-box {
69
+ background-color: #FEF3C7;
70
+ border: 2px solid #F59E0B;
71
+ padding: 1.5rem;
72
+ border-radius: 10px;
73
+ margin: 1rem 0;
74
+ font-family: monospace;
75
+ font-size: 0.9rem;
76
+ }
77
+
78
+ .raw-response-box {
79
+ background-color: #1F2937;
80
+ color: #D1D5DB;
81
+ padding: 1rem;
82
+ border-radius: 5px;
83
+ font-family: 'Courier New', monospace;
84
+ font-size: 0.8rem;
85
+ max-height: 400px;
86
+ overflow-y: auto;
87
+ white-space: pre-wrap;
88
+ word-wrap: break-word;
89
+ }
90
+
91
+ .success-box {
92
+ background-color: #D1FAE5;
93
+ border: 2px solid #10B981;
94
+ padding: 1rem;
95
+ border-radius: 10px;
96
+ margin: 1rem 0;
97
+ }
98
+
99
+ .error-box {
100
+ background-color: #FEE2E2;
101
+ border: 2px solid #EF4444;
102
+ padding: 1rem;
103
+ border-radius: 10px;
104
+ margin: 1rem 0;
105
+ }
106
+
107
+ .info-box {
108
+ background-color: #DBEAFE;
109
+ border: 2px solid #3B82F6;
110
+ padding: 1rem;
111
+ border-radius: 10px;
112
+ margin: 1rem 0;
113
+ }
114
+ </style>
115
+ """, unsafe_allow_html=True)
116
+
117
+ # ============================================
118
+ # πŸ”§ SESSION STATE INITIALIZATION - UPDATED
119
+ # ============================================
120
 
121
+ def init_session_state():
122
+ """Initialize all session state variables"""
123
+ session_vars = {
124
+ 'study_plan': None,
125
+ 'progress': {},
126
+ 'subject': "",
127
+ 'api_key': GEMINI_API_KEY,
128
+ 'raw_ai_response': "", # NEW: Store raw AI response
129
+ 'ai_response_debug': False, # NEW: Debug mode toggle
130
+ 'show_debug': False,
131
+ 'goal_type': "Skill/Topic Completion",
132
+ 'target_date': None,
133
+ 'study_days': ["Mon", "Tue", "Wed", "Thu", "Fri"],
134
+ 'plan_loaded': False,
135
+ 'current_week': 1,
136
+ 'task_states': {},
137
+ 'weekly_streak': 0,
138
+ 'longest_weekly_streak': 0,
139
+ 'selected_topics': [],
140
+ 'ai_error_message': "",
141
+ 'plan_generation_attempted': False,
142
+ 'api_available': True,
143
+ 'api_quota_exceeded': False,
144
+ 'last_raw_response': "" # NEW: Store last response for debugging
145
+ }
146
+
147
+ for key, value in session_vars.items():
148
+ if key not in st.session_state:
149
+ st.session_state[key] = value
150
+
151
+ init_session_state()
152
+
153
+ # ============================================
154
+ # πŸ› οΈ UTILITY FUNCTIONS
155
+ # ============================================
156
+
157
+ def calculate_learning_quality(days_available, hours_per_day, study_days_count):
158
+ """Calculate learning quality score"""
159
+ total_study_hours = days_available * hours_per_day
160
+ required_hours = 60
161
+ coverage = min(100, (total_study_hours / required_hours) * 100)
162
+
163
+ quality_score = 0
164
+
165
+ if 1 <= hours_per_day <= 3:
166
+ quality_score += 30
167
+ elif hours_per_day > 3:
168
+ quality_score += 20
169
+ else:
170
+ quality_score += 10
171
+
172
+ if 3 <= study_days_count <= 5:
173
+ quality_score += 30
174
+ elif study_days_count > 5:
175
+ quality_score += 20
176
+ else:
177
+ quality_score += 10
178
+
179
+ if coverage >= 80:
180
+ quality_score += 40
181
+ elif coverage >= 50:
182
+ quality_score += 20
183
+ else:
184
+ quality_score += 10
185
+
186
+ return {
187
+ "quality_score": min(100, quality_score),
188
+ "coverage_percentage": coverage,
189
+ "total_hours": total_study_hours,
190
+ "required_hours": required_hours,
191
+ "weekly_hours": hours_per_day * study_days_count,
192
+ "is_optimal": quality_score >= 60,
193
+ "is_severely_inadequate": (study_days_count < 2 and days_available < 30) or (coverage < 30)
194
+ }
195
 
196
+ def sanitize_study_inputs(subject, goal):
197
+ """Enhanced sanitization for safe AI interaction"""
198
+ if not subject:
199
+ return subject, goal
200
+
201
+ clean_subject = subject.lower().strip()
202
+ clean_goal = goal.lower().strip() if goal else ""
203
+
204
+ educational_replacements = {
205
+ 'hack': 'cybersecurity analysis',
206
+ 'hacking': 'cybersecurity studies',
207
+ 'exploit': 'security analysis',
208
+ 'crack': 'security testing',
209
+ 'bypass': 'security assessment',
210
+ 'attack': 'defensive strategy',
211
+ }
212
+
213
+ for risky, safe in educational_replacements.items():
214
+ if risky in clean_subject:
215
+ clean_subject = clean_subject.replace(risky, safe)
216
+ if risky in clean_goal:
217
+ clean_goal = clean_goal.replace(risky, safe)
218
+
219
+ if clean_goal and not any(x in clean_goal for x in ['learn', 'study', 'understand', 'master', 'explore', 'analyze']):
220
+ clean_goal += " learning"
221
+
222
+ clean_subject = clean_subject.title()
223
+ clean_goal = clean_goal.capitalize()
224
+
225
+ return clean_subject, clean_goal
226
+
227
+ # ============================================
228
+ # πŸ€– AI RESPONSE DEBUGGER - INTEGRATED
229
+ # ============================================
230
+
231
+ def show_ai_response_debug(raw_response, parsed_json=None, error=None):
232
+ """Display AI response debugging information"""
233
+
234
+ st.markdown('<div class="debug-box">', unsafe_allow_html=True)
235
+ st.markdown("### πŸ› AI Response Debug")
236
+
237
+ # Basic info
238
+ col1, col2, col3, col4 = st.columns(4)
239
+ with col1:
240
+ st.metric("Length", f"{len(raw_response)} chars")
241
+ with col2:
242
+ has_markdown = "```" in raw_response
243
+ st.metric("Markdown", "βœ…" if has_markdown else "❌")
244
+ with col3:
245
+ has_explanation = any(x in raw_response.lower() for x in ['here is', "here's", 'the study plan'])
246
+ st.metric("Explanation", "βœ…" if has_explanation else "❌")
247
+ with col4:
248
+ is_pure_json = raw_response.strip().startswith('{') and raw_response.strip().endswith('}')
249
+ st.metric("Pure JSON", "βœ…" if is_pure_json else "❌")
250
+
251
+ # Show raw response
252
+ with st.expander("πŸ“₯ View Raw AI Response", expanded=True):
253
+ st.markdown('<div class="raw-response-box">', unsafe_allow_html=True)
254
+ st.text(raw_response)
255
+ st.markdown('</div>', unsafe_allow_html=True)
256
+
257
+ # Show first and last parts
258
+ col_left, col_right = st.columns(2)
259
+ with col_left:
260
+ st.text("First 500 characters:")
261
+ st.code(raw_response[:500], language="text")
262
+ with col_right:
263
+ st.text("Last 500 characters:")
264
+ st.code(raw_response[-500:] if len(raw_response) > 500 else raw_response, language="text")
265
+
266
+ # Show analysis
267
+ if error:
268
+ st.markdown('<div class="error-box">', unsafe_allow_html=True)
269
+ st.error(f"❌ JSON Parsing Error: {error}")
270
+ st.markdown('</div>', unsafe_allow_html=True)
271
+
272
+ if parsed_json:
273
+ st.markdown('<div class="success-box">', unsafe_allow_html=True)
274
+ st.success("βœ… JSON Parsed Successfully!")
275
+ with st.expander("View Parsed JSON"):
276
+ st.json(parsed_json)
277
+ st.markdown('</div>', unsafe_allow_html=True)
278
+
279
+ # Copy to clipboard button
280
+ if st.button("πŸ“‹ Copy Raw Response to Clipboard"):
281
+ st.code(raw_response, language="text")
282
+ st.success("Response copied! Paste it in our chat for analysis.")
283
+
284
+ st.markdown('</div>', unsafe_allow_html=True)
285
+ return raw_response
286
+
287
+ def extract_json_robust(text):
288
+ """Robust JSON extraction with debugging"""
289
+ if not text:
290
+ return None
291
+
292
+ st.session_state.last_raw_response = text
293
+
294
+ # Method 1: Try direct parse
295
+ try:
296
+ return json.loads(text)
297
+ except json.JSONDecodeError:
298
+ pass
299
+
300
+ # Method 2: Remove markdown
301
+ cleaned = text.replace("```json", "").replace("```", "").strip()
302
+ try:
303
+ return json.loads(cleaned)
304
+ except json.JSONDecodeError:
305
+ pass
306
+
307
+ # Method 3: Extract between { and }
308
+ start = text.find('{')
309
+ end = text.rfind('}') + 1
310
+
311
+ if start != -1 and end != -1:
312
+ extracted = text[start:end]
313
+ # Clean extracted JSON
314
+ extracted = re.sub(r'//.*', '', extracted) # Remove comments
315
+ extracted = re.sub(r',\s*}', '}', extracted) # Remove trailing commas
316
+ extracted = re.sub(r',\s*]', ']', extracted)
317
+ extracted = extracted.replace('"', '"').replace('"', '"') # Fix smart quotes
318
+
319
+ try:
320
+ return json.loads(extracted)
321
+ except json.JSONDecodeError:
322
+ pass
323
+
324
+ # Method 4: Try to fix common issues
325
+ try:
326
+ # Replace single quotes with double quotes for keys
327
+ fixed = re.sub(r"(\w+):", r'"\1":', text)
328
+ fixed = re.sub(r"'", '"', fixed)
329
+ return json.loads(fixed)
330
+ except json.JSONDecodeError:
331
+ pass
332
+
333
+ return None
334
+
335
+ # ============================================
336
+ # πŸ€– AI STUDY PLAN GENERATION WITH DEBUG
337
+ # ============================================
338
+
339
+ def generate_study_plan_with_debug(api_key, **kwargs):
340
+ """Generate study plan with integrated debugging"""
341
 
 
342
  if not api_key:
343
+ st.session_state.ai_error_message = "API key missing"
344
  return None
345
 
346
  try:
347
  # Configure API
348
  genai.configure(api_key=api_key)
349
+ model = genai.GenerativeModel('gemini-2.5-flash')
 
 
 
 
350
 
351
+ # Extract parameters
352
+ original_subject = kwargs.get('subject', 'General Learning')
353
+ original_goal = kwargs.get('goal', '')
354
+ selected_topics = kwargs.get('selected_topics', [])
355
+ days_available = kwargs.get('days_available', 60)
356
+ hours_per_day = kwargs.get('hours_per_day', 2)
357
+ study_days = kwargs.get('study_days', ["Mon", "Tue", "Wed", "Thu", "Fri"])
358
  study_days_count = len(study_days)
359
+ weeks = max(1, days_available // 7)
360
+
361
+ # Sanitize inputs
362
+ safe_subject, safe_goal = sanitize_study_inputs(original_subject, original_goal)
363
 
364
  # Topics text
365
  topics_text = ""
366
+ if selected_topics:
367
+ topics_text = f"SPECIFIC TOPICS TO COVER: {', '.join(selected_topics)}"
368
 
369
+ # STRICT PROMPT with explicit instructions
370
+ prompt = f'''You are a JSON generator. Return ONLY valid JSON, nothing else.
371
 
372
+ Create a study plan for: {safe_subject}
373
  {topics_text}
374
+ LEARNING GOAL: {safe_goal or "Master " + safe_subject}
375
  DURATION: {weeks} weeks ({days_available} days)
376
+ STUDY SCHEDULE: {hours_per_day} hours/day, {study_days_count} days/week ({", ".join(study_days)})
377
+ CURRENT LEVEL: {kwargs.get('current_level', 'Beginner')}
378
 
379
+ CRITICAL INSTRUCTIONS:
380
+ 1. Return ONLY a valid JSON object
381
+ 2. NO markdown code blocks, NO explanations
382
+ 3. NO comments in the JSON
383
+ 4. NO emojis
384
+ 5. NO trailing commas
385
+ 6. Use double quotes for all strings
386
 
387
  REQUIRED JSON STRUCTURE:
388
  {{
389
+ "subject": "{safe_subject}",
390
  "total_weeks": {weeks},
391
  "daily_hours": {hours_per_day},
392
  "study_days": {json.dumps(study_days)},
 
399
  "weekly_schedule": [
400
  {{
401
  "week": 1,
402
+ "focus": "Week 1: Introduction to {safe_subject}",
403
  "objectives": ["Understand basic concepts", "Setup learning environment", "Complete first exercises"],
404
  "daily_tasks": [
405
  ["Read introduction chapter", "Watch tutorial video"],
 
409
  ["Weekly review", "Plan next week"]
410
  ],
411
  "day_focus": ["Introduction", "Practice", "Review", "Application", "Planning"],
412
+ "week_summary": "This week focuses on building foundational knowledge.",
413
  "resources": ["Online tutorials", "Practice exercises", "Reference materials"],
414
+ "milestone": "Complete basic understanding"
415
  }}
416
  ],
417
  "study_tips": ["Study regularly", "Practice daily", "Review weekly", "Ask questions when stuck"],
418
  "success_metrics": ["Complete all weekly tasks", "Understand core concepts", "Apply knowledge practically"]
419
  }}
420
 
421
+ Add weeks 2 through {weeks} with similar structure.
 
 
 
 
 
422
 
423
+ IMPORTANT: Start your response with {{ and end with }}. Return ONLY JSON.'''
424
 
425
+ # Store prompt for debugging
426
+ st.session_state.last_prompt = prompt
427
 
428
  # Generate response
429
+ response = model.generate_content(
430
+ prompt,
431
+ generation_config=genai.GenerationConfig(
432
+ max_output_tokens=4000,
433
+ temperature=0.3, # Lower temperature for consistent JSON
434
+ top_p=0.8
435
+ ),
436
+ safety_settings={
437
+ 'HARM_CATEGORY_HARASSMENT': 'BLOCK_MEDIUM_AND_ABOVE',
438
+ 'HARM_CATEGORY_HATE_SPEECH': 'BLOCK_MEDIUM_AND_ABOVE',
439
+ 'HARM_CATEGORY_SEXUALLY_EXPLICIT': 'BLOCK_MEDIUM_AND_ABOVE',
440
+ 'HARM_CATEGORY_DANGEROUS_CONTENT': 'BLOCK_MEDIUM_AND_ABOVE'
441
+ }
442
+ )
443
 
 
444
  if hasattr(response, 'text') and response.text:
445
  raw_response = response.text
446
+ st.session_state.raw_ai_response = raw_response
447
 
448
+ # Show debug info if enabled
449
+ if st.session_state.ai_response_debug:
450
+ show_ai_response_debug(raw_response)
451
 
452
+ # Extract JSON
453
+ plan = extract_json_robust(raw_response)
454
+
455
+ if plan:
456
+ # Validate structure
457
+ if 'subject' not in plan:
458
+ plan['subject'] = original_subject
459
+ if 'total_weeks' not in plan:
460
+ plan['total_weeks'] = weeks
461
+ if 'weekly_schedule' not in plan:
462
+ plan['weekly_schedule'] = []
463
+
464
+ # Ensure correct number of weeks
465
+ while len(plan.get('weekly_schedule', [])) < weeks:
466
+ week_num = len(plan['weekly_schedule']) + 1
467
+ plan['weekly_schedule'].append({
468
+ "week": week_num,
469
+ "focus": f"Week {week_num}: {plan.get('subject', safe_subject)}",
470
+ "objectives": ["Learn concepts", "Practice exercises", "Review material"],
471
+ "daily_tasks": [
472
+ ["Study materials", "Take notes"],
473
+ ["Practice exercises", "Review concepts"],
474
+ ["Work on projects", "Apply knowledge"],
475
+ ["Review week", "Prepare for next"],
476
+ ["Weekly assessment", "Plan ahead"]
477
+ ],
478
+ "day_focus": ["Learn", "Practice", "Apply", "Review", "Plan"],
479
+ "week_summary": f"Week {week_num} progress",
480
+ "resources": ["Online resources", "Practice materials"],
481
+ "milestone": f"Complete Week {week_num}"
482
+ })
483
+
484
+ plan['generated_at'] = datetime.now().isoformat()
485
+ plan['total_days'] = days_available
486
+ plan['selected_topics'] = selected_topics
487
+
488
+ return plan
489
+ else:
490
+ st.session_state.ai_error_message = "Failed to parse AI response as JSON"
491
+
492
+ # Show debug even if not enabled (for errors)
493
+ show_ai_response_debug(raw_response, error="JSON parsing failed")
494
+
495
+ return None
496
+ else:
497
+ st.session_state.ai_error_message = "Empty response from AI"
498
+ return None
499
 
500
+ except Exception as e:
501
+ error_msg = str(e).lower()
502
+ if "quota" in error_msg or "exceeded" in error_msg:
503
+ st.session_state.api_quota_exceeded = True
504
+ st.session_state.ai_error_message = "API quota exceeded"
505
+ elif "invalid" in error_msg or "api key" in error_msg:
506
+ st.session_state.ai_error_message = "Invalid API key"
507
+ else:
508
+ st.session_state.ai_error_message = f"AI Error: {str(e)[:100]}"
509
+
510
+ return None
511
+
512
+ # ============================================
513
+ # πŸ“‚ FILE HANDLING FUNCTIONS
514
+ # ============================================
515
+
516
+ def save_plan(plan, subject):
517
+ """Save study plan to file"""
518
+ os.makedirs("data", exist_ok=True)
519
+ filename = f"data/{subject.replace(' ', '_')}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
520
+
521
+ try:
522
+ with open(filename, 'w', encoding='utf-8') as f:
523
+ json.dump(plan, f, indent=4, ensure_ascii=False)
524
+ return filename
525
+ except:
526
+ return None
527
+
528
+ def generate_final_plan(api_key, plan_data, subject, learning_quality):
529
+ """Generate and save plan with proper error handling"""
530
+ try:
531
+ st.session_state.ai_error_message = ""
532
+ st.session_state.plan_generation_attempted = True
533
+
534
+ # Generate plan with debug
535
+ plan = generate_study_plan_with_debug(api_key, **plan_data)
536
+
537
+ if plan:
538
+ # Set session state
539
+ st.session_state.study_plan = plan
540
+ st.session_state.subject = subject
541
+ st.session_state.current_week = 1
542
+ st.session_state.plan_loaded = True
543
 
544
+ # Save plan
545
+ filename = save_plan(plan, subject)
 
 
 
 
 
 
 
546
 
547
+ if filename:
548
+ st.success(f"βœ… Study plan generated and saved!")
549
+ st.info(f"πŸ“ Plan saved as: {filename}")
550
 
551
+ return True
552
+ else:
553
+ if st.session_state.ai_error_message:
554
+ st.error(f"❌ {st.session_state.ai_error_message}")
555
+ else:
556
+ st.error("❌ Failed to generate study plan")
557
+ return False
558
 
559
+ except Exception as e:
560
+ st.error(f"❌ Error: {str(e)[:100]}")
561
+ return False
562
+
563
+ # ============================================
564
+ # πŸ“š TOPIC SUGGESTIONS DATABASE
565
+ # ============================================
566
+
567
+ def get_topic_suggestions(subject):
568
+ """Get topic suggestions based on subject"""
569
+ topic_library = {
570
+ "python": [
571
+ "Python Basics & Syntax", "Data Types & Variables", "Control Flow",
572
+ "Functions & Modules", "Object-Oriented Programming", "Error Handling",
573
+ "File Operations", "Libraries & Packages", "Data Structures",
574
+ "Algorithms", "Web Development", "Data Science", "Machine Learning"
575
+ ],
576
+ "web development": [
577
+ "HTML & CSS", "JavaScript Basics", "DOM Manipulation",
578
+ "React/Vue/Angular", "Node.js", "Express.js",
579
+ "REST APIs", "Database Integration", "Authentication",
580
+ "Deployment", "Performance Optimization", "Security"
581
+ ],
582
+ "data science": [
583
+ "Statistics Fundamentals", "Python for Data Science",
584
+ "NumPy & Pandas", "Data Visualization", "Machine Learning Basics",
585
+ "Data Cleaning", "Exploratory Data Analysis", "Feature Engineering"
586
+ ],
587
+ "machine learning": [
588
+ "Linear Algebra", "Calculus", "Probability",
589
+ "Supervised Learning", "Unsupervised Learning", "Neural Networks",
590
+ "Deep Learning", "Computer Vision", "Natural Language Processing"
591
+ ]
592
+ }
593
+
594
+ subject_lower = subject.lower()
595
+ for key in topic_library:
596
+ if key in subject_lower:
597
+ return topic_library[key]
598
+
599
+ return [
600
+ "Fundamentals & Basics", "Core Concepts", "Practical Applications",
601
+ "Advanced Techniques", "Tools & Software", "Best Practices",
602
+ "Problem Solving", "Project Work", "Review & Revision"
603
+ ]
604
+
605
+ # ============================================
606
+ # πŸŽͺ MAIN APPLICATION LAYOUT
607
+ # ============================================
608
+
609
+ # Main header
610
+ st.markdown('<h1 class="main-header">πŸ“š AI Study Planner Pro</h1>', unsafe_allow_html=True)
611
+ st.markdown('<p class="tagline">Plan β†’ Track β†’ Achieve | Your personal AI study assistant</p>', unsafe_allow_html=True)
612
+
613
+ # API Status
614
+ if not st.session_state.api_key:
615
+ st.warning("⚠️ **API Key Required:** Set GEMINI_API_KEY in Hugging Face Secrets for AI features.")
616
+ elif st.session_state.api_quota_exceeded:
617
+ st.error("🚫 **API Quota Exceeded:** Please try again tomorrow.")
618
+ else:
619
+ st.success("βœ… **AI Features Ready:** You can generate study plans!")
620
+
621
+ # Debug mode toggle (in sidebar if you add one, or at top)
622
+ st.checkbox("πŸ”§ Enable AI Response Debug Mode",
623
+ value=st.session_state.ai_response_debug,
624
+ key="ai_response_debug",
625
+ help="Show raw AI response and debugging info when generating plans")
626
+
627
+ # Create tabs
628
+ tab1, tab2, tab3, tab4 = st.tabs([
629
+ "🎯 Define Goal",
630
+ "πŸ“… Study Plan",
631
+ "βœ… Track Progress",
632
+ "πŸ“€ Export"
633
+ ])
634
+
635
+ # ============================================
636
+ # TAB 1: DEFINE GOAL WITH DEBUG
637
+ # ============================================
638
+
639
+ with tab1:
640
+ st.markdown('<h2 class="sub-header">Define Your Learning Goal</h2>', unsafe_allow_html=True)
641
+
642
+ # Option 1: Upload Existing Plan
643
+ st.info("πŸ“€ **Option 1: Upload Existing Plan**")
644
+ uploaded_file = st.file_uploader("Upload your saved study plan (JSON)", type=['json'], key="upload_plan")
645
+
646
+ if uploaded_file is not None and not st.session_state.plan_loaded:
647
+ try:
648
+ plan = json.load(uploaded_file)
649
+ if 'subject' in plan:
650
+ st.session_state.study_plan = plan
651
+ st.session_state.subject = plan['subject']
652
+ st.session_state.plan_loaded = True
653
+ st.success(f"βœ… Plan loaded: {plan['subject']}")
654
+ st.rerun()
655
+ except:
656
+ st.error("❌ Error loading plan")
657
+
658
+ st.markdown("---")
659
+
660
+ # Option 2: Create New Plan
661
+ st.info("🎯 **Option 2: Create New Plan**")
662
+
663
+ col1, col2 = st.columns([2, 1])
664
+
665
+ with col1:
666
+ st.subheader("🎯 Goal Definition")
667
+
668
+ subject = st.text_input(
669
+ "Subject/Topic:",
670
+ value="",
671
+ placeholder="e.g., Python Programming, AWS Cloud, Spanish Language...",
672
+ help="What do you want to learn?"
673
+ )
674
+
675
+ goal = st.text_area(
676
+ "Specific Learning Goal:",
677
+ value="",
678
+ placeholder="e.g., 'Master Python for data analysis', 'Prepare for AWS certification'...",
679
+ height=80
680
+ )
681
+
682
+ # TOPIC SELECTOR
683
+ if subject:
684
+ st.subheader("πŸ“š Topics to Learn")
685
+ topics = get_topic_suggestions(subject)
686
 
687
+ selected_topics = st.multiselect(
688
+ "Choose topics (select up to 10):",
689
+ options=topics,
690
+ default=topics[:min(5, len(topics))] if topics else [],
691
+ max_selections=10,
692
+ help="Select specific topics you want to focus on"
693
+ )
 
694
 
695
+ st.session_state.selected_topics = selected_topics
 
 
 
 
 
 
 
 
696
 
697
+ if selected_topics:
698
+ st.success(f"βœ… Selected {len(selected_topics)} topics")
699
+
700
+ st.subheader("⏰ Time Availability")
701
+
702
+ col1a, col2a = st.columns(2)
703
+ with col1a:
704
+ hours_per_day = st.slider("Hours per day:", 1, 8, 2)
705
+ with col2a:
706
+ days_available = st.slider("Total days available:", 7, 365, 60)
707
+
708
+ st.write("**Preferred study days:**")
709
+ study_days = st.multiselect(
710
+ "Select days:",
711
+ ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
712
+ default=["Mon", "Tue", "Wed", "Thu", "Fri"],
713
+ label_visibility="collapsed"
714
+ )
715
+ st.session_state.study_days = study_days
716
+
717
+ with col2:
718
+ st.subheader("πŸŽ“ Learning Preferences")
719
+
720
+ current_level = st.selectbox(
721
+ "Current Level:",
722
+ ["Beginner", "Intermediate", "Advanced"],
723
+ index=0
724
+ )
725
+
726
+ # Quality indicator
727
+ if study_days and days_available and hours_per_day:
728
+ study_days_count = len(study_days)
729
+ learning_quality = calculate_learning_quality(days_available, hours_per_day, study_days_count)
730
 
731
+ quality_score = learning_quality['quality_score']
732
+ color = "green" if quality_score >= 70 else "orange" if quality_score >= 40 else "red"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
733
 
734
+ st.markdown(f"""
735
+ <div style="background-color: #F8FAFC; padding: 1rem; border-radius: 10px; border-left: 5px solid {color};">
736
+ <h4 style="margin: 0 0 10px 0;">πŸ“Š Learning Quality</h4>
737
+ <div style="font-size: 2rem; font-weight: bold; color: {color}; text-align: center;">
738
+ {quality_score:.0f}/100
739
+ </div>
740
+ <p style="text-align: center; margin: 5px 0; color: #666;">
741
+ {learning_quality['coverage_percentage']:.0f}% coverage
742
+ </p>
743
+ <div style="background-color: #E5E7EB; height: 10px; border-radius: 5px; margin: 10px 0;">
744
+ <div style="background-color: {color}; width: {quality_score}%; height: 100%; border-radius: 5px;"></div>
745
+ </div>
746
+ </div>
747
+ """, unsafe_allow_html=True)
748
 
749
+ if learning_quality['is_severely_inadequate']:
750
+ st.warning("⚠️ **Low time allocation:** Consider increasing study hours.")
751
+ elif learning_quality['is_optimal']:
752
+ st.success("βœ… **Optimal schedule:** Your study plan looks well-balanced!")
753
+
754
+ st.markdown("---")
755
+
756
+ # Generate Plan Button - WITH DEBUG
757
+ if st.button("πŸš€ Generate AI Study Plan", type="primary", use_container_width=True):
758
+ if not st.session_state.api_key:
759
+ st.error("⚠️ **API Key Required:** Please set GEMINI_API_KEY in Hugging Face Secrets.")
760
+ elif not subject:
761
+ st.error("⚠️ Please enter a Subject/Topic")
762
+ elif not study_days:
763
+ st.error("⚠️ Please select at least one study day")
764
  else:
765
+ with st.spinner("πŸ€– AI is crafting your personalized study plan..."):
766
+ try:
767
+ study_days_count = len(study_days)
768
+ learning_quality = calculate_learning_quality(days_available, hours_per_day, study_days_count)
769
+
770
+ plan_data = {
771
+ 'subject': subject,
772
+ 'goal': goal,
773
+ 'selected_topics': st.session_state.selected_topics,
774
+ 'hours_per_day': hours_per_day,
775
+ 'days_available': days_available,
776
+ 'current_level': current_level,
777
+ 'study_days': study_days,
778
+ 'goal_type': "Skill/Topic Completion"
779
+ }
780
+
781
+ success = generate_final_plan(st.session_state.api_key, plan_data, subject, learning_quality)
782
+
783
+ if success:
784
+ st.rerun()
785
+
786
+ except Exception as e:
787
+ st.error(f"❌ Error: {str(e)[:100]}")
788
 
789
+ # ============================================
790
+ # TAB 2: STUDY PLAN (Simplified)
791
+ # ============================================
792
 
793
+ with tab2:
794
+ if st.session_state.study_plan:
795
+ plan = st.session_state.study_plan
796
+ subject = st.session_state.subject
797
+
798
+ st.markdown(f'<h2 class="sub-header">πŸ“… Study Plan: {subject}</h2>', unsafe_allow_html=True)
799
+
800
+ # Basic info
801
+ col1, col2, col3 = st.columns(3)
802
+ with col1:
803
+ st.metric("Total Weeks", plan.get('total_weeks', 0))
804
+ with col2:
805
+ st.metric("Daily Hours", plan.get('daily_hours', 2))
806
+ with col3:
807
+ st.metric("Study Days", len(plan.get('study_days', [])))
808
+
809
+ # Weekly schedule
810
+ st.subheader("πŸ“‹ Weekly Schedule")
811
+
812
+ current_week = st.selectbox(
813
+ "Select week:",
814
+ options=list(range(1, plan.get('total_weeks', 1) + 1)),
815
+ index=st.session_state.current_week - 1
816
+ )
817
+
818
+ if plan.get('weekly_schedule'):
819
+ week_idx = current_week - 1
820
+ if week_idx < len(plan['weekly_schedule']):
821
+ week = plan['weekly_schedule'][week_idx]
822
+
823
+ with st.expander(f"Week {week['week']}: {week.get('focus', '')}", expanded=True):
824
+ st.write(f"**Objectives:**")
825
+ for obj in week.get('objectives', []):
826
+ st.write(f"β€’ {obj}")
827
+
828
+ st.write(f"**Milestone:** {week.get('milestone', '')}")
829
+
830
+ # Daily tasks
831
+ if 'daily_tasks' in week:
832
+ st.write("**Daily Tasks:**")
833
+ for day_idx, tasks in enumerate(week['daily_tasks']):
834
+ st.write(f"Day {day_idx + 1}:")
835
+ for task in tasks:
836
+ st.write(f" - {task}")
837
+ else:
838
+ st.info("πŸ‘ˆ **Define your learning goal first in the 'Define Goal' tab!**")
839
+
840
+ # Show debug info if available
841
+ if st.session_state.raw_ai_response and st.session_state.ai_response_debug:
842
+ show_ai_response_debug(st.session_state.raw_ai_response)
843
 
844
+ # ============================================
845
+ # TAB 3: TRACK PROGRESS (Simplified)
846
+ # ============================================
 
 
847
 
848
+ with tab3:
849
+ st.markdown('<h2 class="sub-header">βœ… Progress Tracker</h2>', unsafe_allow_html=True)
850
+
851
+ if st.session_state.study_plan:
852
+ st.info("πŸ“Š Progress tracking features will be available in the full version.")
853
+ st.write("Complete tasks in the Study Plan tab to track your progress.")
854
+ else:
855
+ st.info("πŸ‘ˆ **Generate a study plan first to track progress!**")
856
 
857
+ # ============================================
858
+ # TAB 4: EXPORT (Simplified)
859
+ # ============================================
860
+
861
+ with tab4:
862
+ st.markdown('<h2 class="sub-header">πŸ“€ Export Your Study Plan</h2>', unsafe_allow_html=True)
863
+
864
+ if st.session_state.study_plan:
865
+ plan = st.session_state.study_plan
866
+ subject = st.session_state.subject
867
+
868
+ json_str = json.dumps(plan, indent=2, ensure_ascii=False)
869
+
870
+ st.download_button(
871
+ label="πŸ“„ Download Study Plan (JSON)",
872
+ data=json_str,
873
+ file_name=f"study_plan_{subject.replace(' ', '_')}.json",
874
+ mime="application/json",
875
+ use_container_width=True
876
+ )
877
+ else:
878
+ st.info("πŸ‘ˆ **Generate or load a study plan first to export!**")
879
+
880
+ # ============================================
881
+ # πŸ“‹ DEBUG INFO PANEL (Always available)
882
+ # ============================================
883
 
884
+ if st.session_state.raw_ai_response and st.session_state.ai_response_debug:
885
+ st.markdown("---")
886
+ st.markdown("### πŸ”§ Debug Information")
887
+
888
+ # Show the last raw response
889
+ with st.expander("View Last AI Response", expanded=False):
890
+ st.text_area("Raw AI Response:",
891
+ st.session_state.raw_ai_response,
892
+ height=300,
893
+ label_visibility="collapsed")
894
+
895
+ # Copy button for sharing
896
+ if st.button("πŸ“‹ Copy Raw Response for Debugging"):
897
+ st.code(st.session_state.raw_ai_response, language="text")
898
+ st.success("Response ready to copy! Paste it in our chat for analysis.")
899
+
900
+ # Instructions for debugging
901
+ with st.expander("ℹ️ How to Debug JSON Parsing Issues", expanded=False):
902
+ st.markdown("""
903
+ **If you see "❌ Failed to parse AI response as JSON":**
904
+
905
+ 1. **Enable Debug Mode:** Check the "Enable AI Response Debug Mode" box above
906
+ 2. **Click Generate Plan again**
907
+ 3. **The debug panel will show:**
908
+ - Raw AI response
909
+ - Response analysis
910
+ - JSON extraction attempts
911
+
912
+ 4. **Copy the raw response** and share it with me
913
+ 5. **I'll create a fix** based on what the AI actually returns
914
+
915
+ **Common Issues:**
916
+ - AI adds markdown blocks (\`\`\`json)
917
+ - AI adds explanatory text before/after JSON
918
+ - Smart quotes or invalid characters
919
+ - Missing or extra commas
920
+ """)