andevs commited on
Commit
4b25b99
·
verified ·
1 Parent(s): d144bec

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +514 -429
app.py CHANGED
@@ -1,26 +1,31 @@
1
  """
2
- StudyFlow Backend API for Hugging Face Spaces
3
- Complete implementation with PDF processing and content generation
4
  """
5
 
6
- from fastapi import FastAPI, File, UploadFile, Form, HTTPException
7
  from fastapi.middleware.cors import CORSMiddleware
8
- from fastapi.responses import JSONResponse
9
  from datetime import datetime
10
  import json
11
  import PyPDF2
12
  import io
13
  import re
14
- from typing import List, Dict, Any
15
  import os
 
 
 
 
 
16
 
17
  app = FastAPI(
18
- title="StudyFlow API",
19
- description="AI-powered learning assistant backend",
20
- version="1.0.0"
21
  )
22
 
23
- # Enable CORS for frontend
24
  app.add_middleware(
25
  CORSMiddleware,
26
  allow_origins=["*"],
@@ -29,506 +34,586 @@ app.add_middleware(
29
  allow_headers=["*"],
30
  )
31
 
32
- # Store for user sessions (in production, use database)
33
  user_sessions = {}
 
34
 
35
- def extract_key_concepts(text: str, max_concepts: int = 5) -> List[str]:
36
- """Extract key concepts from text using simple NLP techniques"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  # Find capitalized words and phrases
38
  words = re.findall(r'\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*\b', text)
39
  word_freq = {}
40
 
41
  for word in words:
42
- if len(word) > 3: # Ignore short words
43
  word_freq[word] = word_freq.get(word, 0) + 1
44
 
45
- # Sort by frequency and get top concepts
46
- sorted_concepts = sorted(word_freq.items(), key=lambda x: x[1], reverse=True)
47
- return [concept for concept, _ in sorted_concepts[:max_concepts]]
 
 
 
 
 
 
 
 
 
 
 
48
 
49
- def generate_question_from_concept(concept: str, difficulty: str, question_type: str = "multiple_choice") -> Dict[str, Any]:
50
- """Generate a question based on a concept"""
51
- question_id = f"q_{datetime.now().timestamp()}"
 
52
 
53
- if question_type == "multiple_choice":
54
- return {
55
- "id": question_id,
56
- "type": "multiple_choice",
57
- "question": f"What is the significance of {concept}?",
58
- "options": [
59
- f"{concept} is a fundamental concept in this field",
60
- f"{concept} represents a technical specification",
61
- f"{concept} is a historical figure",
62
- f"{concept} is a recent development"
63
- ],
64
- "correct_answer": 0,
65
- "explanation": f"{concept} is mentioned throughout the text as a key concept.",
66
- "difficulty": difficulty,
67
- "concept": concept
68
- }
69
- else: # short_answer
70
- return {
71
- "id": question_id,
72
- "type": "short_answer",
73
- "question": f"Explain {concept} in your own words.",
74
- "correct_answer": f"{concept} is an important concept discussed in the material.",
75
- "explanation": f"This requires understanding of {concept} from the context.",
76
- "difficulty": difficulty,
77
- "concept": concept
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  }
 
 
 
79
 
80
- def generate_flashcard_from_concept(concept: str) -> Dict[str, Any]:
81
- """Generate a flashcard from a concept"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  return {
83
- "id": f"f_{datetime.now().timestamp()}",
84
- "front": f"What is {concept}?",
85
- "back": f"{concept} is a key concept discussed in the material.",
86
- "category": "Key Concepts",
87
- "difficulty": "medium",
88
- "lastReviewed": None,
89
- "nextReview": None,
90
- "easeFactor": 2.5
91
  }
92
 
93
  @app.get("/")
94
  async def root():
95
- """Root endpoint with API information"""
96
  return {
97
- "service": "StudyFlow API",
98
- "version": "1.0.0",
99
  "status": "running",
100
- "endpoints": {
101
- "generate_questions": "POST /api/generate-questions",
102
- "generate_flashcards": "POST /api/generate-flashcards",
103
- "summarize": "POST /api/summarize",
104
- "explain": "POST /api/explain",
105
- "upload_pdf": "POST /api/upload-pdf",
106
- "health": "GET /health"
107
- },
108
- "documentation": "/docs"
109
- }
110
-
111
- @app.get("/health")
112
- async def health():
113
- """Health check endpoint"""
114
- return {
115
- "status": "healthy",
116
- "timestamp": datetime.now().isoformat(),
117
- "sessions": len(user_sessions)
118
  }
119
 
