andevs commited on
Commit
32c8af3
·
verified ·
1 Parent(s): 59ed525

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +251 -203
app.py CHANGED
@@ -1,21 +1,24 @@
1
  """
2
- StudyFlow AI Backend - Complete Working Version
3
  """
4
  import os
5
  import json
6
  import sqlite3
7
  import hashlib
8
  import tempfile
 
9
  from datetime import datetime
 
10
 
11
  from fastapi import FastAPI, UploadFile, File, Form, HTTPException
12
  from fastapi.responses import JSONResponse, HTMLResponse
13
  from fastapi.middleware.cors import CORSMiddleware
 
14
  import PyPDF2
15
  from youtube_transcript_api import YouTubeTranscriptApi
16
 
17
  # Initialize FastAPI
18
- app = FastAPI(title="StudyFlow AI", version="1.0.0")
19
 
20
  # CORS middleware
21
  app.add_middleware(
@@ -42,6 +45,7 @@ def init_db():
42
  content_type TEXT NOT NULL,
43
  difficulty TEXT NOT NULL,
44
  content_hash TEXT,
 
45
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
46
  last_accessed TIMESTAMP DEFAULT CURRENT_TIMESTAMP
47
  )
@@ -106,24 +110,12 @@ def init_db():
106
  )
107
  ''')
108
 
109
- # User profile table
110
- cursor.execute('''
111
- CREATE TABLE IF NOT EXISTS user_profile (
112
- id INTEGER PRIMARY KEY AUTOINCREMENT,
113
- total_questions INTEGER DEFAULT 0,
114
- correct_answers INTEGER DEFAULT 0,
115
- total_study_time INTEGER DEFAULT 0,
116
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
117
- )
118
- ''')
119
-
120
  conn.commit()
121
  conn.close()
122
 
123
  # Initialize database
124
  init_db()
125
 
126
- # Helper functions
127
  def generate_id(text: str = None):
128
  """Generate a unique ID"""
129
  import uuid
@@ -137,13 +129,14 @@ def extract_text_from_pdf(file_path: str) -> str:
137
  with open(file_path, 'rb') as file:
138
  pdf_reader = PyPDF2.PdfReader(file)
139
  text = ""
140
- for page in pdf_reader.pages[:10]: # Limit to first 10 pages
141
  page_text = page.extract_text()
142
  if page_text:
143
  text += page_text + "\n"
144
- return text[:10000] # Limit text length
145
  except Exception as e:
146
- return f"PDF extraction: {str(e)}"
 
147
 
148
  def extract_text_from_youtube(url: str) -> str:
149
  """Extract transcript from YouTube video"""
@@ -153,93 +146,187 @@ def extract_text_from_youtube(url: str) -> str:
153
  elif "youtu.be/" in url:
154
  video_id = url.split("/")[-1].split("?")[0]
155
  else:
156
- return "Invalid YouTube URL"
157
 
158
  transcript = YouTubeTranscriptApi.get_transcript(video_id)
159
- text = " ".join([entry['text'] for entry in transcript])
160
- return text[:10000] # Limit text length
161
  except Exception as e:
162
- return f"YouTube: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
 
164
- def generate_questions(text: str, difficulty: str, count: int = 5) -> list:
165
- """Generate questions from text"""
166
- import re
 
 
167
  sentences = re.split(r'[.!?]+', text)
168
- sentences = [s.strip() for s in sentences if len(s.strip()) > 20]
169
 
170
- if not sentences:
171
- sentences = [text[:200]]
 
172
 
 
173
  questions = []
174
- for i, sentence in enumerate(sentences[:count]):
175
- qid = generate_id(f"q_{i}")
 
 
176
 
177
  if difficulty == "easy":
178
- # Fill in the blank
179
  words = sentence.split()
180
- if len(words) > 3:
181
- blank_index = len(words) // 2
182
- blank_word = words[blank_index]
183
- question_text = sentence.replace(blank_word, "_____", 1)
 
 
184
  questions.append({
185
  "id": qid,
186
- "question_text": question_text,
187
  "question_type": "fill_blank",
 
188
  "correct_answer": blank_word,
189
- "difficulty": difficulty,
190
- "explanation": f"The answer is '{blank_word}'"
 
 
 
 
 
 
 
 
 
 
 
 
 
191
  })
192
 
193
  elif difficulty == "medium":
194
- # True/False
195
- question_text = f"True or False: '{sentence}'"
 
 
 
 
 
 
 
 
196
  questions.append({
197
  "id": qid,
198
- "question_text": question_text,
199
- "question_type": "true_false",
200
- "correct_answer": "0",
201
- "difficulty": difficulty,
202
- "explanation": "This statement appears to be true."
 
 
203
  })
204
 
205
  else: # hard
206
- # Multiple choice
207
- question_text = f"What is the main idea? '{sentence[:100]}...'"
208
- options = ["Detail", "Concept", "Question", "Example"]
209
  questions.append({
210
  "id": qid,
211
- "question_text": question_text,
212
- "question_type": "multiple_choice",
213
- "options": json.dumps(options),
214
- "correct_answer": "1",
215
- "difficulty": difficulty,
216
- "explanation": "This presents a general concept."
 
217
  })
218
 
219
- return questions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
 
221
- def generate_flashcards(text: str, count: int = 3) -> list:
222
- """Generate flashcards from text"""
223
- import re
224
  sentences = re.split(r'[.!?]+', text)
225
- sentences = [s.strip() for s in sentences if len(s.strip()) > 20]
226
 
227
  flashcards = []
228
- for i in range(min(len(sentences), count)):
229
- sentence = sentences[i]
230
  fcid = generate_id(f"fc_{i}")
231
 
 
 
 
 
 
 
 
 
 
 
232
  flashcards.append({
233
  "id": fcid,
234
- "front": sentence[:100],
235
- "back": f"Explanation of: {sentence[:50]}...",
236
- "category": "Study",
237
  "difficulty": "medium"
238
  })
239
 
240
  return flashcards
241
 
242
- # API Endpoints
243
  @app.post("/api/process-content")
244
  async def process_content(
245
  content_type: str = Form(...),
@@ -249,11 +336,16 @@ async def process_content(
249
  file: UploadFile = File(None),
250
  youtube_url: str = Form(None)
251
  ):
252
- """Process uploaded content"""
 
 
253
 
254
  text_content = ""
 
255
  if content_type == "text" and content:
256
- text_content = content[:10000]
 
 
257
  elif content_type == "pdf" and file:
258
  with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as temp_file:
259
  content_bytes = await file.read()
@@ -262,57 +354,79 @@ async def process_content(
262
 
263
  text_content = extract_text_from_pdf(temp_file_path)
264
  os.unlink(temp_file_path)
 
 
265
  elif content_type == "youtube" and youtube_url:
266
  text_content = extract_text_from_youtube(youtube_url)
267
- else:
268
- raise HTTPException(status_code=400, detail="Invalid content")
269
 
270
  if len(text_content) < 50:
271
- raise HTTPException(status_code=400, detail="Content too short")
 
 
 
 
 
 
 
 
272
 
 
 
 
 
 
273
  content_hash = hashlib.md5(text_content.encode()).hexdigest()
274
 
275
  conn = sqlite3.connect(DB_PATH)
276
  cursor = conn.cursor()
277
 
278
- # Check if exists
279
  cursor.execute("SELECT id FROM sessions WHERE content_hash = ?", (content_hash,))
280
  existing = cursor.fetchone()
281
 
282
  if existing:
283
- conn.close()
284
- return {"session_id": existing[0], "is_existing": True}
285
-
286
- # Create new session
287
- session_id = generate_id(text_content[:100])
288
 
 
289
  cursor.execute(
290
- "INSERT INTO sessions (id, title, content_type, difficulty, content_hash) VALUES (?, ?, ?, ?, ?)",
291
- (session_id, title, content_type, difficulty, content_hash)
 
 
292
  )
293
 
294
- # Generate materials
295
- questions = generate_questions(text_content, difficulty, 5)
296
- flashcards = generate_flashcards(text_content, 3)
297
-
298
  # Save questions
299
  for q in questions:
300
  cursor.execute(
301
- "INSERT INTO questions (id, session_id, question_text, question_type, options, correct_answer, difficulty, explanation) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
302
- (q["id"], session_id, q["question_text"], q["question_type"], q.get("options"), q["correct_answer"], q["difficulty"], q.get("explanation", ""))
 
 
 
303
  )
304
 
305
  # Save flashcards
306
  for fc in flashcards:
307
  cursor.execute(
308
- "INSERT INTO flashcards (id, session_id, front, back, category, difficulty) VALUES (?, ?, ?, ?, ?, ?)",
309
- (fc["id"], session_id, fc["front"], fc["back"], fc["category"], fc["difficulty"])
 
 
310
  )
311
 
312
  conn.commit()
313
  conn.close()
314
 
315
- return {"session_id": session_id, "is_existing": False}
 
 
 
 
 
316
 
317
  @app.get("/api/session/{session_id}")
318
  async def get_session(session_id: str):
@@ -338,12 +452,6 @@ async def get_session(session_id: str):
338
  cursor.execute("SELECT * FROM flashcards WHERE session_id = ?", (session_id,))
339
  flashcards = [dict(row) for row in cursor.fetchall()]
340
 
341
- cursor.execute("SELECT * FROM notes WHERE session_id = ?", (session_id,))
342
- notes = [dict(row) for row in cursor.fetchall()]
343
-
344
- cursor.execute("SELECT * FROM highlights WHERE session_id = ?", (session_id,))
345
- highlights = [dict(row) for row in cursor.fetchall()]
346
-
347
  # Calculate performance
348
  total_questions = len(questions)
349
  correct_answers = sum(1 for q in questions if q.get("is_correct") == 1)
@@ -357,14 +465,14 @@ async def get_session(session_id: str):
357
  "materials": {
358
  "questions": questions,
359
  "flashcards": flashcards,
360
- "notes": notes,
361
- "highlights": highlights
362
  },
363
  "summary": {
364
  "question_count": total_questions,
365
  "flashcard_count": len(flashcards),
366
- "note_count": len(notes),
367
- "highlight_count": len(highlights)
368
  },
369
  "performance": {
370
  "total_questions": total_questions,
@@ -407,11 +515,12 @@ async def submit_answer(
407
  user_answer: str = Form(...),
408
  time_spent: int = Form(0)
409
  ):
410
- """Submit an answer"""
411
  conn = sqlite3.connect(DB_PATH)
412
  cursor = conn.cursor()
413
 
414
- cursor.execute("SELECT correct_answer FROM questions WHERE id = ? AND session_id = ?", (question_id, session_id))
 
415
  result = cursor.fetchone()
416
 
417
  if not result:
@@ -419,96 +528,48 @@ async def submit_answer(
419
  raise HTTPException(status_code=404, detail="Question not found")
420
 
421
  correct_answer = result[0]
422
- is_correct = 1 if str(user_answer).strip().lower() == str(correct_answer).strip().lower() else 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
423
 
424
  cursor.execute(
425
  "UPDATE questions SET user_answer = ?, is_correct = ?, time_spent = ? WHERE id = ? AND session_id = ?",
426
  (user_answer, is_correct, time_spent, question_id, session_id)
427
  )
428
 
429
- # Update profile
430
- cursor.execute("INSERT OR IGNORE INTO user_profile (id) VALUES (1)")
431
- cursor.execute(
432
- "UPDATE user_profile SET total_questions = total_questions + 1, correct_answers = correct_answers + ? WHERE id = 1",
433
- (is_correct,)
434
- )
435
-
436
- conn.commit()
437
- conn.close()
438
-
439
- return {"is_correct": bool(is_correct), "correct_answer": correct_answer}
440
-
441
- @app.post("/api/analyze-text")
442
- async def analyze_text(text: str = Form(...), session_id: str = Form(...)):
443
- """Analyze selected text"""
444
- words = text.split()
445
- definitions = []
446
-
447
- for word in words[:3]:
448
- if len(word) > 3:
449
- definitions.append({
450
- "word": word,
451
- "definition": f"Key term in context",
452
- "example": f"Used in: '{text[:50]}...'"
453
- })
454
-
455
- # Save highlight
456
- conn = sqlite3.connect(DB_PATH)
457
- cursor = conn.cursor()
458
-
459
- highlight_id = generate_id(text)
460
- cursor.execute(
461
- "INSERT OR IGNORE INTO highlights (id, session_id, text, context) VALUES (?, ?, ?, ?)",
462
- (highlight_id, session_id, text, json.dumps(definitions))
463
- )
464
-
465
  conn.commit()
466
  conn.close()
467
 
468
  return {
469
- "text": text,
470
- "definitions": definitions,
471
- "explanation": f"Analysis of selected text ({len(words)} words)",
472
- "suggested_actions": ["Create flashcard", "Add to notes"]
473
- }
474
-
475
- @app.get("/api/user/profile")
476
- async def get_user_profile():
477
- """Get user profile"""
478
- conn = sqlite3.connect(DB_PATH)
479
- cursor = conn.cursor()
480
-
481
- cursor.execute("SELECT * FROM user_profile WHERE id = 1")
482
- profile = cursor.fetchone()
483
-
484
- if not profile:
485
- cursor.execute("INSERT INTO user_profile (id) VALUES (1)")
486
- conn.commit()
487
- cursor.execute("SELECT * FROM user_profile WHERE id = 1")
488
- profile = cursor.fetchone()
489
-
490
- total = profile[1] or 0
491
- correct = profile[2] or 0
492
- accuracy = round((correct / total * 100) if total > 0 else 0, 1)
493
-
494
- conn.close()
495
-
496
- return {
497
- "profile": {
498
- "total_questions": total,
499
- "correct_answers": correct,
500
- "total_study_time": profile[3] or 0,
501
- "accuracy": accuracy
502
- },
503
- "insights": {
504
- "weaknesses": ["Practice more to identify areas"],
505
- "strengths": ["Getting started is the first step!"]
506
- }
507
  }
508
 
509
  @app.delete("/api/session/{session_id}")
510
  async def delete_session(session_id: str):
511
- """Delete session"""
512
  conn = sqlite3.connect(DB_PATH)
513
  cursor = conn.cursor()
514
 
@@ -523,45 +584,32 @@ async def delete_session(session_id: str):
523
 
524
  return {"message": "Session deleted"}
525
 
526
- @app.post("/api/update-study-time")
527
- async def update_study_time(session_id: str = Form(...), study_time: int = Form(...)):
528
- """Update study time"""
529
- conn = sqlite3.connect(DB_PATH)
530
- cursor = conn.cursor()
531
-
532
- cursor.execute("UPDATE sessions SET last_accessed = CURRENT_TIMESTAMP WHERE id = ?", (session_id,))
533
- cursor.execute("INSERT OR IGNORE INTO user_profile (id) VALUES (1)")
534
- cursor.execute(
535
- "UPDATE user_profile SET total_study_time = COALESCE(total_study_time, 0) + ? WHERE id = 1",
536
- (study_time,)
537
- )
538
-
539
- conn.commit()
540
- conn.close()
541
-
542
- return {"message": "Study time updated"}
543
 
544
- # Serve frontend
545
  @app.get("/")
546
  async def serve_frontend():
547
  """Serve the main frontend page"""
548
- try:
549
- return HTMLResponse(content=open("index.html").read())
550
- except:
551
- return HTMLResponse(content="<h1>StudyFlow AI Backend Running</h1>")
552
 
553
  @app.get("/app.js")
554
  async def serve_js():
555
  """Serve JavaScript"""
 
 
 
 
 
 
556
  try:
557
- return HTMLResponse(content=open("app.js").read(), media_type="application/javascript")
 
558
  except:
559
- return HTMLResponse(content="console.log('JS loaded');", media_type="application/javascript")
560
-
561
- # Health check
562
- @app.get("/health")
563
- async def health_check():
564
- return {"status": "healthy", "timestamp": datetime.now().isoformat()}
565
 
566
  if __name__ == "__main__":
567
  import uvicorn
 
1
  """
2
+ StudyFlow AI Backend - Complete Working Version with REAL Question Generation
3
  """
4
  import os
5
  import json
6
  import sqlite3
7
  import hashlib
8
  import tempfile
9
+ import re
10
  from datetime import datetime
11
+ from typing import List, Dict
12
 
13
  from fastapi import FastAPI, UploadFile, File, Form, HTTPException
14
  from fastapi.responses import JSONResponse, HTMLResponse
15
  from fastapi.middleware.cors import CORSMiddleware
16
+ from fastapi.staticfiles import StaticFiles
17
  import PyPDF2
18
  from youtube_transcript_api import YouTubeTranscriptApi
19
 
20
  # Initialize FastAPI
21
+ app = FastAPI(title="StudyFlow AI", version="2.0.0")
22
 
23
  # CORS middleware
24
  app.add_middleware(
 
45
  content_type TEXT NOT NULL,
46
  difficulty TEXT NOT NULL,
47
  content_hash TEXT,
48
+ raw_content TEXT,
49
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
50
  last_accessed TIMESTAMP DEFAULT CURRENT_TIMESTAMP
51
  )
 
110
  )
111
  ''')