120
- @app.post("/api/generate-questions")
121
- async def generate_questions(
122
- text: str = Form(...),
123
- difficulty: str = Form("medium"),
124
- mode: str = Form("mixed"),
125
- num_questions: int = Form(5)
126
- ):
127
- """
128
- Generate educational questions from text
129
-
130
- Args:
131
- text: Input text to analyze
132
- difficulty: Difficulty level (easy, medium, hard)
133
- mode: Question type (theory, practice, mixed, flashcards)
134
- num_questions: Number of questions to generate
135
- """
136
- try:
137
- if not text or len(text.strip()) < 100:
138
- raise HTTPException(
139
- status_code=400,
140
- detail="Text too short. Please provide at least 100 characters."
141
- )
142
-
143
- # Validate inputs
144
- if difficulty not in ["easy", "medium", "hard"]:
145
- difficulty = "medium"
146
-
147
- if mode not in ["theory", "practice", "mixed", "flashcards"]:
148
- mode = "mixed"
149
-
150
- num_questions = max(1, min(num_questions, 20)) # Limit to 1-20 questions
151
-
152
- # Extract key concepts from text
153
- key_concepts = extract_key_concepts(text, max_concepts=min(num_questions, 10))
154
-
155
- # Generate questions based on mode
156
- questions = []
157
- concept_index = 0
158
-
159
- while len(questions) < num_questions and concept_index < len(key_concepts):
160
- if mode == "theory" or mode == "mixed":
161
- # Generate theory questions (short answer)
162
- questions.append(generate_question_from_concept(
163
- key_concepts[concept_index],
164
- difficulty,
165
- "short_answer"
166
- ))
167
- concept_index += 1
168
-
169
- if len(questions) >= num_questions:
170
- break
171
-
172
- if mode == "practice" or mode == "mixed":
173
- # Generate practice questions (multiple choice)
174
- questions.append(generate_question_from_concept(
175
- key_concepts[concept_index],
176
- difficulty,
177
- "multiple_choice"
178
- ))
179
- concept_index += 1
180
-
181
- if len(questions) >= num_questions:
182
- break
183
-
184
- # If we don't have enough concepts, add generic questions
185
- while len(questions) < num_questions:
186
- generic_concept = "the main topic" if len(questions) % 2 == 0 else "key concepts"
187
- questions.append(generate_question_from_concept(
188
- generic_concept,
189
- difficulty,
190
- "multiple_choice" if len(questions) % 2 == 0 else "short_answer"
191
- ))
192
-
193
- return {
194
- "success": True,
195
- "questions": questions[:num_questions],
196
- "concepts": key_concepts,
197
- "count": len(questions[:num_questions]),
198
- "difficulty": difficulty,
199
- "mode": mode,
200
- "generated_at": datetime.now().isoformat()
201
- }
202
-
203
- except HTTPException:
204
- raise
205
- except Exception as e:
206
- raise HTTPException(
207
- status_code=500,
208
- detail=f"Failed to generate questions: {str(e)}"
209
- )
210
-
211
- @app.post("/api/generate-flashcards")
212
- async def generate_flashcards(
213
- text: str = Form(...),
214
  difficulty: str = Form("medium")
215
  ):
216
- """
217
- Generate flashcards from text
218
-
219
- Args:
220
- text: Input text to analyze
221
- difficulty: Difficulty level for flashcards
222
- """
223
  try:
224
- if not text or len(text.strip()) < 100:
225
- raise HTTPException(
226
- status_code=400,
227
- detail="Text too short. Please provide at least 100 characters."
228
- )
229
 
230
- # Extract key concepts
231
- key_concepts = extract_key_concepts(text, max_concepts=10)
232
-
233
- # Generate flashcards
234
- flashcards = []
235
- for i, concept in enumerate(key_concepts):
236
- if i >= 10: # Limit to 10 flashcards
237
- break
238
-
239
- flashcard = generate_flashcard_from_concept(concept)
240
 
241
- # Adjust based on difficulty
242
- if difficulty == "easy":
243
- flashcard["difficulty"] = "easy"
244
- flashcard["back"] = f"{concept} is a basic concept mentioned in the text."
245
- elif difficulty == "hard":
246
- flashcard["difficulty"] = "hard"
247
- flashcard["back"] = f"{concept} represents a complex concept requiring deeper understanding."
248
 
249
- flashcards.append(flashcard)
250
-
251
- # Add some generic flashcards if we don't have enough concepts
252
- generic_flashcards = [
253
- {
254
- "id": f"f_gen_{i}",
255
- "front": "What is the main topic of this material?",
256
- "back": "The text discusses key concepts and principles.",
257
- "category": "Overview",
258
- "difficulty": difficulty,
259
- "lastReviewed": None,
260
- "nextReview": None,
261
- "easeFactor": 2.5
262
  }
263
- for i in range(max(0, 5 - len(flashcards)))
264
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
 
266
- flashcards.extend(generic_flashcards)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
 
268
  return {
269
  "success": True,
270
- "flashcards": flashcards,
271
- "count": len(flashcards),
272
- "generated_at": datetime.now().isoformat()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
  }
274
 
275
  except HTTPException:
276
  raise
277
  except Exception as e:
278
- raise HTTPException(
279
- status_code=500,
280
- detail=f"Failed to generate flashcards: {str(e)}"
281
- )
282
 
283
- @app.post("/api/summarize")
284
- async def summarize(
285
- text: str = Form(...),
286
- difficulty: str = Form("medium")
287
  ):
288
- """
289
- Generate a summary of the text
290
-
291
- Args:
292
- text: Text to summarize
293
- difficulty: Reading level for summary
294
- """
295
  try:
296
- if not text:
297
- raise HTTPException(
298
- status_code=400,
299
- detail="No text provided."
300
- )
301
 
302
- # Simple summarization - first 200 words
303
- words = text.split()
304
- word_count = len(words)
305
-
306
- if word_count < 100:
307
- summary = text
308
  else:
309
- # Take first 10% of text for summary
310
- summary_length = max(50, min(500, word_count // 10))
311
- summary_words = words[:summary_length]
312
- summary = ' '.join(summary_words)
313
- if word_count > summary_length:
314
- summary += "..."
315
-
316
- # Adjust summary based on difficulty
317
- if difficulty == "easy":
318
- intro = "This is a simple summary:\n\n"
319
- elif difficulty == "hard":
320
- intro = "Detailed analysis:\n\n"
321
- else:
322
- intro = "Summary:\n\n"
323
 
324
- # Add metadata
325
- reading_time = max(1, word_count // 200) # 200 words per minute
 
 
 
 
 
 
 
 
 
326
 
327
- full_summary = f"""{intro}{summary}
328
-
329
- Key Information:
330
- • Word count: {word_count}
331
- • Estimated reading time: {reading_time} minute{'s' if reading_time != 1 else ''}
332
- • Key concepts: {', '.join(extract_key_concepts(text, 3))}
333
-
334
- Study recommendations:
335
- 1. Read through the summary first
336
- 2. Identify unfamiliar terms
337
- 3. Create flashcards for key concepts
338
- 4. Test your understanding with practice questions"""
339
-
340
  return {
341
  "success": True,
342
- "summary": full_summary,
343
- "word_count": word_count,
344
- "reading_time": reading_time,
345
- "generated_at": datetime.now().isoformat()
 
 
 
 
 
 
 
 
 
 
 
346
  }
347
 
348
- except HTTPException:
349
- raise
350
  except Exception as e:
351
- raise HTTPException(
352
- status_code=500,
353
- detail=f"Failed to generate summary: {str(e)}"
354
- )
355
 
356
- @app.post("/api/explain")
357
- async def explain(
358
- topic: str = Form(...),
359
- context: str = Form("")
 
 
360
  ):
361
- """
362
- Explain a specific topic
363
-
364
- Args:
365
- topic: Topic to explain
366
- context: Context from the material
367
- """
368
  try:
369
- if not topic:
370
- raise HTTPException(
371
- status_code=400,
372
- detail="No topic provided."
373
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374
 
375
- # Generate explanation based on topic
376
- if context and len(context) > 100:
377
- # If we have context, create a more specific explanation
378
- explanation = f"""**Explanation of {topic}:**
379
-
380
- Based on the provided material, {topic} appears to be an important concept.
381
-
382
- The text mentions this in the context of: "{context[:200]}..."
383
-
384
- Key points about {topic}:
385
- • Likely relates to the main themes discussed
386
- • May be connected to other concepts in the text
387
- • Understanding this is important for grasping the material
388
-
389
- To better understand {topic}, try:
390
- 1. Looking for definitions in the text
391
- 2. Finding examples of how it's used
392
- 3. Creating a flashcard to review later"""
393
- else:
394
- # Generic explanation
395
- explanation = f"""**Explanation of {topic}:**
396
-
397
- {topic} is a concept that requires understanding. In learning materials, concepts like this typically:
398
-
399
- • Represent important ideas or principles
400
- • Connect to other topics in meaningful ways
401
- • Have practical applications or implications
402
-
403
- To master {topic}:
404
- 1. Read about it in multiple sources
405
- 2. Try explaining it in your own words
406
- 3. Create practice questions about it
407
- 4. Review it regularly using spaced repetition"""
408
-
409
  return {
410
  "success": True,
411
- "topic": topic,
412
- "explanation": explanation,
413
- "generated_at": datetime.now().isoformat()
 
 
 
414
  }
415
 
416
- except HTTPException:
417
- raise
418
  except Exception as e:
419
- raise HTTPException(
420
- status_code=500,
421
- detail=f"Failed to generate explanation: {str(e)}"
422
- )
423
 
424
- @app.post("/api/upload-pdf")
425
- async def upload_pdf(file: UploadFile = File(...)):
426
- """
427
- Extract text from uploaded PDF
428
-
429
- Args:
430
- file: PDF file to process
431
- """
432
  try:
433
- # Check file type
434
- if not file.filename.lower().endswith('.pdf'):
435
- raise HTTPException(
436
- status_code=400,
437
- detail="Only PDF files are supported."
438
- )
439
-
440
- # Read file content
441
- contents = await file.read()
442
-
443
- if len(contents) > 10 * 1024 * 1024: # 10MB limit
444
- raise HTTPException(
445
- status_code=400,
446
- detail="File too large. Maximum size is 10MB."
447
- )
448
-
449
- # Extract text from PDF
450
- pdf_file = io.BytesIO(contents)
451
- pdf_reader = PyPDF2.PdfReader(pdf_file)
452
-
453
- if len(pdf_reader.pages) == 0:
454
- raise HTTPException(
455
- status_code=400,
456
- detail="PDF file appears to be empty or corrupted."
457
- )
458
-
459
- # Extract text from pages (limit to first 10 pages for performance)
460
- text = ""
461
- pages_to_extract = min(10, len(pdf_reader.pages))
462
-
463
- for i in range(pages_to_extract):
464
- try:
465
- page_text = pdf_reader.pages[i].extract_text()
466
- if page_text:
467
- text += page_text + "\n\n"
468
- except Exception as e:
469
- print(f"Error extracting page {i+1}: {e}")
470
- continue
471
 
472
- if not text.strip():
473
- raise HTTPException(
474
- status_code=400,
475
- detail="Could not extract text from PDF. The file might be scanned or image-based."
476
- )
477
 
478
- # Extract metadata
479
- word_count = len(text.split())
480
 
481
- # Store session
482
- session_id = f"session_{datetime.now().timestamp()}"
483
- user_sessions[session_id] = {
484
- "filename": file.filename,
485
- "size": len(contents),
486
- "pages": len(pdf_reader.pages),
487
- "extracted_pages": pages_to_extract,
488
- "word_count": word_count,
489
- "uploaded_at": datetime.now().isoformat()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
490
  }
491
 
492
  return {
493
  "success": True,
494
- "session_id": session_id,
495
- "filename": file.filename,
496
- "text": text,
497
- "text_preview": text[:1000],
498
- "pages": len(pdf_reader.pages),
499
- "extracted_pages": pages_to_extract,
500
- "word_count": word_count,
501
- "file_size": len(contents),
502
- "uploaded_at": datetime.now().isoformat()
503
  }
504
 
505
- except HTTPException:
506
- raise
507
  except Exception as e:
508
- raise HTTPException(
509
- status_code=500,
510
- detail=f"Failed to process PDF: {str(e)}"
511
- )
512
 
513
  @app.get("/api/session/{session_id}")
514
- async def get_session(session_id: str):
515
- """Get information about a session"""
516
  if session_id not in user_sessions:
517
  raise HTTPException(status_code=404, detail="Session not found")
518
 
 
519
  return {
520
  "success": True,
521
- "session": user_sessions[session_id]
 
 
 
 
 
522
  }
523
 
524
- @app.delete("/api/session/{session_id}")
525
- async def delete_session(session_id: str):
526
- """Delete a session"""
527
- if session_id in user_sessions:
528
- del user_sessions[session_id]
529
- return {"success": True, "message": "Session deleted"}
530
- else:
531
- raise HTTPException(status_code=404, detail="Session not found")
532
 
533
  if __name__ == "__main__":
534
  import uvicorn
 
1
  """
2
+ StudyFlow Enhanced Backend - Complete AI Study Assistant
3
+ Features: PDF, YouTube, Question Generation, Flashcards, Note Creation, Context Analysis
4
  """
5
 
6
+ from fastapi import FastAPI, File, UploadFile, Form, HTTPException, Query
7
  from fastapi.middleware.cors import CORSMiddleware
8
+ from fastapi.responses import JSONResponse, StreamingResponse
9
  from datetime import datetime
10
  import json
11
  import PyPDF2
12
  import io
13
  import re
14
+ from typing import List, Dict, Any, Optional
15
  import os
16
+ import urllib.parse
17
+ import requests
18
+ from youtube_transcript_api import YouTubeTranscriptApi
19
+ import random
20
+ import time
21
 
22
  app = FastAPI(
23
+ title="StudyFlow AI Assistant",
24
+ description="Comprehensive AI-powered learning platform",
25
+ version="2.0.0"
26
  )
27
 
28
+ # Enable CORS
29
  app.add_middleware(
30
  CORSMiddleware,
31
  allow_origins=["*"],
 
34
  allow_headers=["*"],
35
  )
36
 
37
+ # Store for sessions
38
  user_sessions = {}
39
+ vocabulary_database = {}
40
 
41
+ # Enhanced vocabulary database
42
+ ENHANCED_VOCABULARY = {
43
+ "algorithm": {
44
+ "definition": "A set of step-by-step instructions to solve a problem",
45
+ "example": "Search algorithms like binary search help find data efficiently",
46
+ "contexts": ["computer science", "mathematics", "problem solving"],
47
+ "difficulty": "intermediate"
48
+ },
49
+ "hypothesis": {
50
+ "definition": "A proposed explanation made on limited evidence",
51
+ "example": "Scientists test their hypothesis through experiments",
52
+ "contexts": ["science", "research", "statistics"],
53
+ "difficulty": "basic"
54
+ },
55
+ # Add more words as needed
56
+ }
57
+
58
+ def extract_youtube_id(url: str) -> Optional[str]:
59
+ """Extract YouTube video ID from URL"""
60
+ patterns = [
61
+ r'(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([a-zA-Z0-9_-]{11})',
62
+ r'youtube\.com\/watch\?.+&v=([a-zA-Z0-9_-]{11})'
63
+ ]
64
+
65
+ for pattern in patterns:
66
+ match = re.search(pattern, url)
67
+ if match:
68
+ return match.group(1)
69
+ return None
70
+
71
+ def get_youtube_transcript(video_id: str) -> Optional[str]:
72
+ """Get transcript from YouTube video"""
73
+ try:
74
+ transcript_list = YouTubeTranscriptApi.get_transcript(video_id)
75
+ transcript = " ".join([entry['text'] for entry in transcript_list])
76
+ return transcript
77
+ except Exception as e:
78
+ print(f"Error getting transcript: {e}")
79
+ return None
80
+
81
+ def extract_key_concepts(text: str, max_concepts: int = 10) -> List[Dict[str, Any]]:
82
+ """Extract key concepts with context"""
83
  # Find capitalized words and phrases
84
  words = re.findall(r'\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*\b', text)
85
  word_freq = {}
86
 
87
  for word in words:
88
+ if len(word) > 3:
89
  word_freq[word] = word_freq.get(word, 0) + 1
90
 
91
+ # Get context for each concept
92
+ concepts = []
93
+ for concept, freq in sorted(word_freq.items(), key=lambda x: x[1], reverse=True)[:max_concepts]:
94
+ # Find sentences containing the concept
95
+ sentences = re.findall(r'[^.!?]*\b' + re.escape(concept) + r'\b[^.!?]*[.!?]', text)
96
+
97
+ concepts.append({
98
+ "concept": concept,
99
+ "frequency": freq,
100
+ "context": sentences[0] if sentences else "",
101
+ "importance": min(100, freq * 10)
102
+ })
103
+
104
+ return concepts
105
 
106
+ def generate_enhanced_questions(text: str, difficulty: str, question_type: str = "mixed") -> List[Dict[str, Any]]:
107
+ """Generate enhanced questions"""
108
+ concepts = extract_key_concepts(text, 5)
109
+ questions = []
110
 
111
+ # Multiple Choice Questions
112
+ if question_type in ["multiple_choice", "mixed"]:
113
+ for i, concept in enumerate(concepts[:3]):
114
+ questions.append({
115
+ "id": f"mcq_{int(time.time())}_{i}",
116
+ "type": "multiple_choice",
117
+ "question": f"What is the significance of '{concept['concept']}' in the context?",
118
+ "options": [
119
+ f"It represents a core principle discussed",
120
+ f"It's a technical term requiring specialized knowledge",
121
+ f"It's mentioned but not explained in detail",
122
+ f"It contradicts the main argument"
123
+ ],
124
+ "correct_answer": random.randint(0, 3),
125
+ "explanation": f"This concept appears {concept['frequency']} times and is discussed in context: {concept['context'][:100]}...",
126
+ "difficulty": difficulty,
127
+ "concept": concept['concept'],
128
+ "tags": ["comprehension", "analysis"]
129
+ })
130
+
131
+ # True/False Questions
132
+ for i in range(2):
133
+ questions.append({
134
+ "id": f"tf_{int(time.time())}_{i}",
135
+ "type": "true_false",
136
+ "question": f"The text emphasizes practical applications over theoretical concepts.",
137
+ "options": ["True", "False"],
138
+ "correct_answer": random.randint(0, 1),
139
+ "explanation": "This tests understanding of the text's focus and orientation.",
140
+ "difficulty": "medium",
141
+ "tags": ["comprehension", "critical_thinking"]
142
+ })
143
+
144
+ # Fill in the Blank
145
+ if len(text) > 100:
146
+ sentences = re.findall(r'[^.!?]{50,150}[.!?]', text)
147
+ if sentences:
148
+ for i, sentence in enumerate(sentences[:2]):
149
+ words = sentence.split()
150
+ if len(words) > 5:
151
+ blank_idx = random.randint(2, len(words)-3)
152
+ blank_word = words[blank_idx]
153
+ words[blank_idx] = "______"
154
+ blank_sentence = " ".join(words)
155
+
156
+ questions.append({
157
+ "id": f"fitb_{int(time.time())}_{i}",
158
+ "type": "fill_blank",
159
+ "question": f"Complete the sentence: '{blank_sentence}'",
160
+ "correct_answer": blank_word,
161
+ "explanation": f"The missing word is '{blank_word}', which completes the meaning.",
162
+ "difficulty": difficulty,
163
+ "tags": ["vocabulary", "context"]
164
+ })
165
+
166
+ # Matching Questions
167
+ questions.append({
168
+ "id": f"match_{int(time.time())}",
169
+ "type": "matching",
170
+ "question": "Match the concepts with their descriptions:",
171
+ "pairs": [
172
+ {"left": "Main Argument", "right": "Primary point being made"},
173
+ {"left": "Supporting Evidence", "right": "Facts or examples backing the argument"},
174
+ {"left": "Conclusion", "right": "Final summary or implication"}
175
+ ],
176
+ "explanation": "Matching concepts helps reinforce understanding of key terms.",
177
+ "difficulty": difficulty,
178
+ "tags": ["vocabulary", "relationships"]
179
+ })
180
+
181
+ return questions
182
+
183
+ def generate_flashcard_set(text: str, difficulty: str, flashcard_type: str = "concepts") -> List[Dict[str, Any]]:
184
+ """Generate different types of flashcards"""
185
+ concepts = extract_key_concepts(text, 15)
186
+ flashcards = []
187
+
188
+ # Concept Flashcards
189
+ if flashcard_type in ["concepts", "mixed"]:
190
+ for concept in concepts[:10]:
191
+ flashcards.append({
192
+ "id": f"fc_concept_{int(time.time())}_{hash(concept['concept'])}",
193
+ "type": "concept",
194
+ "front": f"What is '{concept['concept']}'?",
195
+ "back": f"A key concept mentioned in the text.\n\nContext: {concept['context'][:150]}",
196
+ "hint": f"Appears {concept['frequency']} times in the text",
197
+ "category": "Key Concepts",
198
+ "difficulty": "medium",
199
+ "metadata": {
200
+ "frequency": concept['frequency'],
201
+ "importance": concept['importance']
202
+ }
203
+ })
204
+
205
+ # Definition Flashcards
206
+ definitions = ["hypothesis", "algorithm", "theory", "methodology", "analysis"]
207
+ for term in definitions:
208
+ if term in ENHANCED_VOCABULARY:
209
+ info = ENHANCED_VOCABULARY[term]
210
+ flashcards.append({
211
+ "id": f"fc_def_{int(time.time())}_{term}",
212
+ "type": "definition",
213
+ "front": f"Define: {term}",
214
+ "back": f"{info['definition']}\n\nExample: {info['example']}",
215
+ "hint": f"Used in {', '.join(info['contexts'])}",
216
+ "category": "Vocabulary",
217
+ "difficulty": info['difficulty']
218
+ })
219
+
220
+ # Question-Answer Flashcards
221
+ qa_pairs = [
222
+ {"q": "What is active recall?", "a": "Retrieving information from memory without cues"},
223
+ {"q": "Define spaced repetition", "a": "Studying at increasing intervals for better retention"},
224
+ {"q": "What is interleaving?", "a": "Mixing different topics during study sessions"}
225
+ ]
226
+
227
+ for i, qa in enumerate(qa_pairs):
228
+ flashcards.append({
229
+ "id": f"fc_qa_{int(time.time())}_{i}",
230
+ "type": "qa",
231
+ "front": qa["q"],
232
+ "back": qa["a"],
233
+ "hint": "Study technique",
234
+ "category": "Learning Methods",
235
+ "difficulty": "basic"
236
+ })
237
+
238
+ return flashcards
239
+
240
+ def create_study_plan(text: str, study_time: int = 60) -> Dict[str, Any]:
241
+ """Create personalized study plan"""
242
+ word_count = len(text.split())
243
+ reading_time = max(5, word_count // 200) # 200 wpm
244
+
245
+ # Analyze text complexity
246
+ sentences = re.findall(r'[^.!?]+[.!?]', text)
247
+ avg_sentence_length = sum(len(s.split()) for s in sentences) / max(1, len(sentences))
248
+ complexity = "basic" if avg_sentence_length < 15 else "intermediate" if avg_sentence_length < 25 else "advanced"
249
+
250
+ # Generate study plan
251
+ plan = {
252
+ "overview": {
253
+ "word_count": word_count,
254
+ "estimated_reading_time": f"{reading_time} minutes",
255
+ "complexity": complexity,
256
+ "recommended_sessions": max(1, reading_time // 20)
257
+ },
258
+ "sessions": []
259
+ }
260
+
261
+ # Create session breakdown
262
+ for i in range(min(5, plan["overview"]["recommended_sessions"])):
263
+ session = {
264
+ "session": i + 1,
265
+ "duration": "20 minutes",
266
+ "focus": ["Preview", "Active Reading", "Note-taking", "Review"][i % 4],
267
+ "activities": [
268
+ "Skim headings and summaries",
269
+ "Read actively with questions in mind",
270
+ "Take structured notes",
271
+ "Create flashcards for key terms",
272
+ "Test recall with practice questions"
273
+ ][:3],
274
+ "goals": [
275
+ "Identify main concepts",
276
+ "Understand relationships",
277
+ "Memorize key terms",
278
+ "Apply knowledge"
279
+ ][i % 4]
280
  }
281
+ plan["sessions"].append(session)
282
+
283
+ return plan
284
 
285
+ def analyze_vocabulary(text: str) -> Dict[str, Any]:
286
+ """Analyze vocabulary in text"""
287
+ words = re.findall(r'\b[a-zA-Z]{4,}\b', text.lower())
288
+ total_words = len(words)
289
+ unique_words = len(set(words))
290
+
291
+ # Find potentially difficult words
292
+ difficult_words = []
293
+ for word in set(words):
294
+ if len(word) > 8 and word not in ["information", "understand", "important"]:
295
+ difficult_words.append({
296
+ "word": word,
297
+ "frequency": words.count(word),
298
+ "suggested_definition": f"A term meaning {word} in context"
299
+ })
300
+
301
  return {
302
+ "total_words": total_words,
303
+ "unique_words": unique_words,
304
+ "vocabulary_richness": round(unique_words / max(1, total_words) * 100, 1),
305
+ "difficult_words": sorted(difficult_words, key=lambda x: x["frequency"], reverse=True)[:10]
 
 
 
 
306
  }
307
 
308
  @app.get("/")
309
  async def root():
310
+ """Root endpoint"""
311
  return {
312
+ "service": "StudyFlow AI Assistant",
313
+ "version": "2.0.0",
314
  "status": "running",
315
+ "features": [
316
+ "PDF/Text processing",
317
+ "YouTube video summarization",
318
+ "Question generation (multiple types)",
319
+ "Flashcard creation",
320
+ "Vocabulary analysis",
321
+ "Study planning",
322
+ "Note creation",
323
+ "Context analysis"
324
+ ]
 
 
 
 
 
 
 
 
325
  }
326
 
327
+ @app.post("/api/process-content")
328
+ async def process_content(
329
+ content_type: str = Form(...),
330
+ content: str = Form(None),
331
+ file: UploadFile = File(None),
332
+ youtube_url: str = Form(None),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
333
  difficulty: str = Form("medium")
334
  ):
335
+ """Process any type of content (PDF, text, YouTube)"""
 
 
 
 
 
 
336
  try:
337
+ extracted_text = ""
338
+ metadata = {}
 
 
 
339
 
340
+ if content_type == "text" and content:
341
+ extracted_text = content
342
+ metadata = {"type": "text", "length": len(content)}
 
 
 
 
 
 
 
343
 
344
+ elif content_type == "pdf" and file:
345
+ contents = await file.read()
346
+ pdf_file = io.BytesIO(contents)
347
+ pdf_reader = PyPDF2.PdfReader(pdf_file)
 
 
 
348
 
349
+ text = ""
350
+ for i in range(min(10, len(pdf_reader.pages))):
351
+ page_text = pdf_reader.pages[i].extract_text()
352
+ if page_text:
353
+ text += page_text + "\n\n"
354
+
355
+ extracted_text = text
356
+ metadata = {
357
+ "type": "pdf",
358
+ "filename": file.filename,
359
+ "pages": len(pdf_reader.pages),
360
+ "extracted_pages": min(10, len(pdf_reader.pages))
 
361
  }
362
+
363
+ elif content_type == "youtube" and youtube_url:
364
+ video_id = extract_youtube_id(youtube_url)
365
+ if not video_id:
366
+ raise HTTPException(status_code=400, detail="Invalid YouTube URL")
367
+
368
+ transcript = get_youtube_transcript(video_id)
369
+ if not transcript:
370
+ raise HTTPException(status_code=400, detail="Could not extract transcript")
371
+
372
+ extracted_text = transcript
373
+ metadata = {
374
+ "type": "youtube",
375
+ "video_id": video_id,
376
+ "url": youtube_url
377
+ }
378
+
379
+ if not extracted_text or len(extracted_text.strip()) < 100:
380
+ raise HTTPException(status_code=400, detail="Insufficient content")
381
 
382
+ # Generate all study materials
383
+ questions = generate_enhanced_questions(extracted_text, difficulty)
384
+ flashcards = generate_flashcard_set(extracted_text, difficulty)
385
+ vocabulary = analyze_vocabulary(extracted_text)
386
+ study_plan = create_study_plan(extracted_text)
387
+ concepts = extract_key_concepts(extracted_text)
388
+
389
+ # Create session
390
+ session_id = f"session_{int(time.time())}_{random.randint(1000, 9999)}"
391
+ user_sessions[session_id] = {
392
+ "metadata": metadata,
393
+ "text_preview": extracted_text[:500],
394
+ "word_count": len(extracted_text.split()),
395
+ "created_at": datetime.now().isoformat()
396
+ }
397
 
398
  return {
399
  "success": True,
400
+ "session_id": session_id,
401
+ "metadata": metadata,
402
+ "materials": {
403
+ "questions": {
404
+ "total": len(questions),
405
+ "by_type": {
406
+ "multiple_choice": len([q for q in questions if q["type"] == "multiple_choice"]),
407
+ "true_false": len([q for q in questions if q["type"] == "true_false"]),
408
+ "fill_blank": len([q for q in questions if q["type"] == "fill_blank"]),
409
+ "matching": len([q for q in questions if q["type"] == "matching"])
410
+ },
411
+ "items": questions
412
+ },
413
+ "flashcards": {
414
+ "total": len(flashcards),
415
+ "by_type": {
416
+ "concept": len([f for f in flashcards if f["type"] == "concept"]),
417
+ "definition": len([f for f in flashcards if f["type"] == "definition"]),
418
+ "qa": len([f for f in flashcards if f["type"] == "qa"])
419
+ },
420
+ "items": flashcards
421
+ },
422
+ "vocabulary": vocabulary,
423
+ "study_plan": study_plan,
424
+ "key_concepts": concepts,
425
+ "summary": extracted_text[:300] + ("..." if len(extracted_text) > 300 else "")
426
+ }
427
  }
428
 
429
  except HTTPException:
430
  raise
431
  except Exception as e:
432
+ raise HTTPException(status_code=500, detail=f"Processing error: {str(e)}")
 
 
 
433
 
434
+ @app.post("/api/analyze-word")
435
+ async def analyze_word(
436
+ word: str = Form(...),
437
+ context: str = Form("")
438
  ):
439
+ """Analyze a word with context"""
 
 
 
 
 
 
440
  try:
441
+ word = word.lower().strip()
 
 
 
 
442
 
443
+ # Check enhanced vocabulary
444
+ if word in ENHANCED_VOCABULARY:
445
+ definition = ENHANCED_VOCABULARY[word]
 
 
 
446
  else:
447
+ # Generate contextual definition
448
+ definition = {
449
+ "definition": f"A term used in this context meaning '{word}'",
450
+ "example": f"In your material, '{word}' appears in context",
451
+ "contexts": ["general"],
452
+ "difficulty": "unknown"
453
+ }
 
 
 
 
 
 
 
454
 
455
+ # If context provided, enhance analysis
456
+ contextual_info = {}
457
+ if context:
458
+ # Find word in context
459
+ sentences = re.findall(r'[^.!?]*\b' + re.escape(word) + r'\b[^.!?]*[.!?]', context, re.IGNORECASE)
460
+ if sentences:
461
+ contextual_info = {
462
+ "example_sentence": sentences[0],
463
+ "occurrences": len(sentences),
464
+ "surrounding_words": list(set(re.findall(r'\b\w+\b', sentences[0])))[:5]
465
+ }
466
 
 
 
 
 
 
 
 
 
 
 
 
 
 
467
  return {
468
  "success": True,
469
+ "word": word,
470
+ "definition": definition,
471
+ "contextual_info": contextual_info,
472
+ "suggested_flashcards": [
473
+ {
474
+ "front": f"Define: {word}",
475
+ "back": definition["definition"],
476
+ "category": "Vocabulary"
477
+ },
478
+ {
479
+ "front": f"Use '{word}' in a sentence",
480
+ "back": contextual_info.get("example_sentence", f"Example using '{word}'"),
481
+ "category": "Usage"
482
+ }
483
+ ]
484
  }
485
 
 
 
486
  except Exception as e:
487
+ raise HTTPException(status_code=500, detail=f"Word analysis error: {str(e)}")
 
 
 
488
 
489
+ @app.post("/api/create-note")
490
+ async def create_note(
491
+ title: str = Form(...),
492
+ content: str = Form(...),
493
+ tags: List[str] = Form([]),
494
+ session_id: str = Form(None)
495
  ):
496
+ """Create structured notes"""
 
 
 
 
 
 
497
  try:
498
+ note_id = f"note_{int(time.time())}_{random.randint(1000, 9999)}"
499
+
500
+ # Extract key points automatically
501
+ sentences = re.findall(r'[^.!?]+[.!?]', content)
502
+ key_points = sentences[:5] # First 5 sentences as key points
503
+
504
+ # Generate summary
505
+ summary = content[:150] + ("..." if len(content) > 150 else "")
506
+
507
+ note = {
508
+ "id": note_id,
509
+ "title": title,
510
+ "content": content,
511
+ "summary": summary,
512
+ "key_points": key_points,
513
+ "tags": tags + ["auto-generated"],
514
+ "created_at": datetime.now().isoformat(),
515
+ "session_id": session_id,
516
+ "word_count": len(content.split())
517
+ }
518
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
519
  return {
520
  "success": True,
521
+ "note": note,
522
+ "suggested_actions": [
523
+ "Create flashcards from key points",
524
+ "Generate quiz questions",
525
+ "Add to study plan"
526
+ ]
527
  }
528
 
 
 
529
  except Exception as e:
530
+ raise HTTPException(status_code=500, detail=f"Note creation error: {str(e)}")
 
 
 
531
 
532
+ @app.post("/api/generate-practice-test")
533
+ async def generate_practice_test(
534
+ session_id: str = Form(...),
535
+ test_type: str = Form("comprehensive"),
536
+ question_count: int = Form(10)
537
+ ):
538
+ """Generate a practice test"""
 
539
  try:
540
+ if session_id not in user_sessions:
541
+ raise HTTPException(status_code=404, detail="Session not found")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
542
 
543
+ # In production, fetch from database
544
+ # For now, generate sample test
545
+ test_id = f"test_{int(time.time())}"
 
 
546
 
547
+ questions = []
548
+ question_types = ["multiple_choice", "true_false", "fill_blank", "matching"]
549
 
550
+ for i in range(min(question_count, 20)):
551
+ q_type = random.choice(question_types)
552
+
553
+ if q_type == "multiple_choice":
554
+ questions.append({
555
+ "id": f"test_q_{i}",
556
+ "type": "multiple_choice",
557
+ "question": f"Practice question #{i+1}: What is the main focus?",
558
+ "options": ["Concept A", "Concept B", "Concept C", "Concept D"],
559
+ "correct_answer": random.randint(0, 3),
560
+ "points": 1,
561
+ "explanation": "This tests understanding of key concepts"
562
+ })
563
+ elif q_type == "true_false":
564
+ questions.append({
565
+ "id": f"test_q_{i}",
566
+ "type": "true_false",
567
+ "question": f"Statement #{i+1}: The text emphasizes practical applications.",
568
+ "options": ["True", "False"],
569
+ "correct_answer": random.randint(0, 1),
570
+ "points": 1,
571
+ "explanation": "True/False questions test binary understanding"
572
+ })
573
+
574
+ test = {
575
+ "id": test_id,
576
+ "session_id": session_id,
577
+ "type": test_type,
578
+ "questions": questions,
579
+ "total_points": len(questions),
580
+ "time_limit": len(questions) * 60, # 1 minute per question
581
+ "instructions": "Answer all questions. You can review your answers before submitting."
582
  }
583
 
584
  return {
585
  "success": True,
586
+ "test": test,
587
+ "recommendations": {
588
+ "preparation": "Review key concepts before starting",
589
+ "time_management": f"Allocate ~{len(questions)} minutes",
590
+ "strategy": "Answer easier questions first"
591
+ }
 
 
 
592
  }
593
 
 
 
594
  except Exception as e:
595
+ raise HTTPException(status_code=500, detail=f"Test generation error: {str(e)}")
 
 
 
596
 
597
  @app.get("/api/session/{session_id}")
598
+ async def get_session_details(session_id: str):
599
+ """Get detailed session information"""
600
  if session_id not in user_sessions:
601
  raise HTTPException(status_code=404, detail="Session not found")
602
 
603
+ session = user_sessions[session_id]
604
  return {
605
  "success": True,
606
+ "session": session,
607
+ "analytics": {
608
+ "duration": "N/A",
609
+ "completion": "0%",
610
+ "performance": "Not assessed"
611
+ }
612
  }
613
 
614
+ @app.get("/health")
615
+ async def health():
616
+ return {"status": "healthy", "timestamp": datetime.now().isoformat()}
 
 
 
 
 
617
 
618
  if __name__ == "__main__":
619
  import uvicorn