112
 
 
 
 
 
 
 
 
 
 
 
 
113
  conn.commit()
114
  conn.close()
115
 
116
  # Initialize database
117
  init_db()
118
 
 
119
  def generate_id(text: str = None):
120
  """Generate a unique ID"""
121
  import uuid
 
129
  with open(file_path, 'rb') as file:
130
  pdf_reader = PyPDF2.PdfReader(file)
131
  text = ""
132
+ for page in pdf_reader.pages[:15]: # First 15 pages
133
  page_text = page.extract_text()
134
  if page_text:
135
  text += page_text + "\n"
136
+ return text[:15000] # Limit text length
137
  except Exception as e:
138
+ print(f"PDF extraction error: {str(e)}")
139
+ return ""
140
 
141
  def extract_text_from_youtube(url: str) -> str:
142
  """Extract transcript from YouTube video"""
 
146
  elif "youtu.be/" in url:
147
  video_id = url.split("/")[-1].split("?")[0]
148
  else:
149
+ return ""
150
 
151
  transcript = YouTubeTranscriptApi.get_transcript(video_id)
152
+ text = " ".join([entry['text'] for entry in transcript[:50]]) # First 50 entries
153
+ return text[:15000]
154
  except Exception as e:
155
+ print(f"YouTube extraction error: {str(e)}")
156
+ return ""
157
+
158
+ def extract_key_concepts(text: str, count: int = 8) -> List[str]:
159
+ """Extract key concepts from text using NLP-like approach"""
160
+ # Clean text
161
+ text = text.lower()
162
+
163
+ # Remove common stop words
164
+ stop_words = {'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by', 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', 'having', 'do', 'does', 'did', 'doing', 'but', 'so', 'if', 'then', 'else', 'when', 'where', 'which', 'what', 'who', 'whom', 'this', 'that', 'these', 'those', 'it', 'they', 'we', 'you', 'he', 'she', 'it', 'them', 'her', 'him', 'us'}
165
+
166
+ # Split into words and count frequencies
167
+ words = re.findall(r'\b[a-z]{4,}\b', text)
168
+ freq = {}
169
+ for word in words:
170
+ if word not in stop_words:
171
+ freq[word] = freq.get(word, 0) + 1
172
+
173
+ # Get top words as concepts
174
+ sorted_words = sorted(freq.items(), key=lambda x: x[1], reverse=True)
175
+ concepts = [word for word, _ in sorted_words[:count]]
176
+
177
+ # Also extract phrases (2-3 word sequences)
178
+ phrases = re.findall(r'\b[a-z]{3,}\s+[a-z]{3,}\b', text)
179
+ phrase_freq = {}
180
+ for phrase in phrases[:50]:
181
+ phrase_freq[phrase] = phrase_freq.get(phrase, 0) + 1
182
+
183
+ top_phrases = sorted(phrase_freq.items(), key=lambda x: x[1], reverse=True)[:3]
184
+ concepts.extend([phrase for phrase, _ in top_phrases])
185
+
186
+ # Remove duplicates and limit
187
+ unique_concepts = []
188
+ for concept in concepts:
189
+ if concept not in unique_concepts:
190
+ unique_concepts.append(concept)
191
+
192
+ return unique_concepts[:count]
193
 
194
+ def generate_questions_from_content(text: str, difficulty: str, count: int = 5) -> List[Dict]:
195
+ """Generate REAL questions based on the actual content"""
196
+
197
+ # Clean and prepare text
198
+ text = text.replace('\n', ' ').strip()
199
  sentences = re.split(r'[.!?]+', text)
200
+ sentences = [s.strip() for s in sentences if len(s.strip()) > 30]
201
 
202
+ if len(sentences) < 3:
203
+ # Fallback for very short text
204
+ sentences = text.split('.')[:5]
205
 
206
+ concepts = extract_key_concepts(text, 6)
207
  questions = []
208
+
209
+ for i in range(min(count, len(sentences))):
210
+ sentence = sentences[i]
211
+ qid = generate_id(f"q_{i}_{difficulty}")
212
 
213
  if difficulty == "easy":
214
+ # Fill in the blank with a key word from the sentence
215
  words = sentence.split()
216
+ if len(words) > 4:
217
+ # Find a meaningful word to blank out (not first or last 2 words)
218
+ blank_pos = min(len(words) - 3, max(2, len(words) // 2))
219
+ blank_word = words[blank_pos]
220
+ question_text = sentence.replace(blank_word, "_______", 1)
221
+
222
  questions.append({
223
  "id": qid,
224
+ "question_text": f"Complete this sentence from the material: {question_text}",
225
  "question_type": "fill_blank",
226
+ "options": None,
227
  "correct_answer": blank_word,
228
+ "difficulty": "easy",
229
+ "explanation": f"The original sentence uses the word '{blank_word}' which is key to understanding this concept.",
230
+ "concept": concepts[i % len(concepts)] if concepts else "Key Concept"
231
+ })
232
+ else:
233
+ # Short sentence - use true/false
234
+ questions.append({
235
+ "id": qid,
236
+ "question_text": f"Based on the material: '{sentence[:100]}'",
237
+ "question_type": "true_false",
238
+ "options": None,
239
+ "correct_answer": "True",
240
+ "difficulty": "easy",
241
+ "explanation": "This statement appears in the study material.",
242
+ "concept": concepts[i % len(concepts)] if concepts else "Key Concept"
243
  })
244
 
245
  elif difficulty == "medium":
246
+ # Multiple choice based on content
247
+ concept = concepts[i % len(concepts)] if concepts else "the topic"
248
+
249
+ # Generate plausible distractors
250
+ other_concepts = [c for c in concepts if c != concept][:3]
251
+ while len(other_concepts) < 3:
252
+ other_concepts.extend(["Important detail", "Background information", "Supporting example"])
253
+
254
+ options = [f"The concept of {concept}", f"{other_concepts[0]}", f"{other_concepts[1]}", f"{other_concepts[2]}"]
255
+
256
  questions.append({
257
  "id": qid,
258
+ "question_text": f"What is the main idea expressed in: '{sentence[:120]}...'?",
259
+ "question_type": "multiple_choice",
260
+ "options": json.dumps(options),
261
+ "correct_answer": options[0],
262
+ "difficulty": "medium",
263
+ "explanation": f"The text emphasizes {concept} as a central theme. Understanding this helps grasp the overall message.",
264
+ "concept": concept
265
  })
266
 
267
  else: # hard
268
+ # Short answer requiring deeper understanding
269
+ concept = concepts[i % len(concepts)] if concepts else "key concept"
270
+
271
  questions.append({
272
  "id": qid,
273
+ "question_text": f"Explain the significance of '{concept}' based on this passage: '{sentence[:150]}...' What makes it important to understanding the material?",
274
+ "question_type": "short_answer",
275
+ "options": None,
276
+ "correct_answer": f"The passage presents '{concept}' as an important element that contributes to the overall understanding of the subject. A good answer should explain how it fits into the broader context.",
277
+ "difficulty": "hard",
278
+ "explanation": "When studying, focus on how key concepts connect to each other and the main theme.",
279
+ "concept": concept
280
  })
281
 
282
+ # If we couldn't generate enough questions, add some general ones about the content
283
+ while len(questions) < count:
284
+ concept = concepts[len(questions) % len(concepts)] if concepts else "the material"
285
+ qid = generate_id(f"q_gen_{len(questions)}")
286
+ questions.append({
287
+ "id": qid,
288
+ "question_text": f"What is the main focus of the section discussing '{concept}'?",
289
+ "question_type": "short_answer",
290
+ "options": None,
291
+ "correct_answer": f"The section focuses on explaining '{concept}' and its relevance to the overall topic.",
292
+ "difficulty": difficulty,
293
+ "explanation": "Try to identify the main ideas when studying.",
294
+ "concept": concept
295
+ })
296
+
297
+ return questions[:count]
298
 
299
+ def generate_flashcards_from_content(text: str, concepts: List[str], count: int = 5) -> List[Dict]:
300
+ """Generate flashcards based on actual content"""
301
+
302
  sentences = re.split(r'[.!?]+', text)
303
+ sentences = [s.strip() for s in sentences if len(s.strip()) > 30]
304
 
305
  flashcards = []
306
+ for i in range(min(count, len(concepts))):
307
+ concept = concepts[i]
308
  fcid = generate_id(f"fc_{i}")
309
 
310
+ # Find a sentence that contains this concept
311
+ context = ""
312
+ for sentence in sentences:
313
+ if concept.lower() in sentence.lower():
314
+ context = sentence[:150]
315
+ break
316
+
317
+ if not context:
318
+ context = sentences[i % len(sentences)] if sentences else text[:150]
319
+
320
  flashcards.append({
321
  "id": fcid,
322
+ "front": f"What is '{concept}'?",
323
+ "back": f"{context}... This concept is key to understanding the material.",
324
+ "category": "Key Concept",
325
  "difficulty": "medium"
326
  })
327
 
328
  return flashcards
329
 
 
330
  @app.post("/api/process-content")
331
  async def process_content(
332
  content_type: str = Form(...),
 
336
  file: UploadFile = File(None),
337
  youtube_url: str = Form(None)
338
  ):
339
+ """Process uploaded content and generate REAL questions"""
340
+
341
+ print(f"Processing: type={content_type}, difficulty={difficulty}, title={title}")
342
 
343
  text_content = ""
344
+
345
  if content_type == "text" and content:
346
+ text_content = content[:15000]
347
+ print(f"Text length: {len(text_content)} chars")
348
+
349
  elif content_type == "pdf" and file:
350
  with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as temp_file:
351
  content_bytes = await file.read()
 
354
 
355
  text_content = extract_text_from_pdf(temp_file_path)
356
  os.unlink(temp_file_path)
357
+ print(f"PDF text length: {len(text_content)} chars")
358
+
359
  elif content_type == "youtube" and youtube_url:
360
  text_content = extract_text_from_youtube(youtube_url)
361
+ print(f"YouTube transcript length: {len(text_content)} chars")
 
362
 
363
  if len(text_content) < 50:
364
+ raise HTTPException(status_code=400, detail=f"Content too short ({len(text_content)} chars). Minimum 50 chars required.")
365
+
366
+ # Extract concepts from the actual content
367
+ concepts = extract_key_concepts(text_content, 8)
368
+ print(f"Extracted concepts: {concepts}")
369
+
370
+ # Generate REAL questions based on content
371
+ questions = generate_questions_from_content(text_content, difficulty, 5)
372
+ print(f"Generated {len(questions)} questions")
373
 
374
+ # Generate flashcards
375
+ flashcards = generate_flashcards_from_content(text_content, concepts, 4)
376
+
377
+ # Create session
378
+ session_id = generate_id(title)
379
  content_hash = hashlib.md5(text_content.encode()).hexdigest()
380
 
381
  conn = sqlite3.connect(DB_PATH)
382
  cursor = conn.cursor()
383
 
384
+ # Check if session exists
385
  cursor.execute("SELECT id FROM sessions WHERE content_hash = ?", (content_hash,))
386
  existing = cursor.fetchone()
387
 
388
  if existing:
389
+ session_id = existing[0]
390
+ # Clear old questions
391
+ cursor.execute("DELETE FROM questions WHERE session_id = ?", (session_id,))
392
+ cursor.execute("DELETE FROM flashcards WHERE session_id = ?", (session_id,))
 
393
 
394
+ # Save session
395
  cursor.execute(
396
+ """INSERT OR REPLACE INTO sessions
397
+ (id, title, content_type, difficulty, content_hash, raw_content, last_accessed)
398
+ VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)""",
399
+ (session_id, title, content_type, difficulty, content_hash, text_content[:5000])
400
  )
401
 
 
 
 
 
402
  # Save questions
403
  for q in questions:
404
  cursor.execute(
405
+ """INSERT INTO questions
406
+ (id, session_id, question_text, question_type, options, correct_answer, difficulty, explanation)
407
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
408
+ (q["id"], session_id, q["question_text"], q["question_type"],
409
+ q.get("options"), q["correct_answer"], q["difficulty"], q.get("explanation", ""))
410
  )
411
 
412
  # Save flashcards
413
  for fc in flashcards:
414
  cursor.execute(
415
+ """INSERT INTO flashcards
416
+ (id, session_id, front, back, category, difficulty)
417
+ VALUES (?, ?, ?, ?, ?, ?)""",
418
+ (fc["id"], session_id, fc["front"], fc["back"], fc["category"], fc.get("difficulty", "medium"))
419
  )
420
 
421
  conn.commit()
422
  conn.close()
423
 
424
+ return {
425
+ "session_id": session_id,
426
+ "is_existing": existing is not None,
427
+ "question_count": len(questions),
428
+ "flashcard_count": len(flashcards)
429
+ }
430
 
431
  @app.get("/api/session/{session_id}")
432
  async def get_session(session_id: str):
 
452
  cursor.execute("SELECT * FROM flashcards WHERE session_id = ?", (session_id,))
453
  flashcards = [dict(row) for row in cursor.fetchall()]
454
 
 
 
 
 
 
 
455
  # Calculate performance
456
  total_questions = len(questions)
457
  correct_answers = sum(1 for q in questions if q.get("is_correct") == 1)
 
465
  "materials": {
466
  "questions": questions,
467
  "flashcards": flashcards,
468
+ "notes": [],
469
+ "highlights": []
470
  },
471
  "summary": {
472
  "question_count": total_questions,
473
  "flashcard_count": len(flashcards),
474
+ "note_count": 0,
475
+ "highlight_count": 0
476
  },
477
  "performance": {
478
  "total_questions": total_questions,
 
515
  user_answer: str = Form(...),
516
  time_spent: int = Form(0)
517
  ):
518
+ """Submit an answer for evaluation"""
519
  conn = sqlite3.connect(DB_PATH)
520
  cursor = conn.cursor()
521
 
522
+ cursor.execute("SELECT correct_answer, question_type FROM questions WHERE id = ? AND session_id = ?",
523
+ (question_id, session_id))
524
  result = cursor.fetchone()
525
 
526
  if not result:
 
528
  raise HTTPException(status_code=404, detail="Question not found")
529
 
530
  correct_answer = result[0]
531
+ question_type = result[1]
532
+
533
+ # Evaluate answer based on question type
534
+ is_correct = 0
535
+
536
+ if question_type == "multiple_choice":
537
+ # Exact match for multiple choice
538
+ is_correct = 1 if user_answer.strip() == correct_answer.strip() else 0
539
+ elif question_type == "true_false":
540
+ # Case-insensitive match for true/false
541
+ is_correct = 1 if user_answer.strip().lower() == correct_answer.strip().lower() else 0
542
+ elif question_type == "fill_blank":
543
+ # Partial matching for fill in blank
544
+ user_clean = user_answer.strip().lower()
545
+ correct_clean = correct_answer.strip().lower()
546
+ is_correct = 1 if (user_clean == correct_clean or correct_clean in user_clean or user_clean in correct_clean) else 0
547
+ elif question_type == "short_answer":
548
+ # Flexible matching for short answers
549
+ user_clean = user_answer.strip().lower()
550
+ correct_clean = correct_answer.strip().lower()
551
+ # Check if answer contains key concepts
552
+ key_words = re.findall(r'\b[a-z]{4,}\b', correct_clean)
553
+ matched = sum(1 for word in key_words if word in user_clean)
554
+ is_correct = 1 if matched >= len(key_words) * 0.4 else 0 # 40% keyword match
555
 
556
  cursor.execute(
557
  "UPDATE questions SET user_answer = ?, is_correct = ?, time_spent = ? WHERE id = ? AND session_id = ?",
558
  (user_answer, is_correct, time_spent, question_id, session_id)
559
  )
560
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
561
  conn.commit()
562
  conn.close()
563
 
564
  return {
565
+ "is_correct": bool(is_correct),
566
+ "correct_answer": correct_answer,
567
+ "feedback": "Correct! Great job!" if is_correct else f"The correct answer is: {correct_answer}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
568
  }
569
 
570
  @app.delete("/api/session/{session_id}")
571
  async def delete_session(session_id: str):
572
+ """Delete a session"""
573
  conn = sqlite3.connect(DB_PATH)
574
  cursor = conn.cursor()
575
 
 
584
 
585
  return {"message": "Session deleted"}
586
 
587
+ @app.get("/health")
588
+ async def health_check():
589
+ """Health check endpoint"""
590
+ return {"status": "healthy", "timestamp": datetime.now().isoformat()}
 
 
 
 
 
 
 
 
 
 
 
 
 
591
 
592
+ # Serve static files
593
  @app.get("/")
594
  async def serve_frontend():
595
  """Serve the main frontend page"""
596
+ with open("index.html", "r") as f:
597
+ return HTMLResponse(content=f.read())
 
 
598
 
599
  @app.get("/app.js")
600
  async def serve_js():
601
  """Serve JavaScript"""
602
+ with open("app.js", "r") as f:
603
+ return HTMLResponse(content=f.read(), media_type="application/javascript")
604
+
605
+ @app.get("/styles.css")
606
+ async def serve_css():
607
+ """Serve CSS"""
608
  try:
609
+ with open("styles.css", "r") as f:
610
+ return HTMLResponse(content=f.read(), media_type="text/css")
611
  except:
612
+ return HTMLResponse(content="", media_type="text/css")
 
 
 
 
 
613
 
614
  if __name__ == "__main__":
615
  import uvicorn